angelo 0.1.24 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +108 -59
- data/lib/angelo.rb +1 -0
- data/lib/angelo/base.rb +39 -2
- data/lib/angelo/minitest/helpers.rb +12 -2
- data/lib/angelo/responder.rb +39 -3
- data/lib/angelo/responder/eventsource.rb +1 -0
- data/lib/angelo/version.rb +1 -1
- data/test/angelo/eventsource_spec.rb +5 -5
- data/test/angelo_spec.rb +56 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 71b7b6db8f9dc6f54045817b05e45895e9a30158
|
4
|
+
data.tar.gz: 0f7c1f1b602476811076b98df9829f15476819f1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 96b4ed9df8cff7a12a62bbd9e5fcbd7b9ffc912587d094f85beb0dbdf1c59c1e7f0501d8dfdab4f66a960845570731c6582739ce23689a5075d595ceb2db88ea
|
7
|
+
data.tar.gz: 3ebb1e69058a7733e05a5f7f990514a3ebdd001382f20129e7e3862404abc242617fc8cc1d6ee8f0111fc5ecb1f92a8c808c9c1d5af3d0b98e628e8f2927458f
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
changelog
|
2
2
|
=========
|
3
3
|
|
4
|
+
### 0.2.0 23 sep 2014
|
5
|
+
|
6
|
+
* chunked responses with `transfer_encoding :chunked` and `return_obj.each`
|
7
|
+
* chunked responses with `chunked_response(){|r| ... }`
|
8
|
+
* `event` and `message` helpers on sse objects
|
9
|
+
|
4
10
|
### 0.1.24 19 sep 2014
|
5
11
|
|
6
12
|
thanks: @chewi
|
data/README.md
CHANGED
@@ -26,16 +26,21 @@ Celluloid::IO and gives you a reactor with evented IO in Ruby!**
|
|
26
26
|
Note: There currently is no "standalone" capability where one can define route handlers at the top level.
|
27
27
|
|
28
28
|
Things will feel very familiar to anyone experienced with Sinatra. Inside the subclass, you can define
|
29
|
-
route handlers denoted by HTTP verb and path.
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
after blocks are handled. See the Errors section below for more info.
|
29
|
+
route handlers denoted by HTTP verb and path. One acceptable return value from a route block is the body
|
30
|
+
of the response in full as a `String`. Another is a `Hash` if the content type is set to `:json`. Finally,
|
31
|
+
you may return any object that responds to `#each(&block)` if the transfer encoding is set to `:chunked`.
|
32
|
+
There is also a `chunked_response` helper that will take a block, set the transfer encoding, and return
|
33
|
+
an appropriate object for you.
|
35
34
|
|
36
35
|
There is also [Mustermann](#mustermann) support for full-on, Sinatra-like path
|
37
36
|
matching and params.
|
38
37
|
|
38
|
+
Angelo also features `before` and `after` blocks, just like Sinatra. Filters are ordered as defined,
|
39
|
+
and called in that order. When defined without a path, they run for all matched requests. With a path,
|
40
|
+
the path must match exactly for the block to be called. If `Angelo::Mustermann` is included, the paths
|
41
|
+
are interpreted as a Mustermann pattern and params are merged. For more info on the difference in how
|
42
|
+
after blocks are handled, see the Errors section below for more info.
|
43
|
+
|
39
44
|
### Websockets!
|
40
45
|
|
41
46
|
One of the main motivations for Angelo was the ability to define websocket handlers with ease. Through
|
@@ -48,7 +53,7 @@ The `websocket` route builder accepts a path and a block, and passes the actual
|
|
48
53
|
as the only argument. This socket is an instance of Reel's
|
49
54
|
[WebSocket](https://github.com/celluloid/reel/blob/master/lib/reel/websocket.rb) class, and, as such,
|
50
55
|
responds to methods like `on_message` and `on_close`. A service-wide `on_pong` handler (defined at the
|
51
|
-
|
56
|
+
|
52
57
|
a connected websocket client.
|
53
58
|
|
54
59
|
##### `websockets` helper
|
@@ -130,14 +135,23 @@ sse.addEventListener('foo', function(e){ console.log("got foo event!\n" + JSON.p
|
|
130
135
|
The `sse_event` helper accepts a normal `String` for the data, but will automatically convert a `Hash`
|
131
136
|
argument to a JSON object.
|
132
137
|
|
138
|
+
NOTE: there is a shortcut helper on the actual SSE object itself:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
eventsource '/sse' do |sse|
|
142
|
+
sse.event :foo, some_key: 'blah', other_key: 'boo'
|
143
|
+
sse.event :close
|
144
|
+
end
|
145
|
+
```
|
146
|
+
|
133
147
|
##### `sse_message` helper
|
134
148
|
|
135
149
|
The `sse_message` helper behaves exactly the same as `sse_event`, but does not take an event name:
|
136
150
|
|
137
151
|
```ruby
|
138
152
|
eventsource '/sse' do |s|
|
139
|
-
msg =
|
140
|
-
s.write msg
|
153
|
+
msg = sse_message some_key: 'blah', other_key: 'boo'
|
154
|
+
s.write msg
|
141
155
|
s.close
|
142
156
|
end
|
143
157
|
```
|
@@ -149,6 +163,15 @@ var sse = new EventSource('/sse');
|
|
149
163
|
sse.onmessage = function(e){ console.log("got message!\n" + JSON.parse(e.data)); };
|
150
164
|
```
|
151
165
|
|
166
|
+
NOTE: there is a shortcut helper on the actual SSE object itself:
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
eventsource '/sse' do |sse|
|
170
|
+
sse.message some_key: 'blah', other_key: 'boo'
|
171
|
+
sse.event :close
|
172
|
+
end
|
173
|
+
```
|
174
|
+
|
152
175
|
##### `sses` helper
|
153
176
|
|
154
177
|
Angelo also includes a "stash" helper for SSE connections. One can `<<` a socket into `sses` from
|
@@ -355,6 +378,67 @@ Content-Length: 18
|
|
355
378
|
everything's fine
|
356
379
|
```
|
357
380
|
|
381
|
+
### [Tilt](https://github.com/rtomayko/tilt) / ERB
|
382
|
+
|
383
|
+
To make `erb` available in route blocks
|
384
|
+
|
385
|
+
1. add `tilt` to your `Gemfile`: `gem 'tilt'`
|
386
|
+
2. require `angelo/tilt/erb`
|
387
|
+
3. include `Angelo::Tilt::ERB` in your app
|
388
|
+
|
389
|
+
```ruby
|
390
|
+
class Foo < Angelo::Base
|
391
|
+
include Angelo::Tilt::ERB
|
392
|
+
|
393
|
+
@@views = 'some/other/path' # defaults to './views'
|
394
|
+
|
395
|
+
get '/' do
|
396
|
+
erb :index
|
397
|
+
end
|
398
|
+
|
399
|
+
end
|
400
|
+
```
|
401
|
+
|
402
|
+
### [Mustermann](https://github.com/rkh/mustermann)
|
403
|
+
|
404
|
+
To make routes blocks match path with Mustermann patterns
|
405
|
+
|
406
|
+
1. be using ruby >=2.0.0
|
407
|
+
2. add 'mustermann' to to your `Gemfile`: `platform(:ruby_20){ gem 'mustermann' }`
|
408
|
+
3. require `angelo/mustermann`
|
409
|
+
4. include `Angelo::Mustermann` in your app
|
410
|
+
|
411
|
+
```ruby
|
412
|
+
class Foo < Angelo::Base
|
413
|
+
include Angelo::Tilt::ERB
|
414
|
+
include Angelo::Mustermann
|
415
|
+
|
416
|
+
get '/:foo/things/:bar' do
|
417
|
+
|
418
|
+
# `params` is merged with the Mustermann object#params hash, so
|
419
|
+
# a "GET /some/things/are_good?foo=other&bar=are_bad" would have:
|
420
|
+
# params: {
|
421
|
+
# 'foo' => 'some',
|
422
|
+
# 'bar' => 'are_good'
|
423
|
+
# }
|
424
|
+
|
425
|
+
@foo = params[:foo]
|
426
|
+
@bar = params[:bar]
|
427
|
+
erb :index
|
428
|
+
end
|
429
|
+
|
430
|
+
before '/:fu/things/*' do
|
431
|
+
|
432
|
+
# `params` is merged with the Mustermann object#params hash, as
|
433
|
+
# parsed with the current request path against this before block's
|
434
|
+
# pattern. in the route handler, `params[:fu]` is no longer available.
|
435
|
+
|
436
|
+
@fu = params[:fu]
|
437
|
+
end
|
438
|
+
|
439
|
+
end
|
440
|
+
```
|
441
|
+
|
358
442
|
### WORK LEFT TO DO
|
359
443
|
|
360
444
|
Lots of work left to do!
|
@@ -466,61 +550,26 @@ class Foo < Angelo::Base
|
|
466
550
|
msg
|
467
551
|
end
|
468
552
|
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
### [Tilt](https://github.com/rtomayko/tilt) / ERB
|
475
|
-
|
476
|
-
To make `erb` available in route blocks
|
477
|
-
|
478
|
-
1. add `tilt` to your `Gemfile`: `gem 'tilt'`
|
479
|
-
2. require `angelo/tilt/erb`
|
480
|
-
3. include `Angelo::Tilt::ERB` in your app
|
481
|
-
|
482
|
-
```ruby
|
483
|
-
class Foo < Angelo::Base
|
484
|
-
include Angelo::Tilt::ERB
|
485
|
-
|
486
|
-
@@views = 'some/other/path' # defaults to './views'
|
553
|
+
# return a chunked response of JSON for 5 seconds
|
554
|
+
#
|
555
|
+
get '/chunky_json' do
|
556
|
+
content_type :json
|
487
557
|
|
488
|
-
|
489
|
-
|
558
|
+
# this helper requires a block that takes one arg, the response
|
559
|
+
# proc to call with each chunk (i.e. the block that is passed to
|
560
|
+
# `#each`)
|
561
|
+
#
|
562
|
+
chunked_response do |response|
|
563
|
+
5.times do
|
564
|
+
response.call time: Time.now.to_i
|
565
|
+
sleep 1
|
566
|
+
end
|
567
|
+
end
|
490
568
|
end
|
491
569
|
|
492
570
|
end
|
493
|
-
```
|
494
571
|
|
495
|
-
|
496
|
-
|
497
|
-
To make routes blocks match path with Mustermann patterns
|
498
|
-
|
499
|
-
1. be using ruby >=2.0.0
|
500
|
-
2. add 'mustermann' to to your `Gemfile`: `platform(:ruby_20){ gem 'mustermann' }`
|
501
|
-
3. require `angelo/mustermann`
|
502
|
-
4. include `Angelo::Mustermann` in your app
|
503
|
-
|
504
|
-
```ruby
|
505
|
-
class Foo < Angelo::Base
|
506
|
-
include Angelo::Tilt::ERB
|
507
|
-
include Angelo::Mustermann
|
508
|
-
|
509
|
-
get '/:foo/things/:bar' do
|
510
|
-
|
511
|
-
# `params` is merged with the Mustermann object#params hash, so
|
512
|
-
# a "GET /some/things/are_good?foo=other&bar=are_bad" would have:
|
513
|
-
# params: {
|
514
|
-
# 'foo' => 'some',
|
515
|
-
# 'bar' => 'are_good'
|
516
|
-
# }
|
517
|
-
|
518
|
-
@foo = params[:foo]
|
519
|
-
@bar = params[:bar]
|
520
|
-
erb :index
|
521
|
-
end
|
522
|
-
|
523
|
-
end
|
572
|
+
Foo.run
|
524
573
|
```
|
525
574
|
|
526
575
|
### Contributing
|
data/lib/angelo.rb
CHANGED
data/lib/angelo/base.rb
CHANGED
@@ -5,7 +5,7 @@ module Angelo
|
|
5
5
|
include Celluloid::Logger
|
6
6
|
|
7
7
|
extend Forwardable
|
8
|
-
def_delegators :@responder, :content_type, :headers, :redirect, :request
|
8
|
+
def_delegators :@responder, :content_type, :headers, :redirect, :request, :transfer_encoding
|
9
9
|
def_delegators :@klass, :websockets, :sses, :sse_event, :sse_message
|
10
10
|
|
11
11
|
@@addr = DEFAULT_ADDR
|
@@ -287,7 +287,7 @@ module Angelo
|
|
287
287
|
|
288
288
|
def eventsource &block
|
289
289
|
headers SSE_HEADER
|
290
|
-
async :handle_event_source, responder.connection.detach.socket, block
|
290
|
+
async :handle_event_source, EventSource.new(responder.connection.detach.socket), block
|
291
291
|
halt 200, :sse
|
292
292
|
end
|
293
293
|
|
@@ -305,6 +305,43 @@ module Angelo
|
|
305
305
|
fs.each {|f| f.bind(self).call}
|
306
306
|
end
|
307
307
|
|
308
|
+
def chunked_response &block
|
309
|
+
transfer_encoding :chunked
|
310
|
+
ChunkedResponse.new &block
|
311
|
+
end
|
312
|
+
|
313
|
+
class EventSource
|
314
|
+
extend Forwardable
|
315
|
+
|
316
|
+
def_delegators :@socket, :close, :closed?, :<<, :write
|
317
|
+
attr_reader :socket
|
318
|
+
|
319
|
+
def initialize socket
|
320
|
+
@socket = socket
|
321
|
+
end
|
322
|
+
|
323
|
+
def event name, data = nil
|
324
|
+
@socket.write Base.sse_event(name, data)
|
325
|
+
end
|
326
|
+
|
327
|
+
def message data
|
328
|
+
@socket.write Base.sse_message(data)
|
329
|
+
end
|
330
|
+
|
331
|
+
end
|
332
|
+
|
333
|
+
class ChunkedResponse
|
334
|
+
|
335
|
+
def initialize &block
|
336
|
+
@chunker = block
|
337
|
+
end
|
338
|
+
|
339
|
+
def each &block
|
340
|
+
@chunker[block]
|
341
|
+
end
|
342
|
+
|
343
|
+
end
|
344
|
+
|
308
345
|
end
|
309
346
|
|
310
347
|
end
|
@@ -38,7 +38,11 @@ module Angelo
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def hc_req method, path, params = {}, headers = {}
|
41
|
-
@last_response =
|
41
|
+
@last_response = if block_given?
|
42
|
+
hc.__send__ method, url(path), params, headers, &Proc.new
|
43
|
+
else
|
44
|
+
hc.__send__ method, url(path), params, headers
|
45
|
+
end
|
42
46
|
end
|
43
47
|
private :hc_req
|
44
48
|
|
@@ -64,8 +68,14 @@ module Angelo
|
|
64
68
|
|
65
69
|
[:get, :post, :put, :delete, :options, :head].each do |m|
|
66
70
|
define_method m do |path, params = {}, headers = {}|
|
71
|
+
|
67
72
|
# http_req m, path, params, headers
|
68
|
-
|
73
|
+
|
74
|
+
if block_given?
|
75
|
+
hc_req m, path, params, headers, &Proc.new
|
76
|
+
else
|
77
|
+
hc_req m, path, params, headers
|
78
|
+
end
|
69
79
|
end
|
70
80
|
end
|
71
81
|
|
data/lib/angelo/responder.rb
CHANGED
@@ -117,6 +117,19 @@ module Angelo
|
|
117
117
|
end
|
118
118
|
end
|
119
119
|
|
120
|
+
def transfer_encoding *encodings
|
121
|
+
encodings.flatten.each do |encoding|
|
122
|
+
case encoding
|
123
|
+
when :chunked
|
124
|
+
@chunked = true
|
125
|
+
headers transfer_encoding: :chunked
|
126
|
+
# when :compress, :deflate, :gzip, :identity
|
127
|
+
else
|
128
|
+
raise ArgumentError.new "invalid transfer_conding: #{encoding}"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
120
133
|
def respond_with? type
|
121
134
|
case headers[CONTENT_TYPE_HEADER_KEY]
|
122
135
|
when JSON_TYPE
|
@@ -147,14 +160,37 @@ module Angelo
|
|
147
160
|
|
148
161
|
when NilClass
|
149
162
|
@body = EMPTY_STRING
|
163
|
+
|
164
|
+
else
|
165
|
+
unless @chunked and @body.respond_to? :each
|
166
|
+
raise RequestError.new "what is this? #{@body}"
|
167
|
+
end
|
150
168
|
end
|
151
169
|
|
152
170
|
status ||= @redirect.nil? ? :ok : :moved_permanently
|
153
171
|
headers LOCATION_HEADER_KEY => @redirect if @redirect
|
154
|
-
size = @body.nil? ? 0 : @body.size
|
155
172
|
|
156
|
-
|
157
|
-
|
173
|
+
if @chunked
|
174
|
+
Angelo.log @connection, @request, nil, status
|
175
|
+
@request.respond status, headers
|
176
|
+
err = nil
|
177
|
+
begin
|
178
|
+
@body.each do |r|
|
179
|
+
r = r.to_json + NEWLINE if respond_with? :json
|
180
|
+
@request << r
|
181
|
+
end
|
182
|
+
rescue => e
|
183
|
+
err = e
|
184
|
+
ensure
|
185
|
+
@request.finish_response
|
186
|
+
raise err if err
|
187
|
+
end
|
188
|
+
else
|
189
|
+
size = @body.nil? ? 0 : @body.size
|
190
|
+
Angelo.log @connection, @request, nil, status, size
|
191
|
+
@request.respond status, headers, @body
|
192
|
+
end
|
193
|
+
|
158
194
|
rescue => e
|
159
195
|
handle_error e, :internal_server_error
|
160
196
|
end
|
data/lib/angelo/version.rb
CHANGED
@@ -9,17 +9,17 @@ describe Angelo::Responder::Eventsource do
|
|
9
9
|
define_app do
|
10
10
|
|
11
11
|
eventsource '/msg' do |c|
|
12
|
-
c.
|
12
|
+
c.message 'hi'
|
13
13
|
c.close
|
14
14
|
end
|
15
15
|
|
16
16
|
eventsource '/event' do |c|
|
17
|
-
c.
|
17
|
+
c.event :sse, 'bye'
|
18
18
|
c.close
|
19
19
|
end
|
20
20
|
|
21
21
|
eventsource '/headers', foo: 'bar' do |c|
|
22
|
-
c.
|
22
|
+
c.event :sse, 'headers'
|
23
23
|
c.close
|
24
24
|
end
|
25
25
|
|
@@ -59,12 +59,12 @@ describe Angelo::Responder::Eventsource do
|
|
59
59
|
end
|
60
60
|
|
61
61
|
eventsource '/msg' do |c|
|
62
|
-
c.
|
62
|
+
c.message 'hi'
|
63
63
|
c.close
|
64
64
|
end
|
65
65
|
|
66
66
|
eventsource '/other' do |c|
|
67
|
-
c.
|
67
|
+
c.message 'other'
|
68
68
|
c.close
|
69
69
|
end
|
70
70
|
|
data/test/angelo_spec.rb
CHANGED
@@ -379,4 +379,60 @@ describe Angelo::Base do
|
|
379
379
|
|
380
380
|
end
|
381
381
|
|
382
|
+
describe 'chunked responses' do
|
383
|
+
|
384
|
+
define_app do
|
385
|
+
|
386
|
+
get '/chunk' do
|
387
|
+
chunked_response do |r|
|
388
|
+
5.times {|n| r[n + "\n"]}
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
get '/chunk.json' do
|
393
|
+
content_type :json
|
394
|
+
chunked_response do |r|
|
395
|
+
5.times {|n| r[{n: n}]}
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
get '/chunk_each' do
|
400
|
+
transfer_encoding :chunked
|
401
|
+
Object.new.tap do |o|
|
402
|
+
def o.each; 5.times {|n| yield n + "\n"}; end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
end
|
407
|
+
|
408
|
+
it 'chunks responses with helper' do
|
409
|
+
i = 0
|
410
|
+
get '/chunk' do |c|
|
411
|
+
c.must_equal i.to_s
|
412
|
+
i += 1
|
413
|
+
end
|
414
|
+
last_response_must_be_html
|
415
|
+
end
|
416
|
+
|
417
|
+
it 'chunks responses with helper in json' do
|
418
|
+
i = 0
|
419
|
+
get '/chunk.json' do |c|
|
420
|
+
JSON.parse(c)['n'].must_equal i
|
421
|
+
i += 1
|
422
|
+
end
|
423
|
+
last_response.status.must_equal 200
|
424
|
+
last_response.headers['Content-Type'].split(';').must_include Angelo::JSON_TYPE
|
425
|
+
end
|
426
|
+
|
427
|
+
it 'chunks responses with any object that responds_to? :each' do
|
428
|
+
i = 0
|
429
|
+
get '/chunk_each' do |c|
|
430
|
+
c.must_equal i.to_s
|
431
|
+
i += 1
|
432
|
+
end
|
433
|
+
last_response_must_be_html
|
434
|
+
end
|
435
|
+
|
436
|
+
end
|
437
|
+
|
382
438
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: angelo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kenichi Nakamura
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-09-
|
11
|
+
date: 2014-09-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: reel
|