gloo 3.1.0 → 3.2.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.
@@ -36,7 +36,9 @@ module Gloo
36
36
  def handle request
37
37
  @request = request
38
38
 
39
- page = @server_obj.router.page_for_route @request.path
39
+ page, id = @server_obj.router.page_for_route( @request.path, @request.method )
40
+ @engine.log.debug "Found Page: #{page&.name}" if page
41
+ request.id = id
40
42
  if page
41
43
  if page.is_a? Gloo::Objs::FileHandle
42
44
  return handle_file page
@@ -53,7 +55,7 @@ module Gloo
53
55
  # Render the page, with possible redirect.
54
56
  #
55
57
  def handle_page page
56
- result = page.render
58
+ result = page.render @request
57
59
  if redirect_set?
58
60
  page = @engine.running_app.obj.redirect
59
61
  @log.debug "Redirecting to: #{page.pn}"
@@ -19,7 +19,8 @@ module Gloo
19
19
  HTTP_HOST = 'HTTP_HOST'.freeze
20
20
  QUERY_STRING = 'QUERY_STRING'.freeze
21
21
 
22
- attr_reader :method, :host, :path, :query
22
+ attr_reader :method, :host, :path, :query, :body
23
+ attr_accessor :id
23
24
 
24
25
 
25
26
  # ---------------------------------------------------------------------
@@ -66,6 +67,10 @@ module Gloo
66
67
  @path = @env[ REQUEST_PATH ]
67
68
  @host = @env[ HTTP_HOST ]
68
69
  @query = @env[ QUERY_STRING ]
70
+
71
+ @body = @env[ 'rack.input' ].read
72
+ @body = Rack::Utils.parse_query @body
73
+ check_body_method
69
74
  end
70
75
 
71
76
 
@@ -78,6 +83,7 @@ module Gloo
78
83
  #
79
84
  def start_timer
80
85
  @start = Time.now
86
+ @engine.running_app.reset_db_time
81
87
  end
82
88
 
83
89
  #
@@ -86,7 +92,8 @@ module Gloo
86
92
  def finish_timer
87
93
  @finish = Time.now
88
94
  @elapsed = ( ( @finish - @start ) * 1000.0 ).round(2)
89
- @log.info "Web request complete. Elapsed time: #{@elapsed} ms"
95
+ db = @engine.running_app.db_time
96
+ @log.info "*** Web request complete. DB: #{db} ms. Elapsed time: #{@elapsed} ms"
90
97
  end
91
98
 
92
99
 
@@ -94,12 +101,38 @@ module Gloo
94
101
  # Helper functions
95
102
  # ---------------------------------------------------------------------
96
103
 
104
+ #
105
+ # Check the body to see if there is a PATCH or a PUT in
106
+ # the method override.
107
+ #
108
+ def check_body_method
109
+ if @body[ '_method' ]
110
+ @method = @body[ '_method' ].upcase
111
+ end
112
+ end
113
+
114
+ #
115
+ # Get the hash of query parameters.
116
+ #
117
+ def query_params
118
+ return {} unless @query
119
+ return Rack::Utils.parse_query( @query )
120
+ end
121
+
122
+ #
123
+ # Get the hash of body parameters.
124
+ #
125
+ def body_params
126
+ return @body ? @body : {}
127
+ end
128
+
97
129
  #
98
130
  # Write the request information to the log.
99
131
  #
100
132
  def log
101
133
  @log.info "#{@method} #{@host}#{@path}"
102
134
  @log.info "Parameters: #{@query}"
135
+ @log.info "Body: #{@body}" unless @body.empty?
103
136
  end
104
137
 
105
138
  end
