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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8706df3687ca6b50dce03271b1b67a0920304bb5
4
- data.tar.gz: 1602e019534f5e051a238fc925691c65d7ad0676
3
+ metadata.gz: 71b7b6db8f9dc6f54045817b05e45895e9a30158
4
+ data.tar.gz: 0f7c1f1b602476811076b98df9829f15476819f1
5
5
  SHA512:
6
- metadata.gz: 13c04846c759e13e2bc13801b3e73c65e84c5669c58080403710de7727965b9ba61472d57cffcf45e5b42e3aaaafa0ed1a7431d2971a3996620047ef05574e0a
7
- data.tar.gz: bec89a7501d6b2726ad865b3e93834a9dae956a466114fe5736bca1bbb88534e415d7da726684fdebe68b0941cda603e7a4676fb3d9f97218477812275fcbf68
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. Unlike Sinatra, the only acceptable return value from a
30
- route block is the body of the response in full. Chunked response support was recently added to Reel,
31
- and look for that support in Angelo soon.
32
-
33
- Angelo also features `before` and `after` blocks, just like Sinatra. The one difference lies in how
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
- class-level of the Angelo app) is available to customize the behavior when a pong frame comes back from
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 = sse_msg some_key: 'blah', other_key: 'boo'
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 &gt;=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
- end
470
-
471
- Foo.run
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
- get '/' do
489
- erb :index
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
- ### [Mustermann](https://github.com/rkh/mustermann)
496
-
497
- To make routes blocks match path with Mustermann patterns
498
-
499
- 1. be using ruby &gt;=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
@@ -55,6 +55,7 @@ module Angelo
55
55
  UNDERSCORE = '_'
56
56
  DASH = '-'
57
57
  EMPTY_STRING = ''
58
+ NEWLINE = "\n"
58
59
 
59
60
  HALT_STRUCT = Struct.new :status, :body
60
61
 
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 = hc.__send__ method, url(path), params, headers
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
- hc_req m, path, params, headers
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
 
@@ -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
- Angelo.log @connection, @request, nil, status, size
157
- @request.respond status, headers, @body
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
@@ -32,6 +32,7 @@ module Angelo
32
32
  rescue IOError => ioe
33
33
  warn "#{ioe.class} - #{ioe.message}"
34
34
  rescue RequestError => re
35
+ headers SSE_HEADER
35
36
  handle_error re, re.type
36
37
  rescue => e
37
38
  handle_error e
@@ -1,3 +1,3 @@
1
1
  module Angelo
2
- VERSION = '0.1.24'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -9,17 +9,17 @@ describe Angelo::Responder::Eventsource do
9
9
  define_app do
10
10
 
11
11
  eventsource '/msg' do |c|
12
- c.write sse_message 'hi'
12
+ c.message 'hi'
13
13
  c.close
14
14
  end
15
15
 
16
16
  eventsource '/event' do |c|
17
- c.write sse_event :sse, 'bye'
17
+ c.event :sse, 'bye'
18
18
  c.close
19
19
  end
20
20
 
21
21
  eventsource '/headers', foo: 'bar' do |c|
22
- c.write sse_event :sse, 'headers'
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.write sse_message 'hi'
62
+ c.message 'hi'
63
63
  c.close
64
64
  end
65
65
 
66
66
  eventsource '/other' do |c|
67
- c.write sse_message 'other'
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.1.24
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-19 00:00:00.000000000 Z
11
+ date: 2014-09-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: reel