gloo-web 1.5 → 1.6

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f65c05593ecaa1245da14193d91d7302b3c831dc211e4e2ff3803d4c923159c7
4
- data.tar.gz: 85d932c0ceca6e2108076a5e243ea4b927e2e605a36f90444d81646c023212d4
3
+ metadata.gz: 76804bbf747238b84d964890005f32e2a6d801b3926169120da712ff542d52b1
4
+ data.tar.gz: 907cac263a2583f2c8fa6d1a9b59a5aa2f4542c4e5bad0a7f9a025fb2c153029
5
5
  SHA512:
6
- metadata.gz: 48c826d58586853dee37b4a616748847f7111bb5f4fe4afc90d2ca56f052dd4d3c45b76ec10e5ced598b6051bd2ab6770da49fecd0da569df9d832a693a76296
7
- data.tar.gz: 44df546269144ffcaac4efe96404ab061ecf4f2dfd49fefb4c3dd2add2904702dfa51e5fe69ad40a5c0051365fd4d9c95a5a8c185b49deb1dca925e98b773aee
6
+ metadata.gz: ef2ae150df15c224c1106a5ef35a0f5ad38b498f5791f36add563a90655b2428ca0ba5ee4710938821287a887a2baa663fed53a84e53aaa8a8085c179d489930
7
+ data.tar.gz: d5e7582e24bf8b1a0418dbe9c9c20cf02a73acebbf61bd70215f971a0353faf8bf53a2017f368f125f2df19b768b4231ff4cc8e572c0b0cc96ca5f17b3e0878a
data/lib/objs/element.rb CHANGED
@@ -169,7 +169,7 @@ module Objs
169
169
  end
170
170
 
171
171
  #
172
- # Get the expiration date for the certificate.
172
+ # Render the element as HTML and put it in it.
173
173
  #
174
174
  def msg_render
175
175
  content = self.render_html
@@ -239,11 +239,15 @@ module Objs
239
239
  return rendered_obj_content
240
240
  end
241
241
 
242
+ #
243
+ # Render a string or other object; an unexpected failure is
244
+ # handled and renders as blank.
245
+ #
242
246
  def self.render_thing e, render_ƒ, engine
243
247
  begin
244
248
  return e.render( render_ƒ )
245
249
  rescue => e
246
- engine.log_exception e
250
+ engine.handle_exception e
247
251
  return ''
248
252
  end
249
253
  end
@@ -265,7 +269,7 @@ module Objs
265
269
  'content (container) — The element contents.'
266
270
  ],
267
271
  :messages => [
268
- 'render — Manually render the HTML element. Normally the render is called by the web server when the page containing the element is requested.'
272
+ 'render — Manually render the HTML element; the HTML is put in it. Normally the render is called by the web server when the page containing the element is requested.'
269
273
  ],
270
274
  :examples => <<~EXAMPLES.strip
271
275
  div_sub [e] :
data/lib/objs/field.rb CHANGED
@@ -453,7 +453,7 @@ module Objs
453
453
  'checked (boolean) — Optional; only used for checkbox fields. Whether the checkbox field is checked.'
454
454
  ],
455
455
  :messages => [
456
- 'render — Manually render the HTML field. Normally the render is called by the web server when the page containing the element is requested.'
456
+ 'render — Manually render the HTML field; the HTML is put in it. Normally the render is called by the web server when the page containing the element is requested.'
457
457
  ],
458
458
  :examples => <<~EXAMPLES.strip
459
459
  f [form] :
data/lib/objs/form.rb CHANGED
@@ -260,7 +260,7 @@ module Objs
260
260
  begin
261
261
  return e.render( 'render_html' )
262
262
  rescue => e
263
- @engine.log_exception e
263
+ @engine.handle_exception e
264
264
  return ''
265
265
  end
266
266
  end
@@ -286,7 +286,7 @@ module Objs
286
286
  'content (container) — The collection of form fields. Other elements can also be included here, for formatting or extra buttons.'
287
287
  ],
