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.
- checksums.yaml +5 -5
- data/README.md +106 -15
- data/bin/wabur +6 -0
- data/export/assets/js/ui.js +18 -0
- data/lib/wab.rb +7 -1
- data/lib/wab/client.rb +145 -0
- data/lib/wab/controller.rb +1 -1
- data/lib/wab/errors.rb +6 -0
- data/lib/wab/impl.rb +1 -1
- data/lib/wab/impl/agoo.rb +18 -0
- data/lib/wab/impl/agoo/export_proxy.rb +55 -0
- data/lib/wab/impl/agoo/handler.rb +51 -0
- data/lib/wab/impl/agoo/sender.rb +50 -0
- data/lib/wab/impl/agoo/server.rb +59 -0
- data/lib/wab/impl/agoo/tql_handler.rb +35 -0
- data/lib/wab/impl/model.rb +8 -1
- data/lib/wab/impl/rack_error.rb +27 -0
- data/lib/wab/impl/rack_handler.rb +69 -0
- data/lib/wab/impl/shell.rb +56 -51
- data/lib/wab/impl/sinatra.rb +18 -0
- data/lib/wab/impl/sinatra/export_proxy.rb +57 -0
- data/lib/wab/impl/sinatra/handler.rb +50 -0
- data/lib/wab/impl/sinatra/sender.rb +53 -0
- data/lib/wab/impl/sinatra/server.rb +66 -0
- data/lib/wab/impl/sinatra/tql_handler.rb +35 -0
- data/lib/wab/impl/templates/wabur.conf.template +1 -1
- data/lib/wab/impl/webrick.rb +18 -0
- data/lib/wab/impl/webrick/export_proxy.rb +41 -0
- data/lib/wab/impl/webrick/handler.rb +116 -0
- data/lib/wab/impl/webrick/sender.rb +34 -0
- data/lib/wab/impl/webrick/server.rb +39 -0
- data/lib/wab/impl/webrick/tql_handler.rb +58 -0
- data/lib/wab/racker.rb +25 -0
- data/lib/wab/version.rb +1 -1
- data/pages/Architecture.md +15 -6
- data/test/test_client.rb +282 -0
- data/test/test_impl.rb +2 -0
- data/test/test_runner.rb +267 -91
- metadata +27 -5
- data/lib/wab/impl/export_proxy.rb +0 -39
- 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
|
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
|