roda 3.25.0 → 3.26.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
  SHA256:
3
- metadata.gz: 4e98660fac24a8bb7b455591674e9427e4fb07521384ae925888b95d7b38509c
4
- data.tar.gz: 5ca8cec4fbff75633280c63375f9b33b5eeb26f7862d05356b5c3e85f0f1a29a
3
+ metadata.gz: 441b09ae88ce4427866fe7c773ce8476f60e837b7b4477bfc5106c162e8b0a06
4
+ data.tar.gz: aca50917cc14429a77c16461352c997dff592e52cc9a44d1f4f64688b8b50693
5
5
  SHA512:
6
- metadata.gz: 1e1ac389fcaad6a03b0ea9a5b89bf895c49421f971ce3a21eacaa7a24f93e061a1318debfd5cc30f2a41c146fdfbe53bd18785e6e2cc53fed938bde8dc078efa
7
- data.tar.gz: fc26dccc7384e5619754d5f2ca4fddaf691aa57b301f1e2ea9b8de9a27224a49961ee0b162117363a7805a80860f4f3bb012d6d11ac8ca81f3b25f1505d888ef
6
+ metadata.gz: bb56cb6dbcb70fbc182d10bf161db009162d6887e9117823b9fab26554670644c3eec1c2e2977008310826fee799d1213ff8d7a85ed261e5f257ed949b9baa2b
7
+ data.tar.gz: 8d7b4ee8b669e7a3a5f3f4ac79f7d51b879cf9700e9634bd5b52b9c219c1158d0f07f03ce5aeab983bbddd3038febd996738b45047fa1e8e49c6e1dc7e07687d
data/CHANGELOG CHANGED
@@ -1,3 +1,9 @@
1
+ = 3.26.0 (2019-11-18)
2
+
3
+ * Combine multiple asset files with a newline when compiling them, avoiding corner cases with comments (ameuret) (#176)
4
+
5
+ * Add asychronous streaming support to the streaming plugin (janko) (#175)
6
+
1
7
  = 3.25.0 (2019-10-15)
2
8
 
3
9
  * Support change in tilt 2.0.10 private API to continue to support compiled templates, with up to 33% performance improvement (jeremyevans)
@@ -0,0 +1,15 @@
1
+ = New Features
2
+
3
+ * Asynchronous streaming is now supported in the streaming plugin,
4
+ using the :async option. When using this option, streaming
5
+ responses are temporarily buffered in a queue. By default, the
6
+ queue is a sized queue with a maximum of 10 elements, but the
7
+ queue can be specified manually via the :queue option, which
8
+ can be used with async libraries that support non-blocking
9
+ queues. This option is currently only supported on Ruby 2.3+.
10
+
11
+ = Other Improvements
12
+
13
+ * When combining multiple compiled assets into a single file, the
14
+ files are now separated by a newline, fixing issues when a
15
+ single line comment is used as the last line of a file.
@@ -494,7 +494,7 @@ class Roda
494
494
  file = "#{dirs.join('/')}/#{file}" if dirs && o[:group_subdirs]
495
495
  file = "#{o[:"#{type}_path"]}#{file}"
496
496
  app.read_asset_file(file, type)
497
- end.join
497
+ end.join("\n")
498
498
 
499
499
  unless o[:concat_only]
500
500
  content = compress_asset(content, type)
@@ -21,6 +21,8 @@ class Roda
21
21
  #
22
22
  # :callback :: A callback proc to call when the connection is closed.
23
23
  # :loop :: Whether to call the stream block continuously until the connection is closed.
24
+ # :async :: Whether to call the stream block in a separate thread (default: false). Only supported on Ruby 2.3+.
25
+ # :queue :: A queue object to use for asynchronous streaming (default: `SizedQueue.new(10)`).
24
26
  #
25
27
  # If the :loop option is used, you can override the
26
28
  # handle_stream_error method to change how exceptions
@@ -88,6 +90,52 @@ class Roda
88
90
  end
89
91
  end
90
92
 
93
+ # Class of the response body if you use #stream with :async set to true.
94
+ # Uses a separate thread that pushes streaming results to a queue, so that
95
+ # data can be streamed to clients while it is being prepared by the application.
96
+ class AsyncStream
97
+ include Enumerable
98
+
99
+ # Handle streaming options, see Streaming for details.
100
+ def initialize(opts=OPTS, &block)
101
+ @stream = Stream.new(opts, &block)
102
+ @queue = opts[:queue] || SizedQueue.new(10) # have some default backpressure
103
+ @thread = Thread.new { enqueue_chunks }
104
+ end
105
+
106
+ # Continue streaming data until the stream is finished.
107
+ def each(&out)
108
+ dequeue_chunks(&out)
109
+ @thread.join
110
+ end
111
+
112
+ # Stop streaming.
113
+ def close
114
+ @queue.close # terminate the producer thread
115
+ @stream.close
116
+ end
117
+
118
+ private
119
+
120
+ # Push each streaming chunk onto the queue.
121
+ def enqueue_chunks
122
+ @stream.each do |chunk|
123
+ @queue.push(chunk)
124
+ end
125
+ rescue ClosedQueueError
126
+ # connection was closed
127
+ ensure
128
+ @queue.close
129
+ end
130
+
131
+ # Pop each streaming chunk from the queue and yield it.
132
+ def dequeue_chunks
133
+ while chunk = @queue.pop
134
+ yield chunk
135
+ end
136
+ end
137
+ end
138
+
91
139
  module InstanceMethods
92
140
  # Immediately return a streaming response using the current response
93
141
  # status and headers, calling the block to get the streaming response.
@@ -105,7 +153,9 @@ class Roda
105
153
  end
106
154
  end
107
155
 
108
- throw :halt, @_response.finish_with_body(Stream.new(opts, &block))
156
+ stream_class = (opts[:async] && RUBY_VERSION >= '2.3') ? AsyncStream : Stream
157
+
158
+ throw :halt, @_response.finish_with_body(stream_class.new(opts, &block))
109
159
  end
110
160
 
111
161
  # Handle exceptions raised while streaming when using :loop
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 3
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 25
7
+ RodaMinorVersion = 26
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
@@ -333,6 +333,21 @@ if run_tests
333
333
  end
334
334
 
335
335
  it 'should handle compiling assets, linking to them, and accepting requests for them' do
336
+ app.plugin :assets, :js=>{:head => %w'comment_1.js comment_2.js'}
337
+ app.compile_assets
338
+ html = body('/test')
339
+ html.scan(/<script/).length.must_equal 1
340
+ html =~ %r{src="(/assets/app\.head\.[a-f0-9]{64}\.js)"}
341
+ js = body($1)
342
+ js.must_equal <<END
343
+ // test
344
+ /*
345
+ a = 1;
346
+ */
347
+ END
348
+ end
349
+
350
+ it 'should separate compiled assets with new lines' do
336
351
  app.compile_assets
337
352
  html = body('/test')
338
353
  html.scan(/<link/).length.must_equal 1
@@ -130,4 +130,117 @@ describe "streaming plugin" do
130
130
  b.each{|v| a << v}
131
131
  a.must_equal %w'a 1 b 1 c 1 e'
132
132
  end
133
+
134
+ describe "with :async" do
135
+ it "should stream in a thread" do
136
+ main_thread = Thread.current
137
+ minitest = self
138
+ app(:streaming) do |r|
139
+ stream(:async=>true) do |out|
140
+ minitest.refute_equal Thread.current, main_thread
141
+ %w'a b c'.each do |v|
142
+ out << v
143
+ end
144
+ end
145
+ end
146
+
147
+ s, h, b = req
148
+ s.must_equal 200
149
+ h.must_equal('Content-Type'=>'text/html')
150
+ b.to_a.must_equal %w'a b c'
151
+ end
152
+
153
+ it "should propagate exceptions" do
154
+ app(:streaming) do |r|
155
+ stream(:async=>true) do |out|
156
+ Thread.current.report_on_exception = false if Thread.current.respond_to?(:report_on_exception=)
157
+ %w'a b'.each{|v| out << v}
158
+ raise Roda::RodaError, 'foo'
159
+ out << 'c'
160
+ end
161
+ end
162
+
163
+ s, h, b = req
164
+ s.must_equal 200
165
+ h.must_equal('Content-Type'=>'text/html')
166
+ a = []
167
+ proc{b.each{|v| a << v}}.must_raise(Roda::RodaError)
168
+ a.must_equal %w'a b'
169
+ end
170
+
171
+ it "should terminate the thread on close" do
172
+ q = Queue.new
173
+ app(:streaming) do |r|
174
+ stream(:async=>true) do |out|
175
+ %w'a b c d e f g h i j'.each{|v| out << v}
176
+ q.deq
177
+ out << 'k'
178
+ end
179
+ end
180
+
181
+ *, b = req
182
+ e = b.enum_for(:each)
183
+ 10.times{e.next}
184
+ b.close
185
+ q.enq 'x'
186
+ proc{e.next}.must_raise(StopIteration)
187
+ end
188
+
189
+ it "should still run callbacks on close" do
190
+ callback = false
191
+ app(:streaming) do |r|
192
+ stream(:async=>true, :callback=>proc{callback = true}) do |out|
193
+ %w'a b c'.each{|v| out << v}
194
+ end
195
+ end
196
+
197
+ *, b = req
198
+ b.close
199
+ callback.must_equal true
200
+ end
201
+
202
+ it "should apply backpressure by default" do
203
+ q = Queue.new
204
+ a = []
205
+ app(:streaming) do |r|
206
+ stream(:async=>true) do |out|
207
+ %w'a b c d e f g h i j'.each do |v|
208
+ out << v
209
+ a << v
210
+ end
211
+
212
+ q.enq 'x'
213
+
214
+ out << 'k'
215
+ a << 'k'
216
+ end
217
+ end
218
+
219
+ *, b = req
220
+ q.deq
221
+ a.must_equal %w'a b c d e f g h i j'
222
+ end
223
+
224
+ it "should handle :queue option to override queue" do
225
+ q = Queue.new
226
+ a = []
227
+ app(:streaming) do |r|
228
+ stream(:async=>true, queue: SizedQueue.new(5)) do |out|
229
+ %w'a b c d e'.each do |v|
230
+ out << v
231
+ a << v
232
+ end
233
+
234
+ q.enq 'x'
235
+
236
+ out << 'f'
237
+ a << 'f'
238
+ end
239
+ end
240
+
241
+ *, b = req
242
+ q.deq
243
+ a.must_equal %w'a b c d e'
244
+ end
245
+ end if RUBY_VERSION >= '2.3'
133
246
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.25.0
4
+ version: 3.26.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-15 00:00:00.000000000 Z
11
+ date: 2019-11-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -236,6 +236,7 @@ extra_rdoc_files:
236
236
  - doc/release_notes/3.23.0.txt
237
237
  - doc/release_notes/3.24.0.txt
238
238
  - doc/release_notes/3.25.0.txt
239
+ - doc/release_notes/3.26.0.txt
239
240
  files:
240
241
  - CHANGELOG
241
242
  - MIT-LICENSE
@@ -297,6 +298,7 @@ files:
297
298
  - doc/release_notes/3.23.0.txt
298
299
  - doc/release_notes/3.24.0.txt
299
300
  - doc/release_notes/3.25.0.txt
301
+ - doc/release_notes/3.26.0.txt
300
302
  - doc/release_notes/3.3.0.txt
301
303
  - doc/release_notes/3.4.0.txt
302
304
  - doc/release_notes/3.5.0.txt