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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: dbf0b84d397a6216caa0d5190e69b2d024a2c3274ddd18137ccc54154cca80ab
|
4
|
+
data.tar.gz: ee17aea00b5048cf3f9cfddc562ec6bbf98b13f48482343e8805dd6e8c03db55
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dcd2a2c8e493a38300942c9f76ad38b4ccaac4503637bc7d0929774371e55bc855441bebb9b49de76e49a619f86deb438f2b2f5d3a0e1ee9ba69ac1e9a1534a0
|
7
|
+
data.tar.gz: a04cadc7cd14f6ecc69a842334a41e57a2d7c6737590ca453c63bbd542211bdc05a0110c0948287a0057ab5bb0176edfac4f1ef376bf2d514c5b2386cdf2b74b
|
data/README.md
CHANGED
@@ -5,27 +5,115 @@
|
|
5
5
|
[![Gem Version](https://badge.fury.io/rb/wabur.svg)](https://rubygems.org/gems/wabur)
|
6
6
|
[![Gem](https://img.shields.io/gem/dt/wabur.svg)](https://rubygems.org/gems/wabur)
|
7
7
|
|
8
|
-
WABuR is a Web Application Builder using Ruby. It
|
9
|
-
|
10
|
-
|
8
|
+
WABuR is a Web Application Builder using Ruby. It is easy to use, taking just
|
9
|
+
a minute to create a `Hello World` web application and it is _FAST_, hitting
|
10
|
+
over 200,000 fetches a second with a Ruby core! It employs a modern NoSQL JSON
|
11
|
+
data store and a single-page UI using JavaScript.
|
11
12
|
|
12
13
|
It is pluggable and extendable in many ways to allow new additions,
|
13
14
|
alternative databases, and any number of UIs.
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
## Quick Start
|
17
|
+
|
18
|
+
With WABuR you are up and running in minutes with only one file to modify to
|
19
|
+
specify attributes. It doesn't get any simpler. Heres how.
|
20
|
+
|
21
|
+
Install the wabur gem.
|
22
|
+
|
23
|
+
```
|
24
|
+
$ gem install wabur
|
25
|
+
```
|
26
|
+
|
27
|
+
Create a new project and `cd` into the directory.
|
19
28
|
|
20
|
-
|
29
|
+
```
|
30
|
+
$ wabur new --base blog Entry
|
31
|
+
$ cd blog
|
32
|
+
```
|
21
33
|
|
22
|
-
|
34
|
+
Define attributes for the data elements along with the attributes that will be
|
35
|
+
displayed in a list view. Open `lib/ui_controller.rb` and modify by adding two
|
36
|
+
lines and changing one.
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
require 'wab/ui'
|
40
|
+
|
41
|
+
class UIController < WAB::UI::MultiFlow
|
42
|
+
|
43
|
+
def initialize(shell)
|
44
|
+
super
|
45
|
+
add_flow(WAB::UI::RestFlow.new(shell,
|
46
|
+
{
|
47
|
+
kind: 'Entry',
|
48
|
+
title: '',
|
49
|
+
content: "\n\n\n\n",
|
50
|
+
}, ['$ref', 'title']))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
Just run the `wabur` command from inside the project directory. Open
|
56
|
+
`localhost:6363` in a browser.
|
57
|
+
|
58
|
+
```
|
59
|
+
$ wabur
|
60
|
+
```
|
23
61
|
|
24
62
|
Want to know more? A tutorial is available in the [tutorial](tutorial/README.md)
|
25
|
-
directory.
|
63
|
+
directory. It takes a couple of minutes to go through the first lesson.
|
64
|
+
|
65
|
+
## WABuR Benchmarks
|
66
|
+
|
67
|
+
<img src="pages/setup.svg" width="400">
|
68
|
+
<table>
|
69
|
+
<tr><td><img src="pages/throughput.svg" width="400"></td><td><img src="pages/latency.svg" width="400"></td></tr>
|
70
|
+
</table>
|
71
|
+
|
72
|
+
| Runner | Read Throughput | Read Latency | Create Throughput | Create Latency | Setup time |
|
73
|
+
| ------------- | --------------- | ------------ | ----------------- | -------------- | ---------- |
|
74
|
+
| WAB Pure Ruby | 2.8K Reads/sec | 1.4 msecs | 2.2K Creates/sec | 1.8 msecs | 1 minute |
|
75
|
+
| OpO-Rub | 212K Reads/sec | 0.09 msecs | 134K Creates/sec | 0.15 msecs | 1 minute |
|
76
|
+
| OpO Direct | 364K Reads/sec | 0.05 msecs | 157K Creates/sec | 0.13 msecs | 1 minute |
|
77
|
+
| Ruby on Rails | 123 Reads/sec | 175 msecs | ---- Creates/sec | ---- msecs | 20 minutes |
|
78
|
+
| Sinatra | 1.5K Reads/sec | 13 msecs | ---- Creates/sec | ---- msecs | 60 minutes |
|
26
79
|
|
27
80
|
More interested in the benchmarks? Then take a look at the [benchmarks page](benchmarks/README.md).
|
28
81
|
|
82
|
+
## Where to use WABuR
|
83
|
+
|
84
|
+
WABuR takes a different approach which opens up new possibilities for uses of
|
85
|
+
Ruby. Some examples that WABuR is suitable for are:
|
86
|
+
|
87
|
+
* Race Results - With the ability to handle massive traffic WABuR can keep up
|
88
|
+
with the load imposed by hundreds of thousands of users requesting results
|
89
|
+
during the race and with the ability to have multiple UIs realtime displays
|
90
|
+
can be different than those used to display results after the race.
|
91
|
+
|
92
|
+
* Voting Systems - Voting systems collect and forward results to central
|
93
|
+
servers. Loads are high during peak periods but well within the limits that
|
94
|
+
WABuR is able to handle. The ease extending WABuR makes it easy to
|
95
|
+
implement the migration of data to a central server or multiple servers.
|
96
|
+
|
97
|
+
* Operations Monitoring - Log using JSON and WABuR can be used to monitor and
|
98
|
+
query logs.
|
99
|
+
|
100
|
+
* Market Data - With the high throughput and low latency WABuR is a good
|
101
|
+
candidate for systems that need to display market data and processing of
|
102
|
+
market data.
|
103
|
+
|
104
|
+
* Mapping - Systems that display data on a map can use WABuRs flexibility
|
105
|
+
with regard to the UI.
|
106
|
+
|
107
|
+
* Directory - As an example of a quick and easy build, an directory or
|
108
|
+
address book can be started in minutes.
|
109
|
+
|
110
|
+
## Rails
|
111
|
+
|
112
|
+
A natural question is *"What about Rails?"*. Rails is well established and has
|
113
|
+
a huge user base. WABuR is not a replacement for Rails. It is an alternative
|
114
|
+
for those who want to explore using JSON databases with a single-page dynamic
|
115
|
+
JavaScript UI.
|
116
|
+
|
29
117
|
## Participate and Contribute
|
30
118
|
|
31
119
|
If you like the idea and want to help out, or become a core developer on the
|
@@ -36,20 +124,23 @@ and lets make something awesome together.
|
|
36
124
|
|
37
125
|
These are the simple guidelines for contributing.
|
38
126
|
|
39
|
-
1.
|
127
|
+
1. Take a look at the [architecture page](pages/Architecture.md) and the source code.
|
128
|
+
|
129
|
+
2. Coordinate with me first before getting started to avoid duplication of
|
40
130
|
effort or implementing something in conflict with the plans.
|
41
131
|
|
42
|
-
|
132
|
+
3. Branch off the `develop` branch and submit a PR.
|
43
133
|
|
44
|
-
|
134
|
+
4. Write unit tests.
|
45
135
|
|
46
|
-
|
136
|
+
5. Write straight forward, clean, and simple code. No magic stuff, no monkey
|
47
137
|
patching Ruby core classes, and no inheriting from core classes.
|
48
138
|
|
49
139
|
## References and Links
|
50
140
|
|
141
|
+
- [Agoo](https://github.com/ohler55/agoo) Web Server GemBuR.
|
51
142
|
- [Oj](https://github.com/ohler55/oj) JSON parser used in WABuR.
|
52
143
|
- [OpO](http://opo.technology) home of the Opo-Rub runner.
|
53
144
|
- [Sass](http://sass-lang.com) used to build the reference implementation UI CSS.
|
54
145
|
- [SystemJS Babel Plugin](https://github.com/systemjs/plugin-babel) also used to transpile JavaScript.
|
55
|
-
- [SystemJS](https://github.com/systemjs/systemjs) used to convert JavaScript ES6 to ES5 in the browser.
|
146
|
+
- [SystemJS](https://github.com/systemjs/systemjs) used to convert JavaScript ES6 to ES5 in the browser.
|
data/bin/wabur
CHANGED
@@ -107,6 +107,12 @@ options = {
|
|
107
107
|
doc: 'HTTP Port to listen on.',
|
108
108
|
arg: 'PORT',
|
109
109
|
},
|
110
|
+
server: {
|
111
|
+
val: 'WEBrick',
|
112
|
+
type: String,
|
113
|
+
doc: 'Web server to use. Can be Agoo, Sinatra, or WEBRick.',
|
114
|
+
arg: 'GEM',
|
115
|
+
},
|
110
116
|
},
|
111
117
|
indent: {
|
112
118
|
val: 0,
|
data/export/assets/js/ui.js
CHANGED
@@ -205,6 +205,24 @@ export class ObjectDisplay extends Display {
|
|
205
205
|
value = obj[key];
|
206
206
|
if (value instanceof Object) {
|
207
207
|
this._setFields(`${path}.${key}`, value);
|
208
|
+
} else if (undefined != element.alt) {
|
209
|
+
switch (element.getAttribute('alt')) {
|
210
|
+
case 'local-time':
|
211
|
+
element.value = new Date(obj[key]).toLocaleString();
|
212
|
+
break;
|
213
|
+
case 'zulu-time':
|
214
|
+
element.value = new Date(obj[key]).toUTCString();
|
215
|
+
break;
|
216
|
+
case 'long-time':
|
217
|
+
element.value = new Date(obj[key]).toString();
|
218
|
+
break;
|
219
|
+
case 'date':
|
220
|
+
element.value = new Date(obj[key]).toDateString();
|
221
|
+
break;
|
222
|
+
default:
|
223
|
+
element.value = obj[key];
|
224
|
+
break;
|
225
|
+
}
|
208
226
|
} else {
|
209
227
|
element.value = obj[key];
|
210
228
|
}
|
data/lib/wab.rb
CHANGED
@@ -11,12 +11,17 @@ module WAB
|
|
11
11
|
end
|
12
12
|
raise ForbiddenError.new(path) if path.include?('..')
|
13
13
|
path = File.expand_path("#{__dir__}/../export#{path}")
|
14
|
-
|
14
|
+
begin
|
15
|
+
File.open(path) { |f| f.read() }
|
16
|
+
rescue Exception
|
17
|
+
nil
|
18
|
+
end
|
15
19
|
end
|
16
20
|
|
17
21
|
end
|
18
22
|
|
19
23
|
require 'wab/controller'
|
24
|
+
require 'wab/racker'
|
20
25
|
require 'wab/data'
|
21
26
|
require 'wab/errors'
|
22
27
|
require 'wab/open_controller'
|
@@ -25,3 +30,4 @@ require 'wab/shell_logger'
|
|
25
30
|
require 'wab/utils'
|
26
31
|
require 'wab/uuid'
|
27
32
|
require 'wab/version'
|
33
|
+
require 'wab/client'
|
data/lib/wab/client.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
|
2
|
+
require 'net/http'
|
3
|
+
require 'oj'
|
4
|
+
|
5
|
+
module WAB
|
6
|
+
|
7
|
+
# A client for a WAB server. It is not specific to any particular
|
8
|
+
# runner. The client allows direct access to the server data. It is just
|
9
|
+
# another view implementation.
|
10
|
+
class Client
|
11
|
+
# Address of the WAB server.
|
12
|
+
attr_accessor :server_address
|
13
|
+
# The port the WAB serer is listening on.
|
14
|
+
attr_accessor :server_port
|
15
|
+
# Prefix to add to the URL path.
|
16
|
+
attr_accessor :path_prefix
|
17
|
+
# The URL path to execute TQL,
|
18
|
+
attr_accessor :tql_path
|
19
|
+
# The key for the type. The default is 'kind'.
|
20
|
+
attr_accessor :type_key
|
21
|
+
# If true the connection to the server is Keep-Alive.
|
22
|
+
attr_accessor :keep_alive
|
23
|
+
|
24
|
+
# Create a new Client for the server at +addr+ and +port+. The provided
|
25
|
+
# options should match the attribute names and types.
|
26
|
+
def initialize(addr, port, options={})
|
27
|
+
@server_address = addr
|
28
|
+
@server_port = port
|
29
|
+
@path_prefix = options.fetch(:path_prefix, '/v1/')
|
30
|
+
@tql_path = options.fetch(:tql_path, '/tql')
|
31
|
+
@type_key = options.fetch(:type_key, 'kind').to_sym
|
32
|
+
@keep_alive = !!options.fetch(:keep_alive, true)
|
33
|
+
@http = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
# Create a new data object. If a query is provided it is treated as a
|
37
|
+
# check against an existing object with the same key/value pairs.
|
38
|
+
#
|
39
|
+
# On error an Exception will be raised.
|
40
|
+
#
|
41
|
+
# data:: the data to use as a new object.
|
42
|
+
# kind:: the kind of the data. If nil the kind is taken from the data
|
43
|
+
# query:: query parameters to match against existing instances. A match fails the insert.
|
44
|
+
def create(data, kind=nil, query=nil)
|
45
|
+
kind ||= data[@type_key] || data[@type_key.to_s]
|
46
|
+
send_request('PUT', kind, query, data)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Return the objects according to the kind and query arguments. The
|
50
|
+
# following patterns supported:
|
51
|
+
#
|
52
|
+
# * query is a ref [12345] looks for MyType with reference ID of 12345
|
53
|
+
# * query is a Hash {name:fred,age:63} looks for all MyTypes with a name
|
54
|
+
# of 'fred' and an age of 63.
|
55
|
+
#
|
56
|
+
# kind:: the data type
|
57
|
+
# query:: query parameters as a Hash.
|
58
|
+
def read(kind, query=nil)
|
59
|
+
send_request('GET', kind, query, nil)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Replaces the object data for the identified object. The query can be a
|
63
|
+
# reference to a specific object or a set of parameters to match.
|
64
|
+
#
|
65
|
+
# The return should be the identifiers for the object updated.
|
66
|
+
#
|
67
|
+
# On error an Exception should be raised.
|
68
|
+
#
|
69
|
+
# kind:: the data type
|
70
|
+
# data:: the data to use as a new object.
|
71
|
+
# query:: query parameters.
|
72
|
+
def update(kind, data, query)
|
73
|
+
raise ArgError.new('data') if data.nil?
|
74
|
+
raise ArgError.new('query') if query.nil?
|
75
|
+
send_request('POST', kind, query, data)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Deletes the data for the identified object(s). The query can be a
|
79
|
+
# reference to a specific object or a set of parameters to match.
|
80
|
+
#
|
81
|
+
# The return is the identifiers for the object updated.
|
82
|
+
#
|
83
|
+
# On error an Exception should be raised.
|
84
|
+
#
|
85
|
+
# kind:: the data type
|
86
|
+
# query:: query parameters.
|
87
|
+
def delete(kind, query=nil)
|
88
|
+
send_request('DELETE', kind, query, nil)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Request the server evaluate a TQL query.
|
92
|
+
#
|
93
|
+
# tql:: query to evaluate.
|
94
|
+
def find(tql)
|
95
|
+
raise ArgError.new('tql') if tql.nil?
|
96
|
+
send_request('POST', 'tql', nil, tql)
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def connect
|
102
|
+
@http ||= Net::HTTP.new(@server_address, @server_port)
|
103
|
+
end
|
104
|
+
|
105
|
+
def form_path(kind, query)
|
106
|
+
return @tql_path if kind == 'tql'
|
107
|
+
path = "#{@path_prefix}#{kind}"
|
108
|
+
if query.is_a?(Hash)
|
109
|
+
first = true
|
110
|
+
query.each_pair { |k,v|
|
111
|
+
if first
|
112
|
+
path << '?'
|
113
|
+
first = false
|
114
|
+
else
|
115
|
+
path << '&'
|
116
|
+
end
|
117
|
+
path << "#{k}=#{v}"
|
118
|
+
}
|
119
|
+
elsif !query.nil?
|
120
|
+
path << '/'
|
121
|
+
path << query.to_s
|
122
|
+
end
|
123
|
+
path
|
124
|
+
end
|
125
|
+
|
126
|
+
def send_request(method, kind, query, content)
|
127
|
+
connect
|
128
|
+
header = {'Connection' => (@keep_alive ? 'Keep-Alive' : 'Close') }
|
129
|
+
unless content.nil?
|
130
|
+
if content.is_a?(Data)
|
131
|
+
content = content.json
|
132
|
+
else
|
133
|
+
content = Oj.dump(content, mode: :wab, indent: 0)
|
134
|
+
end
|
135
|
+
header['Content-Type'] = 'application/json'
|
136
|
+
resp = @http.send_request(method, form_path(kind, query), content, header)
|
137
|
+
else
|
138
|
+
resp = @http.send_request(method, form_path(kind, query))
|
139
|
+
end
|
140
|
+
raise Error.new(resp.body) unless resp.is_a?(Net::HTTPOK)
|
141
|
+
Oj.load(resp.body, mode: :wab)
|
142
|
+
end
|
143
|
+
|
144
|
+
end # Client
|
145
|
+
end # WAB
|
data/lib/wab/controller.rb
CHANGED
@@ -21,7 +21,7 @@ module WAB
|
|
21
21
|
end
|
22
22
|
|
23
23
|
# Handler for paths that do not match the REST pattern or for unregistered
|
24
|
-
# types.
|
24
|
+
# types.
|
25
25
|
#
|
26
26
|
# Processing result are passed back to the view which forward the result
|
27
27
|
# on to the requester. The result, if not nil, should be a Data instance.
|
data/lib/wab/errors.rb
CHANGED
data/lib/wab/impl.rb
CHANGED
@@ -26,9 +26,9 @@ require 'wab/impl/expr'
|
|
26
26
|
require 'wab/impl/path_expr'
|
27
27
|
require 'wab/impl/bool_expr'
|
28
28
|
require 'wab/impl/shell'
|
29
|
-
require 'wab/impl/export_proxy'
|
30
29
|
require 'wab/impl/utils'
|
31
30
|
require 'wab/impl/init'
|
31
|
+
require 'wab/impl/rack_error'
|
32
32
|
|
33
33
|
# Require the concrete Expr subclasses so a mapping table can be created for
|
34
34
|
# the parser.
|
@@ -0,0 +1,18 @@
|
|
1
|
+
|
2
|
+
require 'wab'
|
3
|
+
|
4
|
+
module WAB
|
5
|
+
module Impl
|
6
|
+
|
7
|
+
# The Agoo module contains handlers for the Agoo web server.
|
8
|
+
module Agoo
|
9
|
+
|
10
|
+
end # Agoo
|
11
|
+
end # Impl
|
12
|
+
end # WAB
|
13
|
+
|
14
|
+
require 'wab/impl/agoo/sender'
|
15
|
+
require 'wab/impl/agoo/handler'
|
16
|
+
require 'wab/impl/agoo/tql_handler'
|
17
|
+
require 'wab/impl/agoo/export_proxy'
|
18
|
+
require 'wab/impl/agoo/server'
|
@@ -0,0 +1,55 @@
|
|
1
|
+
|
2
|
+
module WAB
|
3
|
+
module Impl
|
4
|
+
module Agoo
|
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 on_request(req, res)
|
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
|
+
res.code = 200
|
46
|
+
res['Content-Type'] = mime
|
47
|
+
res.body = content
|
48
|
+
rescue Exception => e
|
49
|
+
send_error(e, res)
|
50
|
+
end
|
51
|
+
|
52
|
+
end # ExportProxy
|
53
|
+
end # Agoo
|
54
|
+
end # Impl
|
55
|
+
end # WAB
|