288
288
  :messages => [
289
- 'render — Manually render the HTML form. Normally the render is called by the web server when the page containing the element is requested.'
289
+ 'render — Manually render the HTML form; the HTML is put in it. Normally the render is called by the web server when the page containing the element is requested.'
290
290
  ],
291
291
  :examples => <<~EXAMPLES.strip
292
292
  page [can] :
data/lib/objs/page.rb CHANGED
@@ -314,14 +314,30 @@ module Objs
314
314
  end
315
315
 
316
316
  #
317
- # Get the expiration date for the certificate.
317
+ # Render the page and put the content (HTML, JSON or text) in it,
318
+ # or false if it can't render. Only works inside a running web app.
318
319
  #
319
320
  def msg_render
320
- content = self.render
321
- @engine.heap.it.set_to content
321
+ return nil unless Page.can_render?( @engine, self )
322
+
323
+ response = self.render
324
+ content = response&.data
325
+ @engine.heap.it.set_to( content.nil? ? false : content )
322
326
  return content
323
327
  end
324
328
 
329
+ #
330
+ # Is a web app running, so the object can render? If not, report
331
+ # it and put false in it. Shared with partial.
332
+ #
333
+ def self.can_render?( engine, obj )
334
+ return true if engine.app_running?
335
+
336
+ engine.err "#{obj.type_display} '#{obj.name}' can only render inside a web app (gloo-web)."
337
+ engine.heap.it.set_to false
338
+ return false
339
+ end
340
+
325
341
 
326
342
  # ---------------------------------------------------------------------
327
343
  # Render
@@ -584,7 +600,7 @@ module Objs
584
600
  'download_file (boolean) — Optional, default false. If true, the browser is instructed to download the file instead of rendering it.'
585
601
  ],
586
602
  :messages => [
587
- 'render — Manually render the content of the page. Normally the render is called by the web server when the page is requested.'
603
+ 'render — Manually render the content of the page; the content (HTML, JSON or text) is put in it, or false if it can\'t render. Only works inside a running web app (it is an error otherwise). Normally the render is called by the web server when the page is requested.'
588
604
  ],
589
605
  :examples => <<~EXAMPLES.strip
590
606
  page [can] :
data/lib/objs/partial.rb CHANGED
@@ -140,11 +140,14 @@ module Objs
140
140
  end
141
141
 
142
142
  #
143
- # Get the expiration date for the certificate.
143
+ # Render the partial and put the HTML in it, or false if it can't
144
+ # render. Only works inside a running web app.
144
145
  #
145
146
  def msg_render
147
+ return nil unless Page.can_render?( @engine, self )
148
+
146
149
  part_content = self.render
147
- @engine.heap.it.set_to part_content
150
+ @engine.heap.it.set_to( part_content.nil? ? false : part_content )
148
151
  return part_content
149
152
  end
150
153
 
@@ -226,7 +229,7 @@ module Objs
226
229
  'content (element) — The contents of the partial page.'
227
230
  ],
228
231
  :messages => [
229
- 'render — Manually render the content of the partial page. Normally the render is called by the web server when the page containing the partial is requested.'
232
+ 'render — Manually render the content of the partial page; the HTML is put in it, or false if it can\'t render. Only works inside a running web app (it is an error otherwise). Normally the render is called by the web server when the page containing the partial is requested.'
230
233
  ],
231
234
  :examples => <<~EXAMPLES.strip
232
235
  shared [can] :
data/lib/objs/svr.rb CHANGED
@@ -58,9 +58,10 @@ module Objs
58
58
  # Default layout for pages.
59
59
  LAYOUT = 'layout'.freeze
60
60
 
61
- # Alias to the home and error pages
61
+ # Alias to the home, error and not found pages
62
62
  HOME = 'home'.freeze
63
63
  ERR_PAGE = 'error'.freeze
64
+ NOT_FOUND_PAGE = 'not_found'.freeze
64
65
 
65
66
  # Session
66
67
  SESSION = 'session'.freeze
@@ -68,6 +69,7 @@ module Objs
68
69
 
69
70
  # Messages
70
71
  SERVER_NOT_RUNNING = 'The web server is not running!'.freeze
