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