@@ -0,0 +1,47 @@
1
+ # Author:: Eric Crane (mailto:eric.crane@mac.com)
2
+ # Copyright:: Copyright (c) 2024 Eric Crane. All rights reserved.
3
+ #
4
+ # A helper class for Resource routing.
5
+ #
6
+
7
+ module Gloo
8
+ module WebSvr
9
+ module Routing
10
+ class ResourceRouter
11
+
12
+ INDEX = 'index'.freeze
13
+ SHOW = 'show'.freeze
14
+ DELETE = 'delete'.freeze
15
+ UPDATE = 'update'.freeze
16
+
17
+ POST_ROUTE = 'create'.freeze
18
+
19
+
20
+ #
21
+ # Is the given route segment an implicit create resource?
22
+ # It is explicit if it is 'create'
23
+ # and implicit if it is a POST to the resource.
24
+ #
25
+ def self.is_implicit_create?( method, route_segment )
26
+ return false unless Gloo::WebSvr::WebMethod.is_post?( method )
27
+
28
+ return ! route_segment.eql?( POST_ROUTE )
29
+ end
30
+
31
+ #
32
+ # Add the segment based on the method.
33
+ #
34
+ def self.segment_for_method( method )
35
+ if Gloo::WebSvr::WebMethod.is_delete?( method )
36
+ return DELETE
37
+ elsif Gloo::WebSvr::WebMethod.is_patch?( method )
38
+ return UPDATE
39
+ else
40
+ return SHOW
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,218 @@
1
+ # Author:: Eric Crane (mailto:eric.crane@mac.com)
2
+ # Copyright:: Copyright (c) 20124 Eric Crane. All rights reserved.
3
+ #
4
+ # A helper class for page routing.
5
+ #
6
+ require 'tty-table'
7
+
8
+ module Gloo
9
+ module WebSvr
10
+ module Routing
11
+ class Router
12
+
13
+ PAGE_CONTAINER = 'page'.freeze
14
+ SEGMENT_DIVIDER = '/'.freeze
15
+
16
+ attr_reader :route_segments
17
+
18
+
19
+ # ---------------------------------------------------------------------
20
+ # Initialization
21
+ # ---------------------------------------------------------------------
22
+
23
+ #
24
+ # Set up the web server.
25
+ #
26
+ def initialize( engine, web_svr_obj )
27
+ @engine = engine
28
+ @log = @engine.log
29
+
30
+ @web_svr_obj = web_svr_obj
31
+ @show_routes = ShowRoutes.new( @engine )
32
+ end
33
+
34
+
35
+ # ---------------------------------------------------------------------
36
+ # Routing
37
+ # ---------------------------------------------------------------------
38
+
39
+ #
40
+ # Find and return the page for the given route.
41
+ #
42
+ def page_for_route( path, method )
43
+ @log.info "routing to #{path} for method #{method}"
44
+ @method = method
45
+
46
+ detect_segments path
47
+
48
+ return @web_svr_obj.home_page if is_root_path?
49
+
50
+ pages = @web_svr_obj.pages_container
51
+ if pages
52
+ # If the method is POST and the last segment is NOT 'create',
53
+ # we'll add create to the route segments.
54
+ if Gloo::WebSvr::Routing::ResourceRouter.is_implicit_create?( method, @route_segments.last )
55
+ @route_segments << Gloo::WebSvr::Routing::ResourceRouter::POST_ROUTE
56
+ page = find_route_segment( pages.children )
57
+ return [ page, @id ] if page
58
+
59
+ # We didn't find the page, so remove the last segment and try again
60
+ # posting to the resource.
61
+ @route_segments.pop
62
+ end
63
+
64
+ page = find_route_segment( pages.children )
65
+ return [ page, @id ] if page
66
+ end
67
+
68
+ return nil
69
+ end
70
+
71
+
72
+ # ---------------------------------------------------------------------
73
+ # Dynamic Add Page Routes
74
+ # ---------------------------------------------------------------------
75
+
76
+ #
77
+ # Get the root level page container.
78
+ #
79
+ def page_container
80
+ pn = Gloo::Core::Pn.new( @engine, PAGE_CONTAINER )
81
+ return pn.resolve
82
+ end
83
+
84
+ #
85
+ # Add all page routes to the web server pages (routes).
86
+ #
87
+ def add_page_routes
88
+ can = page_container
89
+ return unless can
90
+
91
+ @log.debug 'Adding page routes to web server…'
92
+ @factory = @engine.factory
93
+
94
+ add_pages can, @web_svr_obj.pages_container
95
+ end
96
+
97
+ #
98
+ # Add the pages to the web server pages.
99
+ # This is a recursive function that will add all
100
+ # pages in the folder and subfolders.
101
+ #
102
+ def add_pages can, parent
103
+ # for each file in the page container
104
+ # create a page object and add it to the routes
105
+ can.children.each do |obj|
106
+ if obj.class == Gloo::Objs::Container
107
+ child_can = parent.find_add_child( obj.name, 'container' )
108
+ add_pages( obj, child_can )
109
+ elsif obj.class == Gloo::Objs::Page
110
+ add_route_alias( parent, obj.name, obj.pn )
111
+ end
112
+ end
113
+ end
114
+
115
+ #
116
+ # Add route alias to the page.
117
+ #
118
+ def add_route_alias( parent, name, pn )
119
+ name = name.gsub( '.', '_' )
120
+
121
+ # First make sure the child doesn't already exist.
122
+ child = parent.find_child( name )
123
+ return if child
124
+
125
+ @factory.create_alias( name, pn, parent )
126
+ end
127
+
128
+
129
+ # ---------------------------------------------------------------------
130
+ # Helper funcions
131
+ # ---------------------------------------------------------------------
132
+
133
+ #
134
+ # Show the routes in the running app.
135
+ # This uses the ShowRoutes helper class.
136
+ #
137
+ def show_routes
138
+ @show_routes.show page_container
139
+ end
140
+
141
+ #
142
+ # Find the route segment in the object container.
143
+ #
144
+ def find_route_segment objs
145
+ this_segment = next_segment
146
+
147
+ if this_segment.blank? # && Gloo::WebSvr::WebMethod.is_post?( @method )
148
+ this_segment = Gloo::WebSvr::Routing::ResourceRouter::INDEX
149
+ end
150
+
151
+ objs.each do |o|
152
+ o = Gloo::Objs::Alias.resolve_alias( @engine, o )
153
+
154
+ if o.name == this_segment
155
+ if o.class == Gloo::Objs::Page
156
+ @log.debug "found page for route: #{o.pn}"
157
+ return o
158
+ elsif o.class == Gloo::Objs::FileHandle
159
+ @log.debug "found static file for route: #{o.pn}"
160
+ return o
161
+ else
162
+ return nil unless o.child_count > 0
163
+
164
+ return find_route_segment( o.children )
165
+ end
166
+ end
167
+ end
168
+
169
+ return nil
170
+ end
171
+
172
+ #
173
+ # Get the next segment in the route.
174
+ #
175
+ def next_segment
176
+ this_segment = @route_segments.shift
177
+ return nil if this_segment.nil?
178
+
179
+ # A URL might include a dot in a name, but we can't do that
180
+ # because dot is a reserve path thing. So we replace it with
181
+ # an underscore.
182
+ this_segment = this_segment.gsub( '.', '_' )
183
+
184
+ return this_segment
185
+ end
186
+
187
+ #
188
+ # Is this the root path?
189
+ def is_root_path?
190
+ return @route_segments.count == 0
191
+ end
192
+
193
+ #
194
+ # Create a list of path segments.
195
+ #
196
+ def detect_segments path
197
+ # Split the path into segments.
198
+ @route_segments = path.split SEGMENT_DIVIDER
199
+
200
+ # Remove the first segment if it is empty.
201
+ @route_segments.shift if @route_segments.first.blank?
202
+
203
+ @route_segments.each do |seg|
204
+ if seg.to_i.to_s == seg
205
+ @id = seg.to_i
206
+ @log.info "found id for route: #{@id}"
207
+ @route_segments.delete seg
208
+ @route_segments << ResourceRouter.segment_for_method( @method )
209
+ end
210
+ end
211
+
212
+ return @route_segments
213
+ end
214
+
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,98 @@
1
+ # Author:: Eric Crane (mailto:eric.crane@mac.com)
2
+ # Copyright:: Copyright (c) 20124 Eric Crane. All rights reserved.
3
+ #
4
+ # A helper class for to show routes for a running app.
5
+ #
6
+ require 'tty-table'
7
+
8
+ module Gloo
9
+ module WebSvr
10
+ module Routing
11
+ class ShowRoutes
12
+
13
+ SEGMENT_DIVIDER = '/'.freeze
14
+ RETURN = "\n".freeze
15
+
16
+
17
+ # ---------------------------------------------------------------------
18
+ # Initialization
19
+ # ---------------------------------------------------------------------
20
+
21
+ #
22
+ # Set up the web server.
23
+ #
24
+ def initialize( engine )
25
+ @engine = engine
26
+ @log = @engine.log
27
+ end
28
+
29
+
30
+ # ---------------------------------------------------------------------
31
+ # Show Routes
32
+ # ---------------------------------------------------------------------
33
+
34
+ #
35
+ # Show all available routes.
36
+ #
37
+ def show page_container
38
+ @log.debug "showing routes"
39
+
40
+ @found_routes = []
41
+ @log.debug "building route table"
42
+ add_container_routes( page_container, SEGMENT_DIVIDER )
43
+
44
+ show_table
45
+ end
46
+
47
+ #
48
+ # Show the routes in the given container.
49
+ # This is a recursive function travese the object tree.
50
+ #
51
+ def add_container_routes can, route_path
52
+ can.children.each do |obj|
53
+ if obj.class == Gloo::Objs::Container
54
+ add_container_routes obj, "#{route_path}#{obj.name}#{SEGMENT_DIVIDER}"
55
+ elsif obj.class == Gloo::Objs::Page
56
+ route = "#{route_path}#{obj.name}"
57
+ @found_routes << [ obj.name, obj.pn, route, Gloo::WebSvr::WebMethod::GET ]
58
+
59
+ # If the method is POST, add a route alias for the create.
60
+ if obj.name.eql? ResourceRouter::POST_ROUTE
61
+ @found_routes << [ '', '', route_path, Gloo::WebSvr::WebMethod::POST ]
62
+ elsif obj.name.eql? ResourceRouter::UPDATE
63
+ @found_routes << [ '', '', route_path, Gloo::WebSvr::WebMethod::PATCH ]
64
+ elsif obj.name.eql? ResourceRouter::DELETE
65
+ @found_routes << [ '', '', route_path, Gloo::WebSvr::WebMethod::DELETE ]
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+
72
+ # ---------------------------------------------------------------------
73
+ # Show Routes
74
+ # ---------------------------------------------------------------------
75
+
76
+ #
77
+ # Show the Routes title.
78
+ #
79
+ def show_table
80
+ @log.show "\n\tRoutes in Running Web App\n", :white
81
+
82
+ table = TTY::Table.new( headers, @found_routes )
83
+ renderer = TTY::Table::Renderer::Unicode.new( table, padding: [0,1] )
84
+ puts renderer.render
85
+ puts RETURN
86
+ end
87
+
88
+ #
89
+ # Get the table headers.
90
+ #
91
+ def headers
92
+ return [ 'Obj Name', 'Obj Path', 'Route', 'Method' ]
93
+ end
94
+
95
+ end
96
+ end
97
+ end
98
+ end
@@ -34,8 +34,9 @@ module Gloo
34
34
  #
