roda 3.25.0 → 3.26.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 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