72
+ SERVER_ALREADY_RUNNING = 'The web server is already running!'.freeze
71
73
 
72
74
  #
73
75
  # Should the current request be redirected?
@@ -123,13 +125,20 @@ module Objs
123
125
  # Configuration
124
126
  # ---------------------------------------------------------------------
125
127
 
128
+ #
129
+ # Get the config child with the given name.
130
+ # Returns nil if there is none, or no config container.
131
+ #
132
+ def config_child( name )
133
+ return find_child( CONFIG )&.find_child( name )
134
+ end
135
+
126
136
  #
127
137
  # Get the Scheme (http or https) from the child object.
128
138
  # Returns nil if there is none.
129
139
  #
130
140
  def scheme_value
131
- config = find_child CONFIG
132
- scheme = config.find_child SCHEME
141
+ scheme = config_child SCHEME
133
142
  return nil unless scheme
134
143
 
135
144
  return scheme.value
@@ -140,8 +149,7 @@ module Objs
140
149
  # Returns nil if there is none.
141
150
  #
142
151
  def host_value
143
- config = find_child CONFIG
144
- host = config.find_child HOST
152
+ host = config_child HOST
145
153
  return nil unless host
146
154
 
147
155
  return host.value
@@ -152,8 +160,7 @@ module Objs
152
160
  # Returns nil if there is none.
153
161
  #
154
162
  def port_value
155
- config = find_child CONFIG
156
- port = config.find_child PORT
163
+ port = config_child PORT
157
164
  return nil unless port
158
165
 
159
166
  return port.value
@@ -171,8 +178,7 @@ module Objs
171
178
  # Get the session cookie name.
172
179
  #
173
180
  def session_name
174
- config = find_child CONFIG
175
- session_name = config.find_child SESSION_NAME
181
+ session_name = config_child SESSION_NAME
176
182
  return nil unless session_name
177
183
 
178
184
  name = session_name.value
@@ -185,8 +191,7 @@ module Objs
185
191
  # Get the key for the encryption cipher.
186
192
  #
187
193
  def encryption_key
188
- config = find_child CONFIG
189
- o = config.find_child ENCRYPT_KEY
194
+ o = config_child ENCRYPT_KEY
190
195
  return nil unless o
191
196
 
192
197
  o = Gloo::Objs::Alias.resolve_alias( @engine, o )
@@ -197,8 +202,7 @@ module Objs
197
202
  # Get the initialization vector for the cipher.
198
203
  #
199
204
  def encryption_iv
200
- config = find_child CONFIG
201
- o = config.find_child ENCRYPT_IV
205
+ o = config_child ENCRYPT_IV
202
206
  return nil unless o
203
207
 
204
208
  o = Gloo::Objs::Alias.resolve_alias( @engine, o )
@@ -210,8 +214,7 @@ module Objs
210
214
  # If not specified, use the root path.
211
215
  #
212
216
  def session_cookie_path
213
- config = find_child CONFIG
214
- o = config.find_child COOKIE_PATH
217
+ o = config_child COOKIE_PATH
215
218
  if o
216
219
  return o.value
217
220
  else
@@ -224,8 +227,7 @@ module Objs
224
227
  # If not specified, use one week from now.
225
228
  #
226
229
  def session_cookie_expires
227
- config = find_child CONFIG
228
- o = config.find_child COOKIE_EXPIRES
230
+ o = config_child COOKIE_EXPIRES
229
231
  if o
230
232
  dt = Chronic.parse( o.value )
231
233
  return dt
@@ -239,7 +241,7 @@ module Objs
239
241
  # Get the value from the scheme settings/config.
240
242
  #
241
243
  def session_cookie_secure
242
- return scheme_value.downcase == HTTPS
244
+ return scheme_value&.downcase == HTTPS
243
245
  end
244
246
 
245
247
 
@@ -392,6 +394,7 @@ module Objs
392
394
  fac.create_alias LAYOUT, nil, self
393
395
  fac.create_alias HOME, nil, self
394
396
  fac.create_alias ERR_PAGE, nil, self
397
+ fac.create_alias NOT_FOUND_PAGE, nil, self
395
398
 
