angelo 0.3.3 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/kenichi/angelo.png?branch=master)](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
|
|