angelo 0.3.3 → 0.4.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 +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +1 -4
- data/CHANGELOG.md +22 -0
- data/Gemfile +5 -4
- data/README.md +101 -57
- data/angelo.gemspec +13 -10
- data/lib/angelo.rb +5 -2
- data/lib/angelo/base.rb +136 -54
- data/lib/angelo/main.rb +47 -0
- data/lib/angelo/minitest/helpers.rb +51 -23
- data/lib/angelo/params_parser.rb +33 -17
- data/lib/angelo/responder.rb +10 -9
- data/lib/angelo/responder/eventsource.rb +21 -20
- data/lib/angelo/responder/websocket.rb +1 -2
- data/lib/angelo/stash.rb +5 -3
- data/lib/angelo/tilt/erb.rb +38 -11
- data/lib/angelo/version.rb +1 -1
- data/test/angelo/erb_spec.rb +65 -3
- data/test/angelo/mustermann_spec.rb +1 -8
- data/test/angelo/options_spec.rb +19 -0
- data/test/angelo/params_spec.rb +27 -2
- data/test/angelo_spec.rb +18 -0
- data/test/main/app.rb +38 -0
- data/test/main/main_spec.rb +101 -0
- data/test/spec_helper.rb +7 -39
- metadata +37 -6
- data/lib/angelo/mustermann.rb +0 -98
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 71eca9d2230c44d5b635e6e46080b5fab3848dab
|
4
|
+
data.tar.gz: 2a29741be7955f76e270e08b7fd05ad5a2ee0394
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e274a5f18cc333463ccaee271cd7ba08dd1b550552f045cfe82ba18b08e73caa09df6712b2e296dc41e37c2b05be079be205337e055a3fdee18e3f822e3e9ab5
|
7
|
+
data.tar.gz: b7034f60500807c47f0177acab56c5de0c4e15bf647ec3a61c995ac5635f2d0653b9217f530077be7a8b95b81e3a2256e30d110c2988bfd9e85d9ac0ec131a95
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,28 @@
|
|
1
1
|
changelog
|
2
2
|
=========
|
3
3
|
|
4
|
+
### 0.4.0 5 feb 2015
|
5
|
+
|
6
|
+
thanks: @tommay, @kyledrake, @katjaeinsfeld
|
7
|
+
|
8
|
+
* refactor UnboundMethod -> instance_exec (#38)
|
9
|
+
* add angelo/main for sinatra-like top-level DSL (#37)
|
10
|
+
* make SymHash.new recurse array values
|
11
|
+
* remove 'layouts_' from view_dir/layouts/*
|
12
|
+
* return anything that respond_to? :to_json when content_type :json
|
13
|
+
* remove event restriction to SSE stash contexts
|
14
|
+
* add reload_templates! DSL method
|
15
|
+
* refactor testing helpers (#35)
|
16
|
+
* refactor ParamsParser#parse_post_body (#34)
|
17
|
+
* Responder.symhash refactored to Angelo::SymHash (#33)
|
18
|
+
* define_app now accepts a subclass of Angelo::Base (#32)
|
19
|
+
* testing auxiliary classes moved to angelo/minitest/helpers (#32)
|
20
|
+
* SimpleCov testing output support on MRI (#28)
|
21
|
+
* Mustermann wholly integrated
|
22
|
+
* include Angelo::Tilt::ERB by default
|
23
|
+
* add Tilt ~> 2.0 and Mustermann ~> 0.4 to gemspec
|
24
|
+
* require ruby >= 2.1 (also a Mustermann requirement)
|
25
|
+
|
4
26
|
### 0.3.3 16 jan 2015
|
5
27
|
|
6
28
|
thanks: @mighe, @tarcieri, @jc00ke, @gunnarmarten, @tommay
|
data/Gemfile
CHANGED
@@ -4,10 +4,7 @@ gem 'reel', '~>0.5'
|
|
4
4
|
gem 'tilt', '~>2.0'
|
5
5
|
gem 'mime-types', '~>2.4'
|
6
6
|
gem 'websocket-driver', '~>0.3'
|
7
|
-
|
8
|
-
platform :ruby_21, :ruby_22 do
|
9
|
-
gem 'mustermann', '~>0.4'
|
10
|
-
end
|
7
|
+
gem 'mustermann', '~>0.4'
|
11
8
|
|
12
9
|
group :development do
|
13
10
|
gem 'pry', '~>0.10'
|
@@ -23,4 +20,8 @@ end
|
|
23
20
|
group :test do
|
24
21
|
gem 'httpclient', '~>2.5'
|
25
22
|
gem 'minitest', '~>5.4'
|
23
|
+
|
24
|
+
platform :mri do
|
25
|
+
gem 'simplecov', '~>0.9.1'
|
26
|
+
end
|
26
27
|
end
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@ Angelo
|
|
3
3
|
|
4
4
|
[](https://travis-ci.org/kenichi/angelo)
|
5
5
|
|
6
|
-
A [Sinatra](https://github.com/sinatra/sinatra)-
|
6
|
+
A [Sinatra](https://github.com/sinatra/sinatra)-like DSL for [Reel](https://github.com/celluloid/reel).
|
7
7
|
|
8
8
|
### tl;dr
|
9
9
|
|
@@ -12,34 +12,35 @@ A [Sinatra](https://github.com/sinatra/sinatra)-esque DSL for [Reel](https://git
|
|
12
12
|
* contextual websocket/sse stashing via `websockets` and `sses` helpers
|
13
13
|
* `task` handling via `async` and `future` helpers
|
14
14
|
* no rack
|
15
|
-
*
|
16
|
-
*
|
15
|
+
* tilt/erb support
|
16
|
+
* mustermann support
|
17
17
|
|
18
18
|
### What is Angelo?
|
19
19
|
|
20
20
|
Just like Sinatra, Angelo gives you an expressive DSL for creating web applications. There are some
|
21
|
-
notable differences, but the basics remain the same
|
22
|
-
|
21
|
+
notable differences, but the basics remain the same: you can either create a "classic" application
|
22
|
+
by requiring 'angelo/main' and using the DSL at the top level of your script, or a "modular"
|
23
|
+
application by requiring 'angelo', subclassing `Angelo::Base`, and calling `.run!` on that class for the
|
24
|
+
service to start.
|
23
25
|
In addition, and perhaps more importantly, **Angelo is built upon Reel, which is, in turn, built upon
|
24
26
|
Celluloid::IO and gives you a reactor with evented IO in Ruby!**
|
25
27
|
|
26
|
-
|
28
|
+
Things will feel very familiar to anyone experienced with Sinatra. You can define
|
29
|
+
route handlers denoted by HTTP verb and path with parameters set from path matching (using
|
30
|
+
[Mustermann](#mustermann)), the query string, and post body.
|
31
|
+
A route block may return:
|
27
32
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
you may return any object that responds to `#each(&block)` if the transfer encoding is set to `:chunked`.
|
33
|
+
* The body of the response in full as a `String`.
|
34
|
+
* A `Hash` (or anything that `respond_to? :to_json`) if the content type is set to `:json`.
|
35
|
+
* Any object that responds to `#each(&block)` if the transfer encoding is set to `:chunked`.
|
32
36
|
There is also a `chunked_response` helper that will take a block, set the transfer encoding, and return
|
33
|
-
an appropriate object
|
37
|
+
an appropriate object.
|
34
38
|
|
35
|
-
|
36
|
-
matching and params.
|
37
|
-
|
38
|
-
Angelo also features `before` and `after` blocks, just like Sinatra. Filters are ordered as defined,
|
39
|
+
Angelo also features `before` and `after` filter blocks, just like Sinatra. Filters are ordered as defined,
|
39
40
|
and called in that order. When defined without a path, they run for all matched requests. With a path,
|
40
|
-
the path
|
41
|
-
|
42
|
-
after blocks are handled, see the Errors section below for more info.
|
41
|
+
the path is interpreted as a Mustermann pattern and params are merged. `before` filters can set instance
|
42
|
+
variables which can be used in the route block and the `after` filter.
|
43
|
+
For more info on the difference in how after blocks are handled, see the Errors section below for more info.
|
43
44
|
|
44
45
|
### Websockets!
|
45
46
|
|
@@ -52,9 +53,8 @@ for you to build real-time web applications.
|
|
52
53
|
The `websocket` route builder accepts a path and a block, and passes the actual websocket to the block
|
53
54
|
as the only argument. This socket is an instance of Reel's
|
54
55
|
[WebSocket](https://github.com/celluloid/reel/blob/master/lib/reel/websocket.rb) class, and, as such,
|
55
|
-
responds to methods like `on_message` and `on_close`. A service-wide `on_pong` handler
|
56
|
-
|
57
|
-
a connected websocket client.
|
56
|
+
responds to methods like `on_message` and `on_close`. A service-wide `on_pong` handler may be defined
|
57
|
+
to customize the behavior when a pong frame comes back from a connected websocket client.
|
58
58
|
|
59
59
|
##### `websockets` helper
|
60
60
|
|
@@ -96,15 +96,15 @@ end
|
|
96
96
|
Foo.run!
|
97
97
|
```
|
98
98
|
|
99
|
-
In this case, any clients that
|
100
|
-
default websockets array; clients that
|
99
|
+
In this case, any clients that connect to a websocket at the path '/' will be stashed in the
|
100
|
+
default websockets array; clients that connect to '/bar' will be stashed in the `:bar` section.
|
101
101
|
|
102
|
-
Each "section"
|
102
|
+
Each "section" is accessed with a familiar, `Hash`-like syntax, and can be iterated over with
|
103
103
|
a `.each` block.
|
104
104
|
|
105
|
-
When a `POST /` with a 'foo' param is received, any value is messaged out to
|
105
|
+
When a `POST /` with a 'foo' param is received, any value is messaged out to all '/' connected
|
106
106
|
websockets. When a `POST /bar` with a 'bar' param is received, any value is messaged out to all
|
107
|
-
websockets
|
107
|
+
websockets connected to '/bar'.
|
108
108
|
|
109
109
|
### SSE - Server-Sent Events
|
110
110
|
|
@@ -176,12 +176,12 @@ end
|
|
176
176
|
##### `sses` helper
|
177
177
|
|
178
178
|
Angelo also includes a "stash" helper for SSE connections. One can `<<` a socket into `sses` from
|
179
|
-
inside an `eventsource` handler block. These can
|
179
|
+
inside an `eventsource` handler block. These can "later" be iterated over so one can do things
|
180
180
|
like emit a message on every SSE connection when the service receives a POST request.
|
181
181
|
|
182
|
-
The `sses` helper includes the same
|
182
|
+
The `sses` helper includes the same context ability as the `websockets` helper. Also, by default,
|
183
183
|
the helper will `reject!` any closed sockets before returning, just like `websockets`. You may
|
184
|
-
optionally pass `false` to the helper to skip this step.In addition, the `sses` stash includes
|
184
|
+
optionally pass `false` to the helper to skip this step. In addition, the `sses` stash includes
|
185
185
|
methods for easily sending events or messages to all stashed connections. **Note that the
|
186
186
|
`Stash::SSE#event` method only works on non-default contexts and uses the context name as the event
|
187
187
|
name.**
|
@@ -227,7 +227,7 @@ do not support EventSource yet, since this will respond with a non-"text/event-s
|
|
227
227
|
|
228
228
|
##### `EventSource#on_close` helper
|
229
229
|
|
230
|
-
When inside an eventsource block, you
|
230
|
+
When inside an eventsource block, you may want to do something specific when a client closes the
|
231
231
|
connection. For this case, there are `on_close` and `on_close=` methods on the object passed to the block
|
232
232
|
that will get called if the client closes the socket. The assignment method takes a proc object and the
|
233
233
|
other one takes a block:
|
@@ -252,13 +252,13 @@ eventsource '/sse' do |es|
|
|
252
252
|
end
|
253
253
|
```
|
254
254
|
|
255
|
-
Note the use of the optional parameter the stashes here; by default, stash accessors (`websockets` and
|
255
|
+
Note the use of the optional parameter of the stashes here; by default, stash accessors (`websockets` and
|
256
256
|
`sses`) will `reject!` any closed sockets before letting you in. If you pass `false` to the stash
|
257
257
|
accessors, they will skip the `reject!` step.
|
258
258
|
|
259
259
|
### Tasks + Async / Future
|
260
260
|
|
261
|
-
Angelo is built on Reel and Celluloid::IO, giving your web application
|
261
|
+
Angelo is built on Reel and Celluloid::IO, giving your web application the ability to define
|
262
262
|
"tasks" and call them from route handler blocks in an `async` or `future` style.
|
263
263
|
|
264
264
|
##### `task` builder
|
@@ -268,7 +268,7 @@ block. The block can take arguments that you can pass later, with `async` or `fu
|
|
268
268
|
|
269
269
|
```ruby
|
270
270
|
# defining a task on the reactor called `:in_sec` which will sleep for
|
271
|
-
# given number of seconds, then return the given message.
|
271
|
+
# the given number of seconds, then return the given message.
|
272
272
|
#
|
273
273
|
task :in_sec do |sec, msg|
|
274
274
|
sleep sec.to_i
|
@@ -300,9 +300,9 @@ end
|
|
300
300
|
##### `future` helper
|
301
301
|
|
302
302
|
Just like `async`, this comes from Celluloid as well. It behaves exactly like `async`, with the
|
303
|
-
notable exception of
|
304
|
-
the return value of the task.
|
305
|
-
|
303
|
+
notable exception of returning a "future" object that you can call `#value` on later to retrieve
|
304
|
+
the return value of the task. Calling `#value` will "block" until the task is finished, while the
|
305
|
+
reactor continues to process requests.
|
306
306
|
|
307
307
|
```ruby
|
308
308
|
get '/' do
|
@@ -324,8 +324,8 @@ Angelo gives you two ordained methods of stopping route processing:
|
|
324
324
|
* raise an instance of `RequestError`
|
325
325
|
* `halt` with a status code and message
|
326
326
|
|
327
|
-
The main difference is that `halt` will still run
|
328
|
-
will bypass
|
327
|
+
The main difference is that `halt` will still run `after` blocks, and raising `RequestError`
|
328
|
+
will bypass `after` blocks.
|
329
329
|
|
330
330
|
Any other exceptions or errors raised by your route handler will be handled with a 500 status
|
331
331
|
code and the message will be the body of the response.
|
@@ -334,7 +334,7 @@ code and the message will be the body of the response.
|
|
334
334
|
|
335
335
|
Raising an instance of `Angelo::RequestError` causes a 400 status code response, and the message
|
336
336
|
in the instance is the body of the the response. If the route or class was set to respond with
|
337
|
-
JSON, the body is converted to a JSON object with one key, `error`, that has
|
337
|
+
JSON, the body is converted to a JSON object with one key, `error`, that has the value of the message.
|
338
338
|
If the message is a `Hash`, the hash is converted to a JSON object, or to a string for other content
|
339
339
|
types.
|
340
340
|
|
@@ -414,15 +414,8 @@ everything's fine
|
|
414
414
|
|
415
415
|
### [Tilt](https://github.com/rtomayko/tilt) / ERB
|
416
416
|
|
417
|
-
To make `erb` available in route blocks
|
418
|
-
|
419
|
-
1. add `tilt` to your `Gemfile`: `gem 'tilt'`
|
420
|
-
2. require `angelo/tilt/erb`
|
421
|
-
3. include `Angelo::Tilt::ERB` in your app
|
422
|
-
|
423
417
|
```ruby
|
424
418
|
class Foo < Angelo::Base
|
425
|
-
include Angelo::Tilt::ERB
|
426
419
|
|
427
420
|
views_dir 'some/other/path' # defaults to './views'
|
428
421
|
|
@@ -448,17 +441,8 @@ See [views](https://github.com/kenichi/angelo/tree/master/test/test_app_root/vie
|
|
448
441
|
|
449
442
|
### [Mustermann](https://github.com/rkh/mustermann)
|
450
443
|
|
451
|
-
To make routes blocks match path with Mustermann patterns
|
452
|
-
|
453
|
-
1. be using ruby >=2.0.0
|
454
|
-
2. add 'mustermann' to to your `Gemfile`: `platform(:ruby_20){ gem 'mustermann' }`
|
455
|
-
3. require `angelo/mustermann`
|
456
|
-
4. include `Angelo::Mustermann` in your app
|
457
|
-
|
458
444
|
```ruby
|
459
445
|
class Foo < Angelo::Base
|
460
|
-
include Angelo::Tilt::ERB
|
461
|
-
include Angelo::Mustermann
|
462
446
|
|
463
447
|
get '/:foo/things/:bar' do
|
464
448
|
|
@@ -486,6 +470,68 @@ class Foo < Angelo::Base
|
|
486
470
|
end
|
487
471
|
```
|
488
472
|
|
473
|
+
### Classic vs. modular apps
|
474
|
+
|
475
|
+
Like Sinatra, Angelo apps can be written in either "classic" style or
|
476
|
+
so-called "modular" style. Which style you use is more of a personal
|
477
|
+
preference than anything else.
|
478
|
+
|
479
|
+
A classic-style app requires "angelo/main" and defines the app
|
480
|
+
directly at the top level using the DSL. In addition, classic apps:
|
481
|
+
|
482
|
+
* Can use a `helpers` block to define methods that can be called from
|
483
|
+
filters and route handlers. The `helpers` method can also include methods
|
484
|
+
from one or more modules passed as arguments instead of or in addition
|
485
|
+
to taking a block.
|
486
|
+
* Parse optional command-line options "-o addr" and "-p port" to set
|
487
|
+
the bind address and listen port, respectively.
|
488
|
+
* Are run automatically.
|
489
|
+
|
490
|
+
*Note: unlike Sinatra, define a classic app by requiring "angelo/main"
|
491
|
+
and a modular app by requiring "angelo". Sinatra uses "sinatra" and
|
492
|
+
"sinatra/base" to do the same things.*
|
493
|
+
|
494
|
+
Here's a classic app:
|
495
|
+
|
496
|
+
```ruby
|
497
|
+
require 'angelo/main'
|
498
|
+
|
499
|
+
helpers do
|
500
|
+
def say_hello
|
501
|
+
"Hello"
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
get "/hello" do
|
506
|
+
"#{say_hello} to you, too."
|
507
|
+
end
|
508
|
+
```
|
509
|
+
|
510
|
+
And the same app in modular style:
|
511
|
+
|
512
|
+
```ruby
|
513
|
+
require 'angelo'
|
514
|
+
|
515
|
+
class HelloApp < Angelo::Base
|
516
|
+
def say_hello
|
517
|
+
"Hello"
|
518
|
+
end
|
519
|
+
|
520
|
+
get "/hello" do
|
521
|
+
"#{say_hello} to you, too."
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
HelloApp.run!
|
526
|
+
```
|
527
|
+
|
528
|
+
### Documentation
|
529
|
+
|
530
|
+
**I'm bad at documentation and I feel bad.**
|
531
|
+
|
532
|
+
Others have helped, and there is a YaRD plugin for Angelo [here](https://github.com/artcom/yard-angelo)
|
533
|
+
if you would like to document your apps built with Angelo. (thanks: @katjaeinsfeld, @artcom)
|
534
|
+
|
489
535
|
### WORK LEFT TO DO
|
490
536
|
|
491
537
|
Lots of work left to do!
|
@@ -494,10 +540,8 @@ Lots of work left to do!
|
|
494
540
|
|
495
541
|
```ruby
|
496
542
|
require 'angelo'
|
497
|
-
require 'angelo/mustermann'
|
498
543
|
|
499
544
|
class Foo < Angelo::Base
|
500
|
-
include Angelo::Mustermann
|
501
545
|
|
502
546
|
# just some constants to use in routes later...
|
503
547
|
#
|
data/angelo.gemspec
CHANGED
@@ -2,16 +2,19 @@
|
|
2
2
|
require File.expand_path('../lib/angelo/version', __FILE__)
|
3
3
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
|
-
gem.authors
|
6
|
-
gem.email
|
7
|
-
gem.description
|
8
|
-
gem.homepage
|
9
|
-
gem.files
|
10
|
-
gem.test_files
|
11
|
-
gem.name
|
12
|
-
gem.require_paths
|
13
|
-
gem.version
|
14
|
-
gem.license
|
5
|
+
gem.authors = ["Kenichi Nakamura"]
|
6
|
+
gem.email = ["kenichi.nakamura@gmail.com"]
|
7
|
+
gem.description = gem.summary = "A Sinatra-like DSL for Reel"
|
8
|
+
gem.homepage = "https://github.com/kenichi/angelo"
|
9
|
+
gem.files = `git ls-files | grep -Ev '^example'`.split("\n")
|
10
|
+
gem.test_files = `git ls-files -- spec/*`.split("\n")
|
11
|
+
gem.name = "angelo"
|
12
|
+
gem.require_paths = ["lib"]
|
13
|
+
gem.version = Angelo::VERSION
|
14
|
+
gem.license = 'apache'
|
15
|
+
gem.required_ruby_version = '>= 2.1.0'
|
15
16
|
gem.add_runtime_dependency 'reel', '~>0.5'
|
17
|
+
gem.add_runtime_dependency 'tilt', '~>2.0'
|
18
|
+
gem.add_runtime_dependency 'mustermann', '~>0.4'
|
16
19
|
gem.add_runtime_dependency 'mime-types', '~>2.4'
|
17
20
|
end
|
data/lib/angelo.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'reel'
|
2
2
|
require 'json'
|
3
|
+
require 'tilt'
|
4
|
+
require 'mustermann'
|
3
5
|
|
4
6
|
# require 'ruby-prof'
|
5
7
|
#
|
@@ -128,11 +130,12 @@ end
|
|
128
130
|
require 'angelo/version'
|
129
131
|
require 'angelo/params_parser'
|
130
132
|
require 'angelo/server'
|
131
|
-
require 'angelo/base'
|
132
|
-
require 'angelo/stash'
|
133
133
|
require 'angelo/responder'
|
134
134
|
require 'angelo/responder/eventsource'
|
135
135
|
require 'angelo/responder/websocket'
|
136
|
+
require 'angelo/tilt/erb'
|
137
|
+
require 'angelo/base'
|
138
|
+
require 'angelo/stash'
|
136
139
|
|
137
140
|
# trap "INT" do
|
138
141
|
# result = RubyProf.stop
|
data/lib/angelo/base.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
module Angelo
|
2
2
|
|
3
3
|
class Base
|
4
|
+
extend Forwardable
|
4
5
|
include ParamsParser
|
5
6
|
include Celluloid::Logger
|
7
|
+
include Tilt::ERB
|
8
|
+
include Mustermann
|
6
9
|
|
7
|
-
|
8
|
-
def_delegators :@responder, :content_type, :headers, :redirect, :request, :transfer_encoding
|
10
|
+
def_delegators :@responder, :content_type, :headers, :mustermann, :redirect, :request, :transfer_encoding
|
9
11
|
def_delegators :@klass, :public_dir, :report_errors?, :sse_event, :sse_message, :sses, :websockets
|
10
12
|
|
11
13
|
attr_accessor :responder
|
@@ -16,9 +18,15 @@ module Angelo
|
|
16
18
|
|
17
19
|
def inherited subclass
|
18
20
|
|
19
|
-
#
|
21
|
+
# Set app_file by groveling up the caller stack until we find
|
22
|
+
# the first caller from a directory different from __FILE__.
|
23
|
+
# This allows base.rb to be required from an arbitrarily deep
|
24
|
+
# nesting of require "angelo/<whatever>" and still set
|
25
|
+
# app_file correctly.
|
20
26
|
#
|
21
|
-
subclass.app_file =
|
27
|
+
subclass.app_file = caller_locations.map(&:absolute_path).find do |f|
|
28
|
+
!f.start_with?(File.dirname(__FILE__) + File::SEPARATOR)
|
29
|
+
end
|
22
30
|
|
23
31
|
# bring RequestError into this namespace
|
24
32
|
#
|
@@ -30,6 +38,12 @@ module Angelo
|
|
30
38
|
subclass.ping_time DEFAULT_PING_TIME
|
31
39
|
subclass.log_level DEFAULT_LOG_LEVEL
|
32
40
|
|
41
|
+
# Parse command line options if angelo/main has been required.
|
42
|
+
# They could also be parsed in run, but this makes them
|
43
|
+
# available to and overridable by the DSL.
|
44
|
+
#
|
45
|
+
subclass.parse_options(ARGV.dup) if @angelo_main
|
46
|
+
|
33
47
|
class << subclass
|
34
48
|
|
35
49
|
def root
|
@@ -39,7 +53,12 @@ module Angelo
|
|
39
53
|
end
|
40
54
|
|
41
55
|
end
|
56
|
+
end
|
42
57
|
|
58
|
+
# Methods defined in module DSL will be available in the DSL both
|
59
|
+
# in Angelo::Base subclasses and in the top level DSL.
|
60
|
+
|
61
|
+
module DSL
|
43
62
|
def addr a = nil
|
44
63
|
@addr = a if a
|
45
64
|
@addr
|
@@ -76,42 +95,25 @@ module Angelo
|
|
76
95
|
@report_errors = true
|
77
96
|
end
|
78
97
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
define_method name, &block
|
85
|
-
method = instance_method name
|
86
|
-
remove_method name
|
87
|
-
method
|
88
|
-
end
|
89
|
-
|
90
|
-
def routes
|
91
|
-
@routes ||= Hash.new{|h,k| h[k] = {}}
|
98
|
+
HTTPABLE.each do |m|
|
99
|
+
define_method m do |path, opts = {}, &block|
|
100
|
+
path = ::Mustermann.new path, opts
|
101
|
+
routes[m][path] = Responder.new &block
|
102
|
+
end
|
92
103
|
end
|
93
104
|
|
94
|
-
def
|
95
|
-
|
105
|
+
def websocket path, &block
|
106
|
+
path = ::Mustermann.new path
|
107
|
+
routes[:websocket][path] = Responder::Websocket.new &block
|
96
108
|
end
|
97
109
|
|
98
|
-
def
|
99
|
-
|
100
|
-
|
101
|
-
when String
|
102
|
-
filter_by which, opts, f
|
103
|
-
when Hash
|
104
|
-
if opts[:path]
|
105
|
-
filter_by which, opts[:path], f
|
106
|
-
else
|
107
|
-
filters[which][:default] << f
|
108
|
-
end
|
109
|
-
end
|
110
|
+
def eventsource path, headers = nil, &block
|
111
|
+
path = ::Mustermann.new path
|
112
|
+
routes[:get][path] = Responder::Eventsource.new headers, &block
|
110
113
|
end
|
111
114
|
|
112
|
-
def
|
113
|
-
|
114
|
-
filters[which][path] << meth
|
115
|
+
def task name, &block
|
116
|
+
Angelo::Server.define_task name, &block
|
115
117
|
end
|
116
118
|
|
117
119
|
def before opts = {}, &block
|
@@ -122,26 +124,47 @@ module Angelo
|
|
122
124
|
filter :after, opts, &block
|
123
125
|
end
|
124
126
|
|
125
|
-
|
126
|
-
|
127
|
-
routes[m][path] = Responder.new &block
|
128
|
-
end
|
127
|
+
def on_pong &block
|
128
|
+
Responder::Websocket.on_pong = block
|
129
129
|
end
|
130
130
|
|
131
|
-
|
132
|
-
|
131
|
+
end
|
132
|
+
|
133
|
+
# Make the DSL methods available to subclass-level code.
|
134
|
+
# main.rb makes them available to the top level.
|
135
|
+
|
136
|
+
extend DSL
|
137
|
+
|
138
|
+
class << self
|
139
|
+
def report_errors?
|
140
|
+
!!@report_errors
|
133
141
|
end
|
134
142
|
|
135
|
-
def
|
136
|
-
routes[
|
143
|
+
def routes
|
144
|
+
@routes ||= Hash.new{|h,k| h[k] = RouteMap.new}
|
137
145
|
end
|
138
146
|
|
139
|
-
def
|
140
|
-
|
147
|
+
def filters
|
148
|
+
@filters ||= {before: {default: []}, after: {default: []}}
|
141
149
|
end
|
142
150
|
|
143
|
-
def
|
144
|
-
|
151
|
+
def filter which, opts = {}, &block
|
152
|
+
case opts
|
153
|
+
when String
|
154
|
+
filter_by which, opts, block
|
155
|
+
when Hash
|
156
|
+
if opts[:path]
|
157
|
+
filter_by which, opts[:path], block
|
158
|
+
else
|
159
|
+
filters[which][:default] << block
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def filter_by which, path, block
|
165
|
+
pattern = ::Mustermann.new path
|
166
|
+
filters[which][pattern] ||= []
|
167
|
+
filters[which][pattern] << block
|
145
168
|
end
|
146
169
|
|
147
170
|
def websockets reject = true
|
@@ -215,8 +238,8 @@ module Angelo
|
|
215
238
|
when GET, DELETE, OPTIONS
|
216
239
|
parse_query_string
|
217
240
|
when POST, PUT
|
218
|
-
|
219
|
-
end
|
241
|
+
parse_query_string_and_post_body
|
242
|
+
end.merge mustermann.params(request.path)
|
220
243
|
end
|
221
244
|
|
222
245
|
def request_headers
|
@@ -319,17 +342,76 @@ module Angelo
|
|
319
342
|
Celluloid.sleep time
|
320
343
|
end
|
321
344
|
|
322
|
-
def filter which
|
323
|
-
fs = self.class.filters[which][:default]
|
324
|
-
fs += self.class.filters[which][request.path] if self.class.filters[which][request.path]
|
325
|
-
fs.each {|f| f.bind(self).call}
|
326
|
-
end
|
327
|
-
|
328
345
|
def chunked_response &block
|
329
346
|
transfer_encoding :chunked
|
330
347
|
ChunkedResponse.new &block
|
331
348
|
end
|
332
349
|
|
350
|
+
def filter which
|
351
|
+
self.class.filters[which].each do |pattern, filters|
|
352
|
+
case pattern
|
353
|
+
when :default
|
354
|
+
filters.each {|filter| instance_eval &filter}
|
355
|
+
when ::Mustermann
|
356
|
+
if pattern.match request.path
|
357
|
+
@pre_filter_params = params
|
358
|
+
@params = @pre_filter_params.merge pattern.params(request.path)
|
359
|
+
filters.each {|filter| instance_eval &filter}
|
360
|
+
@params = @pre_filter_params
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
# It seems more sensible to put this in main.rb since it's used
|
367
|
+
# only if angelo/main is required, but it's here so it can be
|
368
|
+
# tested, since requiring angelo/main doesn't play well with the
|
369
|
+
# test code.
|
370
|
+
|
371
|
+
def self.parse_options(argv)
|
372
|
+
require "optparse"
|
373
|
+
|
374
|
+
optparse = OptionParser.new do |op|
|
375
|
+
op.banner = "Usage: #{$0} [options]"
|
376
|
+
|
377
|
+
op.on('-p port', OptionParser::DecimalInteger, "set the port (default is #{port})") {|val| port val}
|
378
|
+
op.on('-o addr', "set the host (default is #{addr})") {|val| addr val}
|
379
|
+
op.on('-h', '--help', "Show this help") do
|
380
|
+
puts op
|
381
|
+
exit
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
begin
|
386
|
+
optparse.parse(argv)
|
387
|
+
rescue OptionParser::ParseError => ex
|
388
|
+
$stderr.puts ex
|
389
|
+
$stderr.puts optparse
|
390
|
+
exit 1
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
class RouteMap
|
395
|
+
|
396
|
+
def initialize
|
397
|
+
@hash = Hash.new
|
398
|
+
end
|
399
|
+
|
400
|
+
def []= route, responder
|
401
|
+
@hash[route] = responder
|
402
|
+
end
|
403
|
+
|
404
|
+
def [] route
|
405
|
+
responder = nil
|
406
|
+
if mustermann = @hash.keys.select {|k| k.match(route)}.first
|
407
|
+
responder = @hash.fetch mustermann
|
408
|
+
responder.mustermann = mustermann
|
409
|
+
end
|
410
|
+
responder
|
411
|
+
end
|
412
|
+
|
413
|
+
end
|
414
|
+
|
333
415
|
class EventSource
|
334
416
|
extend Forwardable
|
335
417
|
|