wabur 0.6.2 → 0.7.0

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