wabur 0.6.2 → 0.7.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.
Files changed (41) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +106 -15
  3. data/bin/wabur +6 -0
  4. data/export/assets/js/ui.js +18 -0
  5. data/lib/wab.rb +7 -1
  6. data/lib/wab/client.rb +145 -0
  7. data/lib/wab/controller.rb +1 -1
  8. data/lib/wab/errors.rb +6 -0
  9. data/lib/wab/impl.rb +1 -1
  10. data/lib/wab/impl/agoo.rb +18 -0
  11. data/lib/wab/impl/agoo/export_proxy.rb +55 -0
  12. data/lib/wab/impl/agoo/handler.rb +51 -0
  13. data/lib/wab/impl/agoo/sender.rb +50 -0
  14. data/lib/wab/impl/agoo/server.rb +59 -0
  15. data/lib/wab/impl/agoo/tql_handler.rb +35 -0
  16. data/lib/wab/impl/model.rb +8 -1
  17. data/lib/wab/impl/rack_error.rb +27 -0
  18. data/lib/wab/impl/rack_handler.rb +69 -0
  19. data/lib/wab/impl/shell.rb +56 -51
  20. data/lib/wab/impl/sinatra.rb +18 -0
  21. data/lib/wab/impl/sinatra/export_proxy.rb +57 -0
  22. data/lib/wab/impl/sinatra/handler.rb +50 -0
  23. data/lib/wab/impl/sinatra/sender.rb +53 -0
  24. data/lib/wab/impl/sinatra/server.rb +66 -0
  25. data/lib/wab/impl/sinatra/tql_handler.rb +35 -0
  26. data/lib/wab/impl/templates/wabur.conf.template +1 -1
  27. data/lib/wab/impl/webrick.rb +18 -0
  28. data/lib/wab/impl/webrick/export_proxy.rb +41 -0
  29. data/lib/wab/impl/webrick/handler.rb +116 -0
  30. data/lib/wab/impl/webrick/sender.rb +34 -0
  31. data/lib/wab/impl/webrick/server.rb +39 -0
  32. data/lib/wab/impl/webrick/tql_handler.rb +58 -0
  33. data/lib/wab/racker.rb +25 -0
  34. data/lib/wab/version.rb +1 -1
  35. data/pages/Architecture.md +15 -6
  36. data/test/test_client.rb +282 -0
  37. data/test/test_impl.rb +2 -0
  38. data/test/test_runner.rb +267 -91
  39. metadata +27 -5
  40. data/lib/wab/impl/export_proxy.rb +0 -39
  41. data/lib/wab/impl/handler.rb +0 -98
