gloo-web 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,152 @@
1
+ # Author:: Eric Crane (mailto:eric.crane@mac.com)
2
+ # Copyright:: Copyright (c) 2024 Eric Crane. All rights reserved.
3
+ #
4
+ # A helper class used to render parameters (ERB) in text.
5
+ # Also uses helper functions to render.
6
+ #
7
+
8
+ module WebSvr
9
+ class EmbeddedRenderer
10
+
11
+ HELPER = 'helper'.freeze
12
+
13
+ attr_reader :engine, :log, :web_svr_obj
14
+
15
+
16
+ # ---------------------------------------------------------------------
17
+ # Initialization
18
+ # ---------------------------------------------------------------------
19
+
20
+ #
21
+ # Set up the web server.
22
+ #
23
+ def initialize( engine, web_svr_obj )
24
+ @engine = engine
25
+ @log = @engine.log
26
+
27
+ @web_svr_obj = web_svr_obj
28
+ end
29
+
30
+
31
+ # ---------------------------------------------------------------------
32
+ # Tag Helpers
33
+ # ---------------------------------------------------------------------
34
+
35
+ #
36
+ # Render a favicon tag.
37
+ # By default the name is 'favicon.ico' and does not need to be provided
38
+ # if that is the correct file name.
39
+ #
40
+ def favicon_tag( name = 'favicon.ico' )
41
+ icon_path = "/#{Asset::ASSET_FOLDER}/#{Asset::IMAGE_FOLDER}/#{name}"
42
+ published_name = @engine.running_app.obj.asset.published_name( icon_path )
43
+ return "<link rel='shortcut icon' type='image/x-icon' href='#{published_name}' />"
44
+ end
45
+
46
+ #
47
+ # Render a Apple Touch Icon tag.
48
+ # By default the name is 'apple-touch-icon.png' and does not need to be provided
49
+ # if that is the correct file name.
50
+ #
51
+ def apple_touch_icon_tag( name = 'apple-touch-icon.png', type = 'image/png' )
52
+ icon_path = "/#{Asset::ASSET_FOLDER}/#{Asset::IMAGE_FOLDER}/#{name}"
53
+ published_name = @engine.running_app.obj.asset.published_name( icon_path )
54
+ return "<link rel='apple-touch-icon' type='#{type}' href='#{published_name}' />"
55
+ end
56
+
57
+ #
58
+ # Render an image tag for the given image name.
59
+ # Include optional proterties as part of the tag.
60
+ #
61
+ def image_tag( img_name, properties = '' )
62
+ image_path = "/#{Asset::ASSET_FOLDER}/#{Asset::IMAGE_FOLDER}/#{img_name}"
63
+ published_name = @engine.running_app.obj.asset.published_name( image_path )
64
+ return "<image src='#{published_name}' #{properties} />"
65
+ end
66
+
67
+ #
68
+ # Render a script tag for the given script name.
69
+ #
70
+ def js_tag( name )
71
+ js_path = "/#{Asset::ASSET_FOLDER}/#{Asset::JAVASCRIPT_FOLDER}/#{name}"
72
+ published_name = @engine.running_app.obj.asset.published_name( js_path )
73
+ return "<script src='#{published_name}'></script>"
74
+ end
75
+
76
+ #
77
+ # Render a stylesheet tag for the given stylesheet name.
78
+ #
79
+ def css_tag( name )
80
+ css_path = "/#{Asset::ASSET_FOLDER}/#{Asset::STYLESHEET_FOLDER}/#{name}"
81
+ published_name = @engine.running_app.obj.asset.published_name( css_path )
82
+ return "<link rel='stylesheet' media='all' href='#{published_name}' />"
83
+ end
84
+
85
+ #
86
+ # Embed a hidden field with the autenticity token.
87
+ #
88
+ def autenticity_token_tag
89
+ session_id = @engine.running_app.obj&.session&.get_session_id
90
+ return Gloo::Objs::CsrfToken.get_csrf_token_hidden_field( session_id )
91
+ end
92
+
93
+
94
+ # ---------------------------------------------------------------------
95
+ # Obj Helper Functions
96
+ # ---------------------------------------------------------------------
97
+
98
+ #
99
+ # Handle a missing method by looking for a helper function.
100
+ # If there is one, then call it and return the result.
101
+ # If not, log an error and return nil.
102
+ #
103
+ def method_missing( method_name, *args )
104
+ @log.debug "missing method '#{method_name}' with args #{args}"
105
+
106
+ helper_pn = "#{HELPER}.#{method_name}"
107
+ @log.debug "looking for function: #{helper_pn}"
108
+
109
+ pn = Gloo::Core::Pn.new( @engine, helper_pn )
110
+ obj = pn.resolve
111
+ if obj
112
+ @log.debug "found obj: #{obj.pn}"
113
+ return obj.invoke args
114
+ else
115
+ @log.error "Function not found: #{helper_pn}"
116
+ end
117
+
118
+ return nil
119
+ end
120
+
121
+
122
+ # ---------------------------------------------------------------------
123
+ # Renderer
124
+ # ---------------------------------------------------------------------
125
+
126
+ #
127
+ # Render content with the given params.
128
+ # Params might be nil, in which case the content
129
+ # is returned with no changes.
130
+ #
131
+ def render content, params
132
+ # If the params is nil, let's make it an empty hash.
133
+ params = {} unless params
134
+
135
+ # Get the binding context for this render.
136
+ b = binding
137
+
138
+ # Add the params to the binding context.
139
+ params.each_pair do |key, value|
140
+ b.local_variable_set key.to_sym, value
141
+ end
142
+
143
+ # Render in the current binding content.
144
+ renderer = ERB.new( content )
145
+ content = renderer.result( b )
146
+
147
+ return content
148
+ end
149
+
150
+ end
151
+
152
+ end
@@ -0,0 +1,152 @@
1
+ # Author:: Eric Crane (mailto:eric.crane@mac.com)
2
+ # Copyright:: Copyright (c) 2024 Eric Crane. All rights reserved.
3
+ #
4
+ # Web application request handler.
5
+ # Takes a request and does what is needed to create a response.
6
+ #
7
+
8
+ module WebSvr
9
+ class Handler
10
+
11
+ attr_reader :server_obj
12
+
13
+
14
+ # ---------------------------------------------------------------------
15
+ # Initialization
16
+ # ---------------------------------------------------------------------
17
+
18
+ #
19
+ # Set up the web server.
20
+ #
21
+ def initialize( engine, obj )
22
+ @engine = engine
23
+ @log = @engine.log
24
+ @server_obj = obj
25
+ end
26
+
27
+
28
+ # ---------------------------------------------------------------------
29
+ # Process Request
30
+ # ---------------------------------------------------------------------
31
+
32
+ #
33
+ # Process the request and return a result.
34
+ #
35
+ def handle request
36
+ @request = request
37
+ page_obj = nil
38
+ route_params = nil
39
+
40
+ page, id, route_params = @server_obj.router.page_for_route( @request.path, @request.method )
41
+ @engine.log.debug "Found Page: #{page&.name}" if page
42
+
43
+ request.request_params.id = id
44
+ request.request_params.route_params = route_params
45
+ request.request_params.log_id_keys
46
+
47
+ if page
48
+ # Run the on_request script with the found page.
49
+ @server_obj.run_on_request( page )
50
+
51
+ if page.is_a? Gloo::Objs::FileHandle
52
+ result = handle_file page
53
+ else
54
+ result = handle_page page
55
+ page_obj = page
56
+ end
57
+ else
58
+ result = server_error_result
59
+ end
60
+
61
+ return result, page_obj
62
+ end
63
+
64
+ #
65
+ # Handle request for a page.
66
+ # Render the page, with possible redirect.
67
+ #
68
+ def handle_page page
69
+ result = page.render @request
70
+ if redirect_hard_set?
71
+ result = server_redirect_result
72
+ @engine.running_app.obj.redirect_hard = nil
73
+ elsif redirect_set?
74
+ page = @engine.running_app.obj.redirect
75
+ @log.debug "Redirecting to: #{page.pn}"
76
+ @engine.running_app.obj.redirect = nil
77
+ result = page.render
78
+ end
79
+ return result
80
+ end
81
+
82
+ #
83
+ # Handle a request for a static file such as an image.
84
+ #
85
+ def handle_file file
86
+ pn = @server_obj.asset.path_for_file file
87
+
88
+ # Check to make sure it is a valid file
89
+ # return error if it is not
90
+ return file_error_result unless File.exist? pn
91
+
92
+ return @server_obj.asset.render_file pn
93
+ end
94
+
95
+
96
+ # ---------------------------------------------------------------------
97
+ # Errors
98
+ # ---------------------------------------------------------------------
99
+
100
+ #
101
+ # Return a server error result.
102
+ # Use the app's error if there is one, otherwise a generic message.
103
+ #
104
+ def server_error_result
105
+ err_page = @server_obj.err_page
106
+ return err_page.render if err_page
107
+
108
+ # Last resort, just return a generic error message.
109
+ return WebSvr::Response.text_response( @engine,
110
+ "Server error!", WebSvr::ResponseCode::SERVER_ERR )
111
+ end
112
+
113
+ #
114
+ # Get a file not found error result.
115
+ #
116
+ def file_error_result
117
+ return WebSvr::Response.text_response( @engine,
118
+ "File not found!", WebSvr::ResponseCode::NOT_FOUND )
119
+ end
120
+
121
+
122
+ # ---------------------------------------------------------------------
123
+ # Redirect Helper functions
124
+ # ---------------------------------------------------------------------
125
+
126
+ #
127
+ # Is there a redirect page set in the running app?
128
+ #
129
+ def redirect_set?
130
+ return false unless @engine.app_running?
131
+ return @engine.running_app.obj.redirect
132
+ end
133
+
134
+ #
135
+ # Is there a redirect page set in the running app?
136
+ #
137
+ def redirect_hard_set?
138
+ return false unless @engine.app_running?
139
+ return @engine.running_app.obj.redirect_hard
140
+ end
141
+
142
+ #
143
+ # Return a redirect result.
144
+ #
145
+ def server_redirect_result
146
+ target = @engine.running_app.obj.redirect_hard
147
+
148
+ return WebSvr::Response.redirect_response( @engine, target )
149
+ end
150
+
151
+ end
152
+ end
@@ -0,0 +1,141 @@
1
+ # Author:: Eric Crane (mailto:eric.crane@mac.com)
2
+ # Copyright:: Copyright (c) 2024 Eric Crane. All rights reserved.
3
+ #
4
+ # A web Request for a page, action, or static resource.
5
+ #
6
+ # Kinds of Resources
7
+ # Web Page
8
+ # Action - does something and redirects to a page (or returns nothing)
9
+ # API - returns JSON instead of HTML (but is that different from Web Page?)
10
+ # Static Resource - File, PDF, Image, etc.
11
+ #
12
+ #
13
+ # See More doc here:
14
+ # https://www.rubydoc.info/gems/rack/Rack/Request/Helpers#path-instance_method
15
+ #
16
+
17
+ module WebSvr
18
+ class Request
19
+
20
+ REQUEST_METHOD = 'REQUEST_METHOD'.freeze
21
+ REQUEST_PATH = 'REQUEST_PATH'.freeze
22
+ HTTP_HOST = 'HTTP_HOST'.freeze
23
+ QUERY_STRING = 'QUERY_STRING'.freeze
24
+
25
+ attr_reader :method, :host, :path, :ip, :query
26
+ attr_reader :db, :elapsed
27
+ attr_accessor :request_params
28
+
29
+
30
+ # ---------------------------------------------------------------------
31
+ # Initialization
32
+ # ---------------------------------------------------------------------
33
+
34
+ #
35
+ # Set up the web server.
36
+ #
37
+ def initialize( engine, handler, env = nil )
38
+ @engine = engine
39
+ @log = @engine.log
40
+ @request_params = RequestParams.new( @log )
41
+
42
+ @handler = handler
43
+
44
+ @env = env
45
+ detect_env
46
+ end
47
+
48
+
49
+ # ---------------------------------------------------------------------
50
+ # Process Request
51
+ # ---------------------------------------------------------------------
52
+
53
+ #
54
+ # Process the request and return a result.
55
+ #
56
+ def process
57
+ start_timer
58
+
59
+ # Run the on_request script if there is one.
60
+ @handler.server_obj.set_request_data self
61
+
62
+ # Check authenticity token if it's given.
63
+ if @request_params.check_authenticity_token( @engine )
64
+ result, page_obj = @handler.handle self
65
+ else
66
+ # Render the error page.
67
+ result = @handler.server_error_result
68
+ end
69
+
70
+ finish_timer
71
+
72
+ # Run the on_response script if there is one.
73
+ @handler.server_obj.set_response_data( self, result, page_obj )
74
+ @handler.server_obj.run_on_response
75
+
76
+ return result
77
+ end
78
+
79
+
80
+ # ---------------------------------------------------------------------
81
+ # ENV
82
+ # ---------------------------------------------------------------------
83
+
84
+ #
85
+ # Write the request information to the log.
86
+ #
87
+ def detect_env
88
+ req = Rack::Request.new( @env )
89
+
90
+ @method = req.request_method
91
+ @path = req.path
92
+ @host = req.host_with_port
93
+ @query = req.query_string
94
+
95
+ @request_params.init_query_params( @query )
96
+ @ip = req.ip
97
+ @handler.server_obj.session.set_session_data_for_request( @env )
98
+
99
+ @request_params.init_body_params( @env[ 'rack.input' ].read )
100
+ @method = @request_params.get_body_method_override @method
101
+ end
102
+
103
+
104
+ # ---------------------------------------------------------------------
105
+ # Request timer
106
+ # ---------------------------------------------------------------------
107
+
108
+ #
109
+ # Keep track of the request start time.
110
+ #
111
+ def start_timer
112
+ @start = Time.now
113
+ @engine.running_app.reset_db_time
114
+ end
115
+
116
+ #
117
+ # Write the request completion time to the log.
118
+ #
119
+ def finish_timer
120
+ @finish = Time.now
121
+ @elapsed = ( ( @finish - @start ) * 1000.0 ).round(2)
122
+ @db = @engine.running_app.db_time
123
+ @log.info "*** Web request complete. DB: #{@db} ms. Elapsed time: #{@elapsed} ms"
124
+ end
125
+
126
+
127
+ # ---------------------------------------------------------------------
128
+ # Helper functions
129
+ # ---------------------------------------------------------------------
130
+
131
+ #
132
+ # Write the request information to the log.
133
+ #
134
+ def log
135
+ @log.info "#{@method} #{@host}#{@path}"
136
+
137
+ @request_params.log_params
138
+ end
139
+
140
+ end
141
+ end
@@ -0,0 +1,179 @@
1
+ # Author:: Eric Crane (mailto:eric.crane@mac.com)
2
+ # Copyright:: Copyright (c) 2024 Eric Crane. All rights reserved.
3
+ #
4
+ # A Parameters associated with a request.
5
+ #
6
+ # Kinds of Params
7
+ # Id - The entity id
8
+ # Key - URL parameter key
9
+ # URL Params - Parameters in the URL
10
+ # Body Params - Data from the body of the request
11
+ #
12
+
13
+ module WebSvr
14
+ class RequestParams
15
+
16
+ attr_accessor :id, :route_params
17
+ attr_reader :query_params, :body_params, :body_binary
18
+
19
+ # ---------------------------------------------------------------------
20
+ # Initialization
21
+ # ---------------------------------------------------------------------
22
+
23
+ #
24
+ # Set up the web server.
25
+ #
26
+ def initialize( log )
27
+ @log = log
28
+ end
29
+
30
+
31
+ # ---------------------------------------------------------------------
32
+ # Value Detection
33
+ # ---------------------------------------------------------------------
34
+
35
+ #
36
+ # Detect the parameters from query string.
37
+ #
38
+ def init_query_params query_string
39
+ if query_string
40
+ @query_params = Rack::Utils.parse_query( query_string )
41
+ else
42
+ @query_params = {}
43
+ end
44
+ end
45
+
46
+ #
47
+ # Detect the parameters from the body of the request.
48
+ #
49
+ def init_body_params body
50
+ if body && body.length > 0
51
+ # if body is binary, then it is not a query string
52
+ begin
53
+ @body_params = Rack::Utils.parse_query body
54
+ rescue => exception
55
+ init_multipart body
56
+ end
57
+ else
58
+ @body_params = {}
59
+ end
60
+ end
61
+
62
+ #
63
+ # Set the body to a binary file.
64
+ #
65
+ # TODO: find a lib or method to handle this.
66
+ # This is very rough and will need to be fixed.
67
+ #
68
+ def init_multipart body
69
+ # puts "*********** first lines: *********** "
70
+ # body.lines[0..3].each { |line| puts line }
71
+ # puts "*********** last lines: *********** "
72
+ # body.lines.last(5).each { |line| puts line }
73
+ # puts "************************************"
74
+
75
+ # boundary = body.lines.first
76
+ # puts "boundary: #{boundary}"
77
+
78
+ header = body.lines[1..3].join
79
+ # puts "header: #{header}"
80
+
81
+ footer = body.lines.last(5).join
82
+ # puts "footer: #{footer}"
83
+
84
+ binary_data = body.lines[4..-6].join
85
+ # puts "binary_data length: #{binary_data.length}"
86
+ # puts "binary first line: #{binary_data.lines.first}"
87
+ # puts "binary last line: #{binary_data.lines.last}"
88
+
89
+ i = header.lines.first.index( 'filename=' )
90
+ filename = header.lines.first[ i+10..-4 ]
91
+ content_type = header.lines.second[14..-3]
92
+ # puts "filename: #{filename}"
93
+ # puts "content_type: #{content_type}"
94
+
95
+ @body_binary = body
96
+ @body_params = {}
97
+ @body_params[ 'content_type' ] = content_type
98
+ @body_params[ 'file_name' ] = filename
99
+ @body_params[ 'file_size' ] = binary_data.length
100
+ @body_params[ 'file_data' ] = binary_data
101
+ end
102
+
103
+
104
+ # ---------------------------------------------------------------------
105
+ # Authenticity Token checking
106
+ # ---------------------------------------------------------------------
107
+
108
+ #
109
+ # Check the authenticity token if it is present.
110
+ # Returns true if it is present and valid, and
111
+ # also if it is not present.
112
+ # Returns false if it is present but not valid.
113
+ #
114
+ def check_authenticity_token engine
115
+ auth_token = @query_params[ Gloo::Objs::CsrfToken::AUTHENTICITY_TOKEN ]
116
+ if auth_token
117
+ session_id = engine.running_app.obj&.session&.get_session_id
118
+ return false unless session_id
119
+
120
+ return Gloo::Objs::CsrfToken.valid_csrf_token?( session_id, auth_token )
121
+ end
122
+
123
+ return true
124
+ end
125
+
126
+
127
+ # ---------------------------------------------------------------------
128
+ # Helper functions
129
+ # ---------------------------------------------------------------------
130
+
131
+ #
132
+ # Check the body to see if there is a PATCH or a PUT in
133
+ # the method override.
134
+ #
135
+ def get_body_method_override orig_method
136
+ if @body_params[ '_method' ]
137
+ return @body_params[ '_method' ].upcase
138
+ end
139
+ return orig_method
140
+ end
141
+
142
+ #
143
+ # Write the querey and body params to the log.
144
+ #
145
+ def log_params
146
+ return unless @log
147
+
148
+ if @query_params && ! @query_params.empty?
149
+ @log.info "--- Query Parameters: #{@query_params}"
150
+ end
151
+
152
+ if @body_params && ! @body_params.empty?
153
+ if @body_params[ 'file_data' ]
154
+ # exclude the file data from the params shown
155
+ params = @body_params.dup
156
+ params.delete( 'file_data' )
157
+ params[ 'file_data' ] = '...'
158
+ @log.info "--- Body Parameters: #{params}"
159
+ else
160
+ @log.info "--- Body Parameters: #{@body_params}"
161
+ end
162
+ end
163
+ end
164
+
165
+ #
166
+ # Write the id and route params to the log.
167
+ #
168
+ def log_id_keys
169
+ return unless @log
170
+
171
+ @log.info "--- ID Parameter: #{@id}" if @id
172
+
173
+ if @route_params && ! @route_params.empty?
174
+ @log.info "--- Route Parameters: #{@route_params}"
175
+ end
176
+ end
177
+
178
+ end
179
+ end