gloo 3.1.1 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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