396
399
  fac.create_can PAGES, self
397
400
  end
@@ -415,6 +418,11 @@ module Objs
415
418
  # Start the gloo web server.
416
419
  #
417
420
  def msg_start
421
+ if @engine.app_running?
422
+ @engine.err SERVER_ALREADY_RUNNING
423
+ return
424
+ end
425
+
418
426
  @engine.log.debug "Starting web server…"
419
427
  # @engine.log.quiet = true
420
428
 
@@ -522,12 +530,14 @@ module Objs
522
530
  # Start running the web server.
523
531
  #
524
532
  def start
525
- config = WebSvr::Config.new( scheme_value, host_value, port_value )
533
+ # Settings that aren't given use the defaults.
534
+ config = WebSvr::Config.new( scheme_value || WebSvr::Config::HTTP,
535
+ host_value || WebSvr::Config::LOCALHOST,
536
+ port_value || WebSvr::Config::PORT_DEFAULT )
526
537
  @engine.log.info "Web Server URL: #{config.base_url}"
527
538
 
528
539
  handler = WebSvr::Handler.new( @engine, self )
529
540
  @web_server = WebSvr::Server.new( @engine, handler, config, ssl_config )
530
- @web_server.start
531
541
 
532
542
  @router = Routing::Router.new( @engine, self )
533
543
  @router.add_page_routes
@@ -540,6 +550,10 @@ module Objs
540
550
  @session = WebSvr::Session.new( @engine, self )
541
551
 
542
552
  run_on_start
553
+
554
+ # Listen last: if the server can't start, it stops the running
555
+ # app from its own thread, so everything else must be done.
556
+ @web_server.start
543
557
  @engine.log.info "Web server started and listening…"
544
558
  end
545
559
 
@@ -658,7 +672,7 @@ module Objs
658
672
  data.find_child( PAGE )&.set_value( page_obj.pn )
659
673
  end
660
674
  rescue => e
661
- @engine.log_exception e
675
+ @engine.handle_exception e
662
676
  end
663
677
  end
664
678
 
@@ -696,6 +710,17 @@ module Objs
696
710
  return o
697
711
  end
698
712
 
713
+ #
714
+ # Get the application not found page.
715
+ #
716
+ def not_found_page
717
+ o = find_child NOT_FOUND_PAGE
718
+ return nil unless o
719
+
720
+ o = Gloo::Objs::Alias.resolve_alias( @engine, o )
721
+ return o
722
+ end
723
+
699
724
  #
700
725
  # Get the default layout for pages.
701
726
  #
@@ -720,12 +745,13 @@ module Objs
720
745
  :shortcut => KEYWORD_SHORT,
721
746
  :description => 'A web server running inside gloo.',
722
747
  :children => [
723
- 'config (container) — Configuration and settings for the server. See below for its children.',
748
+ 'config (container) — Configuration and settings for the server. See below for its children. Settings left out use the defaults.',
724
749
  'on_start (script) — Run when the web server is started.',
725
750
  'on_stop (script) — Run when the web server is stopped.',
726
751
  "layout (partial) — By convention an alias pointing to the layout used for all pages (layouts live in the layout root-level folder).",
727
752
  'home (page) — By convention an alias pointing to the home page object (pages live in the page root-level folder).',
728
- 'error (page) — By convention an alias pointing to the default error page object.',
753
+ 'error (page) — By convention an alias pointing to the default error page object. Sent with a 500 when a request fails; without one, a plain "Server error!" is sent.',
754
+ 'not_found (page) — By convention an alias pointing to the page sent with a 404 when no route or file matches the request; without one, a plain "Not found" is sent.',
729
755
  'pages (container) — Routes. By convention, aliases pointing to pages in the page root-level folder.',
730
756
  'config.scheme (string) — \'http\' or \'https\'.',
731
757
  "config.host (string) — Default: 'localhost'.",
@@ -735,16 +761,16 @@ module Objs
735
761
  'config.encryption_iv (string) — Optional. Initialization vector for the session cookie encryption key.',
736
762
  "config.cookie_expires (string) — Optional, default 'in 1 week'. When the session expires.",
737
763
  "config.cookie_path (string) — Optional, default '/'. The path for the session cookie.",
738
- 'config.ssl_cert (string) — Optional, required for SSL. Path to certificate.pem.',
739
- 'config.ssl_key (string) — Optional, required for SSL. Path to key.pem.',
764
+ 'ssl_cert (string or file) — Optional, required for SSL; a child of the server, not of config. Path to certificate.pem. May be an alias.',
765
+ 'ssl_key (string or file) — Optional, required for SSL; a child of the server, not of config. Path to key.pem. May be an alias.',
740
766
  'on_request (script) — Optional. Run when the web server receives a request.',
741
767
  'request_data (container) — Optional. Data available for use in on_request or elsewhere in the page rendering life-cycle: method, host, path, query, ip (all string, all optional, populated only if present).',
742
768
  'on_response (script) — Optional. Run when the web server is done rendering and about to return a response.',
743
769
  'response_data (container) — Optional. Data available for use in on_response (request_data is also available at this point): page, type, code, elapsed, db (all string, all optional, populated only if present).'
744
770
  ],
