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.
data/lib/objs/svr.rb ADDED
@@ -0,0 +1,711 @@
1
+ # Author:: Eric Crane (mailto:eric.crane@mac.com)
2
+ # Copyright:: Copyright (c) 2024 Eric Crane. All rights reserved.
3
+ #
4
+ # A web web server running inside gloo.
5
+ #
6
+
7
+ module Objs
8
+ class Svr < Gloo::Core::Obj
9
+
10
+ KEYWORD = 'server'.freeze
11
+ KEYWORD_SHORT = 'svr'.freeze
12
+
13
+ # ---------------------------------------------------------------------
14
+ # CONFIGURATION KEYS
15
+ # ---------------------------------------------------------------------
16
+ CONFIG = 'config'.freeze
17
+ SCHEME = 'scheme'.freeze
18
+ HTTP = 'http'.freeze
19
+ HTTPS = 'https'.freeze
20
+ HOST = 'host'.freeze
21
+ PORT = 'port'.freeze
22
+ SESSION_NAME = 'session_name'.freeze
23
+ ENCRYPT_KEY = 'encryption_key'.freeze
24
+ ENCRYPT_IV = 'encryption_iv'.freeze
25
+ COOKIE_EXPIRES = 'cookie_expires'.freeze
26
+ COOKIE_PATH = 'cookie_path'.freeze
27
+ DEFAULT_COOKIE_PATH = '/'.freeze
28
+
29
+ # SSL Configuration
30
+ SSL_CERT = 'ssl_cert'.freeze
31
+ SSL_KEY = 'ssl_key'.freeze
32
+
33
+ # ---------------------------------------------------------------------
34
+ # OTHER KEYS
35
+ # ---------------------------------------------------------------------
36
+
37
+ # Events
38
+ ON_START = 'on_start'.freeze
39
+ ON_STOP = 'on_stop'.freeze
40
+ ON_REQUEST = 'on_request'.freeze
41
+ ON_RESPONSE = 'on_response'.freeze
42
+ RESQUEST_DATA = 'request_data'.freeze
43
+ METHOD = 'method'.freeze
44
+ PATH = 'path'.freeze
45
+ QUERY = 'query'.freeze
46
+ IP = 'ip'.freeze
47
+ RESPONSE_DATA = 'response_data'.freeze
48
+ TYPE = 'type'.freeze
49
+ CODE = 'code'.freeze
50
+ ELAPSED = 'elapsed'.freeze
51
+ DB = 'db'.freeze
52
+ PAGE = 'page'.freeze
53
+ CURRENT_PAGE = 'current_page'.freeze
54
+
55
+ # Container with pages in the web app.
56
+ PAGES = 'pages'.freeze
57
+
58
+ # Default layout for pages.
59
+ LAYOUT = 'layout'.freeze
60
+
61
+ # Alias to the home and error pages
62
+ HOME = 'home'.freeze
63
+ ERR_PAGE = 'error'.freeze
64
+
65
+ # Session
66
+ SESSION = 'session'.freeze
67
+
68
+
69
+ # Messages
70
+ SERVER_NOT_RUNNING = 'The web server is not running!'.freeze
71
+
72
+ #
73
+ # Should the current request be redirected?
74
+ # If the redirect is set, then use that page instead
75
+ # of the one requested.
76
+ #
77
+ attr_accessor :redirect, :redirect_hard
78
+ attr_accessor :router, :asset, :embedded_renderer
79
+ attr_accessor :session
80
+
81
+ #
82
+ # The name of the object type.
83
+ #
84
+ def self.typename
85
+ return KEYWORD
86
+ end
87
+
88
+ #
89
+ # The short name of the object type.
90
+ #
91
+ def self.short_typename
92
+ return KEYWORD_SHORT
93
+ end
94
+
95
+ #
96
+ # Set the value with any necessary type conversions.
97
+ #
98
+ def set_value( new_value )
99
+ self.value = new_value.to_s
100
+ end
101
+
102
+ #
103
+ # Does this object support multi-line values?
104
+ # Initially only true for scripts.
105
+ #
106
+ def multiline_value?
107
+ return false
108
+ end
109
+
110
+ #
111
+ # Get the default layout for the app.
112
+ #
113
+ def default_page_layout
114
+ o = find_child LAYOUT
115
+ return nil unless o
116
+
117
+ o = Gloo::Objs::Alias.resolve_alias( @engine, o )
118
+ return o
119
+ end
120
+
121
+
122
+ # ---------------------------------------------------------------------
123
+ # Configuration
124
+ # ---------------------------------------------------------------------
125
+
126
+ #
127
+ # Get the Scheme (http or https) from the child object.
128
+ # Returns nil if there is none.
129
+ #
130
+ def scheme_value
131
+ config = find_child CONFIG
132
+ scheme = config.find_child SCHEME
133
+ return nil unless scheme
134
+
135
+ return scheme.value
136
+ end
137
+
138
+ #
139
+ # Get the host from the child object.
140
+ # Returns nil if there is none.
141
+ #
142
+ def host_value
143
+ config = find_child CONFIG
144
+ host = config.find_child HOST
145
+ return nil unless host
146
+
147
+ return host.value
148
+ end
149
+
150
+ #
151
+ # Get the port from the child object.
152
+ # Returns nil if there is none.
153
+ #
154
+ def port_value
155
+ config = find_child CONFIG
156
+ port = config.find_child PORT
157
+ return nil unless port
158
+
159
+ return port.value
160
+ end
161
+
162
+ #
163
+ # Is this server configured to use a session?
164
+ # It is if theere is a non-empty session name.
165
+ #
166
+ def use_session?
167
+ return ! session_name.blank?
168
+ end
169
+
170
+ #
171
+ # Get the session cookie name.
172
+ #
173
+ def session_name
174
+ config = find_child CONFIG
175
+ session_name = config.find_child SESSION_NAME
176
+ return nil unless session_name
177
+
178
+ name = session_name.value
179
+ return nil if name.blank?
180
+
181
+ return name
182
+ end
183
+
184
+ #
185
+ # Get the key for the encryption cipher.
186
+ #
187
+ def encryption_key
188
+ config = find_child CONFIG
189
+ o = config.find_child ENCRYPT_KEY
190
+ return nil unless o
191
+
192
+ o = Gloo::Objs::Alias.resolve_alias( @engine, o )
193
+ return o.value
194
+ end
195
+
196
+ #
197
+ # Get the initialization vector for the cipher.
198
+ #
199
+ def encryption_iv
200
+ config = find_child CONFIG
201
+ o = config.find_child ENCRYPT_IV
202
+ return nil unless o
203
+
204
+ o = Gloo::Objs::Alias.resolve_alias( @engine, o )
205
+ return o.value
206
+ end
207
+
208
+ #
209
+ # Get the path for the session cookie.
210
+ # If not specified, use the root path.
211
+ #
212
+ def session_cookie_path
213
+ config = find_child CONFIG
214
+ o = config.find_child COOKIE_PATH
215
+ if o
216
+ return o.value
217
+ else
218
+ return DEFAULT_COOKIE_PATH
219
+ end
220
+ end
221
+
222
+ #
223
+ # Get the expiration time for the session cookie.
224
+ # If not specified, use one week from now.
225
+ #
226
+ def session_cookie_expires
227
+ config = find_child CONFIG
228
+ o = config.find_child COOKIE_EXPIRES
229
+ if o
230
+ dt = Chronic.parse( o.value )
231
+ return dt
232
+ else
233
+ return 1.week.from_now
234
+ end
235
+ end
236
+
237
+ #
238
+ # Should the session cookie be secure?
239
+ # Get the value from the scheme settings/config.
240
+ #
241
+ def session_cookie_secure
242
+ return scheme_value.downcase == HTTPS
243
+ end
244
+
245
+
246
+ # ---------------------------------------------------------------------
247
+ # Session
248
+ # ---------------------------------------------------------------------
249
+
250
+ #
251
+ # Get the session container object.
252
+ # If there is none, one will be created.
253
+ #
254
+ def session_container
255
+ o = find_child SESSION
256
+
257
+ unless o
258
+ o = add_session_container
259
+ end
260
+
261
+ return o
262
+ end
263
+
264
+ #
265
+ # Add the session container because it is missing.
266
+ #
267
+ def add_session_container
268
+ fac = @engine.factory
269
+ return fac.create_can SESSION, self
270
+ end
271
+
272
+ #
273
+ # Get the data from the session container.
274
+ # Data will be in the form of a hash ( key => value ).
275
+ #
276
+ def get_session_data
277
+ data = {}
278
+
279
+ session_container.children.each do |session_var|
280
+ key = session_var.name
281
+ value = session_var.value
282
+ data[ key ] = value
283
+ end
284
+
285
+ return data
286
+ end
287
+
288
+ #
289
+ # Get the session child object with the given value.
290
+ # Create the child if it does not exist.
291
+ #
292
+ def set_session_var( key, value )
293
+ child_obj = session_container.find_child( key )
294
+ unless child_obj
295
+ fac = @engine.factory
296
+ child_obj = fac.create_string key, value, session_container
297
+ end
298
+ child_obj.value = value
299
+ end
300
+
301
+ #
302
+ # Clear out all session data.
303
+ # Important to do this after the response is sent
304
+ # to avoid holding on to data that is no longer needed.
305
+ #
306
+ def reset_session_data
307
+ session_container.children.each do |session_var|
308
+ session_var.value = ''
309
+ end
310
+ end
311
+
312
+
313
+ # ---------------------------------------------------------------------
314
+ # SSL
315
+ # ---------------------------------------------------------------------
316
+
317
+ #
318
+ # Is SSL configured for this server?
319
+ # True if the Cert and Key are both present.
320
+ #
321
+ def use_ssl?
322
+ return ssl_cert && ssl_key
323
+ end
324
+
325
+ #
326
+ # Get the SSL certificate from the child object.
327
+ # Returns nil if there is none.
328
+ #
329
+ def ssl_cert
330
+ cert = find_child SSL_CERT
331
+ return nil unless cert
332
+
333
+ cert = Gloo::Objs::Alias.resolve_alias( @engine, cert )
334
+ return cert
335
+ end
336
+
337
+ #
338
+ # Get the SSL key from the child object.
339
+ # Returns nil if there is none.
340
+ #
341
+ def ssl_key
342
+ key = find_child SSL_KEY
343
+ return nil unless key
344
+
345
+ key = Gloo::Objs::Alias.resolve_alias( @engine, key )
346
+ return key
347
+ end
348
+
349
+ #
350
+ # Get the SSL configuration for the server.
351
+ #
352
+ def ssl_config
353
+ return nil unless use_ssl?
354
+
355
+ return {
356
+ :private_key_file => ssl_key.value,
357
+ :cert_chain_file => ssl_cert.value,
358
+ :verify_peer => false,
359
+ }
360
+ end
361
+
362
+ # ---------------------------------------------------------------------
363
+ # Children
364
+ # ---------------------------------------------------------------------
365
+
366
+ #
367
+ # Does this object have children to add when an object
368
+ # is created in interactive mode?
369
+ # This does not apply during obj load, etc.
370
+ #
371
+ def add_children_on_create?
372
+ return true
373
+ end
374
+
375
+ #
376
+ # Add children to this object.
377
+ # This is used by containers to add children needed
378
+ # for default configurations.
379
+ #
380
+ def add_default_children
381
+ fac = @engine.factory
382
+
383
+ # Configuration
384
+ config = fac.create_can CONFIG, self
385
+ fac.create_string SCHEME, HTTP, config
386
+ fac.create_string HOST, 'localhost', config
387
+ fac.create_string PORT, '8080', config
388
+
389
+ fac.create_script ON_START, '', self
390
+ fac.create_script ON_STOP, '', self
391
+
392
+ fac.create_alias LAYOUT, nil, self
393
+ fac.create_alias HOME, nil, self
394
+ fac.create_alias ERR_PAGE, nil, self
395
+
396
+ fac.create_can PAGES, self
397
+ end
398
+
399
+
400
+ # ---------------------------------------------------------------------
401
+ # Messages
402
+ # ---------------------------------------------------------------------
403
+
404
+ #
405
+ # Get a list of message names that this object receives.
406
+ #
407
+ def self.messages
408
+ return super + [ 'start', 'stop',
409
+ 'list_routes', 'list_assets',
410
+ 'add_session_to_response', 'clear_session_data',
411
+ 'list_asset_img', 'list_asset_css', 'list_asset_js' ]
412
+ end
413
+
414
+ #
415
+ # Start the gloo web server.
416
+ #
417
+ def msg_start
418
+ @engine.log.debug "Starting web server…"
419
+ # @engine.log.quiet = true
420
+
421
+ # Set running app to this object.
422
+ @engine.start_running_app( self )
423
+ # The running app will call the start function (below)
424
+ end
425
+
426
+ #
427
+ # Stop the running web server.
428
+ #
429
+ def msg_stop
430
+ if @web_server
431
+ @engine.stop_running_app
432
+ # The running app will call the stop function (below)
433
+ else
434
+ @engine.err SERVER_NOT_RUNNING
435
+ end
436
+ end
437
+
438
+ #
439
+ # Helper message to show all routes in the running server.
440
+ # A Debugging tool.
441
+ #
442
+ def msg_list_routes
443
+ if @router
444
+ @router.show_routes
445
+ else
446
+ @engine.err SERVER_NOT_RUNNING
447
+ end
448
+ end
449
+
450
+ #
451
+ # Helper message to show all assets in the running server.
452
+ # A Debugging tool.
453
+ #
454
+ def msg_list_assets
455
+ if @router
456
+ WebSvr::AssetInfo.list_all( @engine )
457
+ else
458
+ @engine.err SERVER_NOT_RUNNING
459
+ end
460
+ end
461
+
462
+ #
463
+ # List all asset images in the running server.
464
+ # A Debugging tool.
465
+ #
466
+ def msg_list_asset_img
467
+ if @router
468
+ @asset.list_image_assets
469
+ else
470
+ @engine.err SERVER_NOT_RUNNING
471
+ end
472
+ end
473
+
474
+ #
475
+ # List all asset css in the running server.
476
+ # A Debugging tool.
477
+ #
478
+ def msg_list_asset_css
479
+ if @router
480
+ @asset.list_css_assets
481
+ else
482
+ @engine.err SERVER_NOT_RUNNING
483
+ end
484
+ end
485
+
486
+ #
487
+ # List all asset javascript in the running server.
488
+ # A Debugging tool.
489
+ #
490
+ def msg_list_asset_js
491
+ if @router
492
+ @asset.list_js_assets
493
+ else
494
+ @engine.err SERVER_NOT_RUNNING
495
+ end
496
+ end
497
+
498
+ #
499
+ # Add the session data to the response.
500
+ # This will be done for the current (next) request only.
501
+ #
502
+ def msg_add_session_to_response
503
+ @session.add_session_to_response if @session
504
+ end
505
+
506
+ #
507
+ # Clear out the session data, and remove it from the response.
508
+ #
509
+ def msg_clear_session_data
510
+ reset_session_data
511
+ @session.clear_session_data if @session
512
+ end
513
+
514
+
515
+ # ---------------------------------------------------------------------
516
+ # Start and Stop Events
517
+ # Might come from messages or from other application events.
518
+ # RunningApp fires these events.
519
+ # ---------------------------------------------------------------------
520
+
521
+ #
522
+ # Start running the web server.
523
+ #
524
+ def start
525
+ config = WebSvr::Config.new( scheme_value, host_value, port_value )
526
+ @engine.log.info "Web Server URL: #{config.base_url}"
527
+
528
+ handler = WebSvr::Handler.new( @engine, self )
529
+ @web_server = WebSvr::Server.new( @engine, handler, config, ssl_config )
530
+ @web_server.start
531
+
532
+ @router = Routing::Router.new( @engine, self )
533
+ @router.add_page_routes
534
+
535
+ @asset = WebSvr::Asset.new( @engine, self )
536
+ @asset.add_asset_routes
537
+
538
+ @embedded_renderer = WebSvr::EmbeddedRenderer.new( @engine, self )
539
+
540
+ @session = WebSvr::Session.new( @engine, self )
541
+
542
+ run_on_start
543
+ @engine.log.info "Web server started and listening…"
544
+ end
545
+
546
+ #
547
+ # Stop the running web server.
548
+ #
549
+ def stop
550
+ @engine.log.info "Stopping web server…"
551
+
552
+ # Last chance to clear out session data.
553
+ reset_session_data
554
+
555
+ @web_server.stop
556
+ @web_server = nil
557
+ @router = nil
558
+
559
+ run_on_stop
560
+ @engine.log.info "Web server stopped…"
561
+ end
562
+
563
+
564
+ # ---------------------------------------------------------------------
565
+ # On Events - Scripts
566
+ # ---------------------------------------------------------------------
567
+
568
+ #
569
+ # Run the on start script if there is one.
570
+ #
571
+ def run_on_start
572
+ o = find_child ON_START
573
+ return unless o
574
+
575
+ Gloo::Exec::Dispatch.message( @engine, 'run', o )
576
+ end
577
+
578
+ #
579
+ # Run the on stop script if there is one.
580
+ #
581
+ def run_on_stop
582
+ o = find_child ON_STOP
583
+ return unless o
584
+
585
+ Gloo::Exec::Dispatch.message( @engine, 'run', o )
586
+ end
587
+
588
+ #
589
+ # Run the on request script if there is one.
590
+ # Set thee current page object so the app knows
591
+ # which page is being requested.
592
+ #
593
+ def run_on_request current_page
594
+ for_page = find_child CURRENT_PAGE
595
+ alias_value = current_page.pn
596
+ for_page.set_value( alias_value ) if for_page
597
+ o = find_child ON_REQUEST
598
+ return unless o
599
+ o = Gloo::Objs::Alias.resolve_alias( @engine, o )
600
+
601
+ Gloo::Exec::Dispatch.message( @engine, 'run', o, CURRENT_PAGE => current_page )
602
+ end
603
+
604
+ #
605
+ # Run the on response script if there is one.
606
+ #
607
+ def run_on_response
608
+ o = find_child ON_RESPONSE
609
+ return unless o
610
+ o = Gloo::Objs::Alias.resolve_alias( @engine, o )
611
+
612
+ Gloo::Exec::Dispatch.message( @engine, 'run', o )
613
+ end
614
+
615
+ #
616
+ # Set up the request data for the page load.
617
+ # This is done before the on_request event is fired.
618
+ #
619
+ def set_request_data( request )
620
+ # Clear out the redirect if there is one since this is the start of
621
+ # a new request.
622
+ @redirect = nil
623
+
624
+ data = find_child RESQUEST_DATA
625
+ return unless data
626
+ data = Gloo::Objs::Alias.resolve_alias( @engine, data )
627
+
628
+ data.find_child( METHOD )&.set_value( request.method )
629
+ data.find_child( HOST )&.set_value( request.host )
630
+ data.find_child( PATH )&.set_value( request.path )
631
+ data.find_child( QUERY )&.set_value( request.query )
632
+ data.find_child( IP )&.set_value( request.ip )
633
+ end
634
+
635
+ #
636
+ # Set up the response data for the page load.
637
+ # This is done after the page is rendered and before
638
+ # the on_response event is fired.
639
+ #
640
+ def set_response_data( request, response, page_obj=nil )
641
+ begin
642
+ data = find_child RESPONSE_DATA
643
+ return unless data
644
+ data = Gloo::Objs::Alias.resolve_alias( @engine, data )
645
+
646
+ data.find_child( ELAPSED )&.set_value( request.elapsed )
647
+ data.find_child( DB )&.set_value( request.db )
648
+
649
+ if ( response )
650
+ data.find_child( TYPE )&.set_value( response.type )
651
+ data.find_child( CODE )&.set_value( response.code )
652
+ else
653
+ data.find_child( TYPE )&.set_value( '' )
654
+ data.find_child( CODE )&.set_value( '' )
655
+ end
656
+
657
+ if page_obj
658
+ data.find_child( PAGE )&.set_value( page_obj.pn )
659
+ end
660
+ rescue => e
661
+ @engine.log_exception e
662
+ end
663
+ end
664
+
665
+
666
+ # ---------------------------------------------------------------------
667
+ # Pages and standard elements.
668
+ # ---------------------------------------------------------------------
669
+
670
+ #
671
+ # Get the pages container.
672
+ #
673
+ def pages_container
674
+ return find_child PAGES
675
+ end
676
+
677
+ #
678
+ # Get the home page, the root/default route.
679
+ #
680
+ def home_page
681
+ o = find_child HOME
682
+ return nil unless o
683
+
684
+ o = Gloo::Objs::Alias.resolve_alias( @engine, o )
685
+ return o
686
+ end
687
+
688
+ #
689
+ # Get the application error page.
690
+ #
691
+ def err_page
692
+ o = find_child ERR_PAGE
693
+ return nil unless o
694
+
695
+ o = Gloo::Objs::Alias.resolve_alias( @engine, o )
696
+ return o
697
+ end
698
+
699
+ #
700
+ # Get the default layout for pages.
701
+ #
702
+ def default_layout
703
+ o = find_child LAYOUT
704
+ return nil unless o
705
+
706
+ o = Gloo::Objs::Alias.resolve_alias( @engine, o )
707
+ return o
708
+ end
709
+
710
+ end
711
+ end