35
35
  # Set up the web server.
36
36
  #
37
- def initialize( engine, handler, config = nil )
37
+ def initialize( engine, handler, config = nil, ssl_config = nil )
38
38
  @config = config ? config : Gloo::WebSvr::Config.new
39
+ @ssl_config = ssl_config
39
40
  @engine = engine
40
41
  @log = @engine.log
41
42
  @handler = handler
@@ -57,7 +58,14 @@ module Gloo
57
58
  :Host => @config.host
58
59
  }
59
60
  Thread.abort_on_exception = true
60
- @server_thread = Thread.new { Rack::Handler::Thin.run( self, **options=opts ) }
61
+ @server_thread = Thread.new {
62
+ Rack::Handler::Thin.run( self, **options=opts ) do |server|
63
+ if @ssl_config
64
+ server.ssl = true
65
+ server.ssl_options = @ssl_config
66
+ end
67
+ end
68
+ }
61
69
  @log.debug 'Web server has started.'
62
70
  end
63
71
 
@@ -0,0 +1,147 @@
1
+ # Author:: Eric Crane (mailto:eric.crane@mac.com)
2
+ # Copyright:: Copyright (c) 20124 Eric Crane. All rights reserved.
3
+ #
4
+ # A helper class used to render HTML tables.
5
+ #
6
+
7
+ module Gloo
8
+ module WebSvr
9
+ class TableRenderer
10
+
11
+ TABLE = 'table'.freeze
12
+ THEAD = 'thead'.freeze
13
+ HEAD_CELL = 'head_cell'.freeze
14
+ ROW = 'row'.freeze
15
+ CELL = 'cell'.freeze
16
+
17
+ # ---------------------------------------------------------------------
18
+ # Initialization
19
+ # ---------------------------------------------------------------------
20
+
21
+ #
22
+ # Set up the web server.
23
+ #
24
+ def initialize( engine )
25
+ @engine = engine
26
+ @log = @engine.log
27
+ end
28
+
29
+
30
+ # ---------------------------------------------------------------------
31
+ # Container Renderer
32
+ # ---------------------------------------------------------------------
33
+
34
+ #
35
+ # Render the query result set to an HTML table.
36
+ #
37
+ # params = {
38
+ # head: head,
39
+ # cols: result[0],
40
+ # rows: rows,
41
+ # styles: self.styles,
42
+ # cell_renderers: self.cell_renderers
43
+ # }
44
+ #
45
+ def data_to_table params
46
+ data = params[ :rows ]
47
+
48
+ if data.nil? || ( data.length == 0 )
49
+ return "<p>No data found.</p>"
50
+ elsif data.length == 1
51
+ return data_to_single_row_table( params )
52
+ else
53
+ return data_to_table_rows( params )
54
+ end
55
+ end
56
+
57
+ #
58
+ # Show in single-row (form) format.
59
+ #
60
+ def data_to_single_row_table( params )
61
+ styles = params[ :styles ]
62
+ str = "<table class='#{styles[ TABLE ]}'> <tbody>"
63
+ row = params[ :rows ].first
64
+
65
+ params[ :columns ].each do |head|
66
+ next unless head[ :visible ]
67
+ cell = row[ head[ :data_index ] ]
68
+
69
+ if head[ :cell_renderer ]
70
+ cell_value = render_cell( row, head, params[ :columns ] )
71
+ else
72
+ cell_value = cell
73
+ end
74
+
75
+ str += "<tr class='#{styles[ ROW ]}'>"
76
+ str += "<th style='#{styles[ HEAD_CELL ]}'>#{head[ :title ]}</th>"
77
+ str += "<td style='#{styles[ CELL ]}'>#{cell_value}</td>"
78
+ str += "</tr>"
79
+ end
80
+
81
+ str += "</tbody></table>"
82
+ return str
83
+ end
84
+
85
+ #
86
+ # Show in normal, multi-row format.
87
+ #
88
+ def data_to_table_rows( params )
89
+ styles = params[ :styles ]
90
+ # headers = params[ :head ]
91
+
92
+ str = "<table class='#{styles[ TABLE ]}'>"
93
+ str << "<thead class='#{styles[ THEAD ]}'><tr>"
94
+
95
+ params[ :columns ].each do |head|
96
+ next unless head[ :visible ]
97
+ str += "<th class='#{styles[ HEAD_CELL ]}'>#{head[ :title ]}</th>"
98
+ end
99
+ str << "</tr></thead><tbody>"
100
+
101
+ params[ :rows ].each do |row|
102
+ str += "<tr class='#{styles[ ROW ]}'>"
103
+
104
+ # row.each_with_index do |cell, i|
105
+ params[ :columns ].each do |head|
106
+ next unless head[ :visible ]
107
+
108
+ cell = row[ head[ :data_index ] ]
109
+ this_col_name = head[ :name ]
110
+
111
+ if head[ :cell_renderer ]
112
+ cell_value = render_cell( row, head, params[ :columns ] )
113
+ else
114
+ cell_value = cell
115
+ end
116
+ str += "<td style='#{styles[ CELL ]}'>#{cell_value}</td>"
117
+ end
118
+ str += "</tr>"
119
+ end
120
+ str += "</tbody></table>"
121
+
122
+ return str
123
+ end
124
+
125
+ #
126
+ # Render a cell using the cell renderer and the given
127
+ # context data (the row's values).
128
+ #
129
+ def render_cell row, col, cols
130
+ params = {}
131
+
132
+ cols.each_with_index do |c, i|
133
+ params[ c[ :name ] ] = row[ c[ :data_index ] ]
134
+ end
135
+
136
+ content = col[ :cell_renderer ]
137
+ content = @engine.running_app.obj.embedded_renderer.render content, params
138
+
139
+ # renderer = ERB.new( col[ :cell_renderer ] )
140
+ # content = renderer.result_with_hash( params )
141
+
142
+ return content
143
+ end
144
+ end
145
+
146
+ end
147
+ end
@@ -0,0 +1,54 @@
1
+ # Author:: Eric Crane (mailto:eric.crane@mac.com)
2
+ # Copyright:: Copyright (c) 2024 Eric Crane. All rights reserved.
3
+ #
4
+ # A helper class for Web Methods.
5
+ #
6
+
7
+ module Gloo
8
+ module WebSvr
9
+ class WebMethod
10
+
11
+ GET = 'GET'.freeze
12
+ POST = 'POST'.freeze
13
+ PUT = 'PUT'.freeze
14
+ DELETE = 'DELETE'.freeze
15
+ PATCH = 'PATCH'.freeze
16
+
17
+ #
18
+ # Is the method a GET?
19
+ #
20
+ def self.is_get?( method )
21
+ return method.upcase == GET
22
+ end
23
+
24
+ #
25
+ # Is the method a POST?
26
+ #
27
+ def self.is_post?( method )
28
+ return method.upcase == POST
29
+ end
30
+
31
+ #
32
+ # Is the method a PUT?
33
+ #
34
+ def self.is_put?( method )
35
+ return method.upcase == PUT
36
+ end
37
+
38
+ #
39
+ # Is the method a PATCH?
40
+ #
41
+ def self.is_patch?( method )
42
+ return method.upcase == PATCH
43
+ end
44
+
45
+ #
46
+ # Is the method a DELETE?
47
+ #
48
+ def self.is_delete?( method )
49
+ return method.upcase == DELETE
50
+ end
51
+
52
+ end
53
+ end
54
+ end