745
771
  :messages => [
746
- 'start — Start the web server.',
747
- 'stop — Stop the web server.',
772
+ 'start — Start the web server. It is an error if a web server is already running. If it can\'t listen on its port (in use, or needs root privileges), that is reported as an error and the server stops.',
773
+ 'stop — Stop the web server. It is an error if it is not running.',
748
774
  'list_routes — Show the routing table. A debugging tool.',
749
775
  'list_assets — Show the list of assets. A debugging tool.',
750
776
  'list_asset_img — Show the list of image assets. A debugging tool.',
@@ -773,6 +799,7 @@ module Objs
773
799
  layout [alias] : layout.primary
774
800
  home [alias] : page.home
775
801
  error [alias] : page.err
802
+ not_found [alias] : page.not_found
776
803
 
777
804
  # Routes for the web server.
778
805
  pages [container] :
@@ -98,7 +98,7 @@ module WebSvr
98
98
  #
99
99
  # Handle a missing method by looking for a helper function.
100
100
  # If there is one, then call it and return the result.
101
- # If not, log an error and return nil.
101
+ # If not, report an error and return nil (it renders as blank).
102
102
  #
103
103
  def method_missing( method_name, *args )
104
104
  @log.debug "missing method '#{method_name}' with args #{args}"
@@ -108,11 +108,13 @@ module WebSvr
108
108
 
109
109
  pn = Gloo::Core::Pn.new( @engine, helper_pn )
110
110
  obj = pn.resolve
111
- if obj
111
+ if obj.nil?
112
+ @engine.err "Helper function '#{method_name}' was not found; add it as #{helper_pn}."
113
+ elsif !obj.respond_to?( :invoke )
114
+ @engine.err "'#{helper_pn}' is not a function."
115
+ else
112
116
  @log.debug "found obj: #{obj.pn}"
113
117
  return obj.invoke args
114
- else
115
- @log.error "Function not found: #{helper_pn}"
116
118
  end
117
119
 
118
120
  return nil
@@ -7,7 +7,10 @@
7
7
 
8
8
  module WebSvr
9
9
  class Handler
10
-
10
+
11
+ SERVER_ERR_MSG = 'Server error!'.freeze
12
+ NOT_FOUND_MSG = 'Not found'.freeze
13
+
11
14
  attr_reader :server_obj
12
15
 
13
16
 
@@ -55,7 +58,7 @@ module WebSvr
55
58
  page_obj = page
56
59
  end
57
60
  else
58
- result = server_error_result
61
+ result = not_found_result
59
62
  end
60
63
 
61
64
  return result, page_obj
@@ -87,7 +90,7 @@ module WebSvr
87
90
 
88
91
  # Check to make sure it is a valid file
89
92
  # return error if it is not
90
- return file_error_result unless File.exist? pn
93
+ return not_found_result unless File.exist? pn
91
94
 
92
95
  return @server_obj.asset.render_file pn
93
96
  end
@@ -98,25 +101,37 @@ module WebSvr
98
101
  # ---------------------------------------------------------------------
99
102
 
100
103
  #
101
- # Return a server error result.
102
- # Use the app's error if there is one, otherwise a generic message.
104
+ # Return a server error result: the app's error page, or a generic
105
+ # message, sent as a 500.
103
106
  #
104
107
  def server_error_result
105
- err_page = @server_obj.err_page
106
- return err_page.render if err_page
108
+ return page_or_text_result( @server_obj.err_page,
109
+ SERVER_ERR_MSG, WebSvr::ResponseCode::SERVER_ERR )
110
+ end
107
111
 
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
112
  #
114
- # Get a file not found error result.
113
+ # Return a not found result: the app's not found page, or a generic
114
+ # message, sent as a 404.
115
115
  #
116
- def file_error_result
117
- return WebSvr::Response.text_response( @engine,
118
- "File not found!", WebSvr::ResponseCode::NOT_FOUND )
119
- end
116
+ def not_found_result
117
+ return page_or_text_result( @server_obj.not_found_page,
118
+ NOT_FOUND_MSG, WebSvr::ResponseCode::NOT_FOUND )
119
+ end
120
+
121
+ #
122
+ # Render the given page with the given status code, whatever the
123
+ # page's own return code. Without a page, or if it doesn't render,
124
+ # send the message as text.
125
+ #
126
+ def page_or_text_result( page, msg, code )
127
+ result = page.render if page.is_a?( Objs::Page )
128
+ if result
129
+ result.code = code
130
+ return result
131
+ end
132
+
133
+ return WebSvr::Response.text_response( @engine, msg, code )
134
+ end
120
135
 
121
136
 
122
137
  # ---------------------------------------------------------------------
@@ -53,6 +53,9 @@ module WebSvr
53
53
  begin
54
54
  @body_params = Rack::Utils.parse_query body
55
55
  rescue => exception
56
+ # A binary body (a file upload) isn't a query string and
57
+ # fails to parse; read it as multipart instead. Any problem
58
+ # with that is reported there.
56
59
  init_multipart body
57
60
  end
58
61
  else
@@ -101,8 +104,7 @@ module WebSvr
101
104
  @body_params[ 'file_size' ] = binary_data.length
102
105
  @body_params[ 'file_data' ] = binary_data
103
106
  rescue => ex
104
- @engine.log_exception ex
105
- # @log.error "Error parsing multipart request", ex
107
+ @engine.err "Could not read the uploaded file: #{ex.message}"
106
108
  end
107
109
  end
108
110
 
@@ -16,7 +16,8 @@ module WebSvr
16
16
  JSON_TYPE = 'application/json'.freeze
17
17
  HTML_TYPE = 'text/html'.freeze
18
18
 
19
- attr_reader :code, :type, :data
19
+ attr_reader :type, :data
20
+ attr_accessor :code
20
21
  attr_accessor :location
21
22
  attr_accessor :file_name
22
23
 
@@ -25,7 +25,11 @@ require 'rack'
25
25
 
26
26
  module WebSvr
27
27
  class Server
28
-
28
+
29
+ # EventMachine (under Thin) raises a RuntimeError starting with this
30
+ # when it can't listen: the port is in use or needs root.
31
+ NO_ACCEPTOR = 'no acceptor'.freeze
32
+
29
33
  # ---------------------------------------------------------------------
30
34
  # Initialization
31
35
  # ---------------------------------------------------------------------
@@ -52,20 +56,34 @@ module WebSvr
52
56
  # Start the web server.
53
57
  #
54
58
  def start
59
+ @server_thread = Thread.new { run_server }
60
+ @log.debug 'Web server has started.'
61
+ end
62
+
63
+ #
64
+ # Run the web server. This doesn't return while the server is
65
+ # running, so it runs in its own thread. If the server fails, it is
66
+ # reported here and the running app is stopped.
67
+ #
68
+ def run_server
55
69
  opts = {
56
70
  :Port => @config.port,
57
71
  :Host => @config.host
58
72
  }
59
- Thread.abort_on_exception = true
60
- @server_thread = Thread.new {
61
- Rack::Handler::Thin.run( self, **options=opts ) do |server|
62
- if @ssl_config
63
- server.ssl = true
64
- server.ssl_options = @ssl_config
65
- end
73
+ Rack::Handler::Thin.run( self, **opts ) do |server|
74
+ if @ssl_config
75
+ server.ssl = true
76
+ server.ssl_options = @ssl_config
66
77
  end
67
- }
68
- @log.debug 'Web server has started.'
78
+ end
79
+ rescue => e
80
+ if e.message.start_with?( NO_ACCEPTOR )
81
+ @engine.err "Could not start the web server on #{@config.host}:#{@config.port}: " \
82
+ 'the port is in use or needs root privileges.'
83
+ else
84
+ @engine.handle_exception e
85
+ end
86
+ @engine.stop_running_app
69
87
  end
70
88
 
71
89
  #
@@ -74,7 +92,8 @@ module WebSvr
74
92
  def stop
75
93
  @log.debug 'Stopping the web server…'
76
94
 
77
- @server_thread.kill
95
+ # When the server thread itself failed, it is ending already.
96
+ @server_thread.kill unless Thread.current == @server_thread
78
97
 
79
98
  @log.debug 'The web server has been stopped.'
80
99
  end
@@ -92,9 +111,31 @@ module WebSvr
92
111
  request.log
93
112
 
94
113
  response = request.process
95
- response.log if response
96
114
 
97
- return response ? response.result : nil
115
+ # A page that failed to render gives no response.
116
+ response ||= @handler.server_error_result
117
+ response.log
118
+
119
+ return response.result
120
+ rescue => e
121
+ # A bug in gloo-web's own code. Errors in gloo statements are
122
+ # handled where they happen, so this is the unexpected case:
123
+ # report it and send the error page.
124
+ @engine.handle_exception e
125
+ return error_result
126
+ end
127
+
128
+ #
129
+ # The result for a request that failed: the app's error page, or a
130
+ # plain 500 if that fails too.
131
+ #
132
+ def error_result
133
+ return @handler.server_error_result.result
134
+ rescue => e
135
+ @engine.handle_exception e
136
+ return [ WebSvr::ResponseCode::SERVER_ERR,
137
+ { WebSvr::Response::CONTENT_TYPE => WebSvr::Response::TEXT_TYPE },
138
+ WebSvr::Handler::SERVER_ERR_MSG ]
98
139
  end
99
140
 
100
141
 
@@ -9,12 +9,14 @@
9
9
  # https://en.wikipedia.org/wiki/HTTP_cookie
10
10
  #
11
11
  require 'base64'
12
+ require 'openssl'
12
13
 
13
14
  module WebSvr
14
15
  class Session
15
16
 
16
17
  SESSION_CONTAINER = 'session'.freeze
17
18
  SESSION_ID_NAME = 'session_id'.freeze
19
+ SESSION_UNREADABLE = 'Session cookie could not be read; starting a new session.'.freeze
18
20
 
19
21
 
20
22
  # ---------------------------------------------------------------------
@@ -64,7 +66,8 @@ module WebSvr
64
66
  end
65
67
  end
66
68
  rescue => e
67
- @engine.log_exception e
69
+ # Not expected: a bad cookie is handled in decode_decrypt.
70
+ @engine.handle_exception e
68
71
  end
69
72
  end
70
73
 
@@ -166,8 +169,10 @@ module WebSvr
166
169
  begin
167
170
  data = Gloo::Objs::Cipher.decrypt( data, key, iv )
168
171
  return JSON.parse( data )
169
- rescue => e
170
- @engine.log_exception e
172
+ rescue OpenSSL::Cipher::CipherError, JSON::ParserError
173
+ # The cookie was made with another key, or has been changed.
174
+ # Not the app's error: the visitor gets a new session.
175
+ @engine.warn SESSION_UNREADABLE
171
176
  return nil
172
177
  end
173
178
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gloo-web
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.5'
4
+ version: '1.6'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Crane
@@ -9,6 +9,20 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: gloo
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.0'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: thin
14
28
  requirement: !ruby/object:Gem::Requirement