@@ -0,0 +1,18 @@
1
+
2
+ require 'wab'
3
+
4
+ module WAB
5
+ module Impl
6
+
7
+ # The Sinatra module contains handlers for the Sinatra web server.
8
+ module Sinatra
9
+
10
+ end # Sinatra
11
+ end # Impl
12
+ end # WAB
13
+
14
+ require 'wab/impl/sinatra/sender'
15
+ require 'wab/impl/sinatra/handler'
16
+ require 'wab/impl/sinatra/tql_handler'
17
+ require 'wab/impl/sinatra/export_proxy'
18
+ require 'wab/impl/sinatra/server'
@@ -0,0 +1,57 @@
1
+
2
+ module WAB
3
+ module Impl
4
+ module Sinatra
5
+
6
+ # A handler that provides missing files in an assets directory where the
7
+ # files are the wab and wab UI files.
8
+ class ExportProxy
9
+ include Sender
10
+
11
+ def initialize(shell)
12
+ @shell = shell
13
+ end
14
+
15
+ def call(req)
16
+ path = (req.script_name + req.path_info)
17
+ query = parse_query(req.query_string)
18
+ path = '/index.html' if '/' == path
19
+ mime = nil
20
+ index = path.rindex('.')
21
+ unless index.nil?
22
+ mime = {
23
+ 'css' => 'text/css',
24
+ 'eot' => 'application/vnd.ms-fontobject',
25
+ 'es5' => 'application/javascript',
26
+ 'es6' => 'application/javascript',
27
+ 'gif' => 'image/gif',
28
+ 'html' => 'text/html',
29
+ 'ico' => 'image/x-icon',
30
+ 'jpeg' => 'image/jpeg',
31
+ 'jpg' => 'image/jpeg',
32
+ 'js' => 'application/javascript',
33
+ 'json' => 'application/json',
34
+ 'png' => 'image/png',
35
+ 'sse' => 'text/plain',
36
+ 'svg' => 'image/svg+xml',
37
+ 'ttf' => 'application/font-sfnt',
38
+ 'txt' => 'text/plain',
39
+ 'woff' => 'application/font-woff',
40
+ 'woff2' => 'font/woff2',
41
+ }[path[index + 1..-1].downcase]
42
+ end
43
+ mime = 'text/plain' if mime.nil?
44
+ content = WAB.get_export(path)
45
+ [
46
+ 200,
47
+ {'Content-Type' => mime},
48
+ [content]
49
+ ]
50
+ rescue Exception => e
51
+ send_error(e, res)
52
+ end
53
+
54
+ end # ExportProxy
55
+ end # Sinatra
56
+ end # Impl
57
+ end # WAB
@@ -0,0 +1,50 @@
1
+
2
+ module WAB
3
+ module Impl
4
+ module Sinatra
5
+
6
+ # Handler for requests that fall under the path assigned to the
7
+ # Controller. This is used only with the WAB::Impl::Shell.
8
+ class Handler
9
+ include Sender
10
+
11
+ def initialize(shell, controller)
12
+ @shell = shell
13
+ @controller = controller
14
+ end
15
+
16
+ def wab_call(req)
17
+ path = (req.script_name + req.path_info).split('/')[1..-1]
18
+ query = parse_query(req.query_string)
19
+ body = nil
20
+ unless req.body.nil?
21
+ content = req.body.read
22
+ unless content.empty?
23
+ body = Data.new(Oj.strict_load(content, symbol_keys: true))
24
+ body.detect
25
+ end
26
+ end
27
+ case req.request_method
28
+ when 'GET'
29
+ @shell.log_call(@controller, 'read', path, query)
30
+ send_result(@controller.read(path, query), path, query)
31
+ when 'PUT'
32
+ @shell.log_call(@controller, 'create', path, query, body)
33
+ send_result(@controller.create(path, query, body), path, query)
34
+ when 'POST'
35
+ @shell.log_call(@controller, 'update', path, query, body)
36
+ send_result(@controller.update(path, query, body), path, query)
37
+ when 'DELETE'
38
+ @shell.log_call(@controller, 'delete', path, query)
39
+ send_result(@controller.delete(path, query), path, query)
40
+ else
41
+ raise StandardError.new("#{method} is not a supported method") if op.nil?
42
+ end
43
+ rescue StandardError => e
44
+ send_error(e)
45
+ end
46
+
47
+ end # Handler
48
+ end # Sinatra
49
+ end # Impl
50
+ end # WAB
@@ -0,0 +1,53 @@
1
+
2
+ module WAB
3
+ module Impl
4
+ module Sinatra
5
+
6
+ # The Sender module adds support for sending results and errors.
7
+ module Sender
8
+
9
+ # Sends the results from a controller request.
10
+ def send_result(result, path, query)
11
+ result = @shell.data(result) unless result.is_a?(WAB::Data)
12
+ response_body = result.json(@shell.indent)
13
+ @shell.logger.debug("reply to #{path.join('/')}#{query}: #{response_body}") if @shell.logger.debug?
14
+ [
15
+ 200,
16
+ {'Content-Type' => 'application/json'},
17
+ [result.json(@shell.indent)]
18
+ ]
19
+ end
20
+
21
+ # Sends an error from a rescued call.
22
+ def send_error(e)
23
+ @shell.logger.warn(Impl.format_error(e))
24
+ body = { code: -1, error: "#{e.class}: #{e.message}" }
25
+ body[:backtrace] = e.backtrace
26
+ [ 500,
27
+ { 'Content-Type' => 'application/json' },
28
+ [ @shell.data(body).json(@shell.indent) ]
29
+ ]
30
+ end
31
+
32
+ # Parses a query string into a Hash.
33
+ def parse_query(query_string)
34
+ query = {}
35
+ if !query_string.nil? && !query_string.empty?
36
+ query_string.split('&').each { |opt|
37
+ k, v = opt.split('=')
38
+ # TBD convert %xx to char
39
+ query[k] = v
40
+ }
41
+ end
42
+ # Detect numbers (others later)
43
+ query.each_pair { |k,v|
44
+ i = Utils.attempt_key_to_int(v)
45
+ query[k] = i unless i.nil?
46
+ # TBD how about float
47
+ }
48
+ end
49
+
50
+ end # Sender
51
+ end # Sinatra
52
+ end # Impl
53
+ end # WAB
@@ -0,0 +1,66 @@
1
+
2
+ require 'sinatra/base'
3
+
4
+ module WAB
5
+ module Impl
6
+ module Sinatra
7
+
8
+ # The Server module provides a server start method.
9
+ class Server < ::Sinatra::Application
10
+
11
+ # Start the server and set the mount points.
12
+ def self.start(shell)
13
+ set(:port, shell.http_port)
14
+ set(:public_folder, 'public')
15
+ set(:public_folder, File.expand_path(shell.http_dir))
16
+ set(:static, true)
17
+ set(:logging, shell.logger.info?)
18
+
19
+ shell.mounts.each { |hh|
20
+ if hh.has_key?(:type)
21
+ handler = WAB::Impl::Sinatra::Handler.new(shell, shell.create_controller(hh[:handler]))
22
+ path = "#{shell.pre_path}/#{hh[:type]}"
23
+
24
+ get(path) { handler.wab_call(request) }
25
+ get(path+'/*') { handler.wab_call(request) }
26
+
27
+ put(path) { handler.wab_call(request) }
28
+ put(path+'/*') { handler.wab_call(request) }
29
+
30
+ post(path) { handler.wab_call(request) }
31
+ post(path+'/*') { handler.wab_call(request) }
32
+
33
+ delete(path) { handler.wab_call(request) }
34
+ delete(path+'/*') { handler.wab_call(request) }
35
+
36
+ elsif hh.has_key?(:path)
37
+ path = hh[:path]
38
+ if path.empty?
39
+ path = '/**'
40
+ elsif '*' != path[-1]
41
+ path << '/' unless '/' == path[-1]
42
+ path << '**'
43
+ end
44
+ controller = shell.create_controller(hh[:handler])
45
+ post(path) { controller.call(request.env) }
46
+ else
47
+ raise WAB::Error.new("Invalid handle configuration. Missing path or type.")
48
+ end
49
+ }
50
+ unless (shell.tql_path.nil? || shell.tql_path.empty?)
51
+ tql_handler = WAB::Impl::Sinatra::TqlHandler.new(shell)
52
+ post('/tql') { tql_handler.call(request) }
53
+ end
54
+ if shell.export_proxy
55
+ exporter = WAB::Impl::Sinatra::ExportProxy.new(shell)
56
+ get('/**') { exporter.call(request) }
57
+ end
58
+
59
+ trap 'INT' do server.shutdown end
60
+ run!
61
+ end
62
+
63
+ end # Server
64
+ end # Sinatra
65
+ end # Impl
66
+ end # WAB
@@ -0,0 +1,35 @@
1
+
2
+ module WAB
3
+ module Impl
4
+ module Sinatra
5
+
6
+ # Handler for requests that fall under the path assigned to the
7
+ # Controller. This is used only with the WAB::Impl::Shell.
8
+ class TqlHandler
9
+ include Sender
10
+
11
+ def initialize(shell)
12
+ @shell = shell
13
+ end
14
+
15
+ def call(req)
16
+ path = (req.script_name + req.path_info).split('/')[1..-1]
17
+ query = parse_query(req.query_string)
18
+ tql = Oj.load(req.body, mode: :wab)
19
+ log_request_with_body('TQL', path, query, tql) if @shell.logger.info?
20
+ send_result(@shell.query(tql), path, query)
21
+ rescue StandardError => e
22
+ send_error(e, res)
23
+ end
24
+
25
+ private
26
+
27
+ def log_request_with_body(caller, path, query, body)
28
+ body = Data.new(body) unless body.is_a?(WAB::Data)
29
+ @shell.logger.info("#{caller} #{path.join('/')}#{query}\n#{body.json(@shell.indent)}")
30
+ end
31
+
32
+ end # TqlHandler
33
+ end # Sinatra
34
+ end # Impl
35
+ end # WAB
@@ -33,7 +33,7 @@ export_proxy = true
33
33
  # JSON sent as a response to View requests.
34
34
  indent = 0
35
35
 
36
- # Handlers for each type of record and for the UI shich are Ruby generated UI
36
+ # Handlers for each type of record and for the UI which are Ruby generated UI
37
37
  # configuration records.
38
38
  handler.0.type = ui
39
39
  handler.0.handler = UIController%{handlers}
@@ -0,0 +1,18 @@
1
+
2
+ require 'wab'
3
+
4
+ module WAB
5
+ module Impl
6
+
7
+ # The WEBrick module contains handlers for the WEBrick web server.
8
+ module WEBrick
9
+
10
+ end # WEBrick
11
+ end # Impl
12
+ end # WAB
13
+
14
+ require 'wab/impl/webrick/export_proxy'
15
+ require 'wab/impl/webrick/sender'
16
+ require 'wab/impl/webrick/handler'
17
+ require 'wab/impl/webrick/tql_handler'
18
+ require 'wab/impl/webrick/server'
@@ -0,0 +1,41 @@
1
+
2
+ require 'webrick'
3
+
4
+ module WAB
5
+ module Impl
6
+ module WEBrick
7
+
8
+ # A handler that provides missing files in an assets directory where the
9
+ # files are the wab and wab UI files.
10
+ class ExportProxy < ::WEBrick::HTTPServlet::FileHandler
11
+
12
+ def initialize(server, path)
13
+ super
14
+ end
15
+
16
+ def do_GET(req, res)
17
+ super
18
+ rescue Exception => e
19
+ path = req.path
20
+ path = '/index.html' if '/' == path
21
+ begin
22
+ mime = nil
23
+ index = path.rindex('.')
24
+ unless index.nil?
25
+ mime = WEBrick::HTTPUtils::DefaultMimeTypes[path[index + 1..-1]]
26
+ end
27
+ mime = 'text/plain' if mime.nil?
28
+ content = WAB.get_export(path)
29
+ res.status = 200
30
+ res['Content-Type'] = mime
31
+ res.body = content
32
+ rescue Exception
33
+ # raise the original error for a normal not found error
34
+ raise e
35
+ end
36
+ end
37
+
38
+ end # ExportProxy
39
+ end # WEBrick
40
+ end # Impl
41
+ end # WAB
@@ -0,0 +1,116 @@
1
+
2
+ require 'webrick'
3
+
4
+ module WAB
5
+ module Impl
6
+ module WEBrick
7
+
8
+ # Handler for requests that fall under the path assigned to the
9
+ # Controller. This is used only with the WAB::Impl::Shell.
10
+ class Handler < ::WEBrick::HTTPServlet::AbstractServlet
11
+ include Sender
12
+
13
+ def initialize(server, shell, controller, is_rack)
14
+ super(server)
15
+ @shell = shell
16
+ @controller = controller
17
+ @is_rack = is_rack
18
+ end
19
+
20
+ def service(req, res)
21
+ if @is_rack
22
+ rack_call(req, res)
23
+ else
24
+ path, query, body = extract_path_query(req)
25
+ case req.request_method
26
+ when 'GET'
27
+ @shell.log_call(@controller, 'read', path, query)
28
+ send_result(@controller.read(path, query), res, path, query)
29
+ when 'PUT'
30
+ @shell.log_call(@controller, 'create', path, query, body)
31
+ send_result(@controller.create(path, query, body), res, path, query)
32
+ when 'POST'
33
+ @shell.log_call(@controller, 'update', path, query, body)
34
+ send_result(@controller.update(path, query, body), res, path, query)
35
+ when 'DELETE'
36
+ @shell.log_call(@controller, 'delete', path, query)
37
+ send_result(@controller.delete(path, query), res, path, query)
38
+ else
39
+ raise StandardError.new("#{method} is not a supported method") if op.nil?
40
+ end
41
+ end
42
+ rescue StandardError => e
43
+ send_error(e, res)
44
+ end
45
+
46
+ private
47
+
48
+ def rack_call(req, res)
49
+ env = {
50
+ 'REQUEST_METHOD' => req.request_method,
51
+ 'SCRIPT_NAME' => req.script_name,
52
+ 'PATH_INFO' => req.path_info,
53
+ 'QUERY_STRING' => req.query_string,
54
+ 'SERVER_NAME' => req.server_name,
55
+ 'SERVER_PORT' => req.port,
56
+ 'rack.version' => '1.2',
57
+ 'rack.url_scheme' => req.ssl? ? 'https' : 'http',
58
+ 'rack.errors' => WAB::Impl::RackError.new(@shell),
59
+ 'rack.multithread' => false,
60
+ 'rack.multiprocess' => false,
61
+ 'rack.run_once' => false,
62
+ }
63
+ path = req.script_name + req.path_info
64
+
65
+ @shell.log_call(@controller, 'call', path, req.query_string, req.body)
66
+ req.each { |k| env['HTTP_' + k] = req[k] }
67
+ env['rack.input'] = StringIO.new(req.body) unless req.body.nil?
68
+ rres = @controller.call(env)
69
+ res.status = rres[0]
70
+ rres[1].each { |a| res[a[0]] = a[1] }
71
+ unless rres[2].empty?
72
+ res.body = ''
73
+ rres[2].each { |s| res.body << s }
74
+ end
75
+ @shell.logger.debug("reply to #{path}#{req.query_string}: #{res.body}") if @shell.logger.debug?
76
+ rescue StandardError => e
77
+ send_error(e, res)
78
+ end
79
+
80
+ # Pulls and converts the request path, query, and body.
81
+ def extract_path_query(req)
82
+ path = req.path.split('/')[1..-1]
83
+ query = {}
84
+ if !req.query_string.nil? && !req.query_string.empty? && req.query.empty?
85
+ # WEBRick does not parse queries on PUT and some others so do it
86
+ # manually.
87
+ req.query_string.split('&').each { |opt|
88
+ k, v = opt.split('=')
89
+ # TBD convert %xx to char
90
+ query[k] = v
91
+ }
92
+ else
93
+ req.query.each { |k,v| query[k.to_sym] = v }
94
+ end
95
+ # Detect numbers (others later)
96
+ query.each_pair { |k,v|
97
+ i = Utils.attempt_key_to_int(v)
98
+ query[k] = i unless i.nil?
99
+ # TBD how about float
100
+ }
101
+ request_body = req.body
102
+ if request_body.nil?
103
+ body = nil
104
+ else
105
+ body = Data.new(
106
+ Oj.strict_load(request_body, symbol_keys: true)
107
+ )
108
+ body.detect
109
+ end
110
+ [path, query, body]
111
+ end
112
+
113
+ end # Handler
114
+ end # WEBrick
115
+ end # Impl
116
+ end # WAB