mieps_http-2 0.8.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 +7 -0
- data/.autotest +20 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +17 -0
- data/.gitmodules +3 -0
- data/.rspec +4 -0
- data/.rubocop.yml +18 -0
- data/.rubocop_todo.yml +46 -0
- data/.travis.yml +13 -0
- data/Gemfile +18 -0
- data/README.md +285 -0
- data/Rakefile +11 -0
- data/example/README.md +40 -0
- data/example/client.rb +117 -0
- data/example/helper.rb +19 -0
- data/example/keys/mycert.pem +23 -0
- data/example/keys/mykey.pem +27 -0
- data/example/server.rb +97 -0
- data/example/upgrade_server.rb +193 -0
- data/http-2.gemspec +23 -0
- data/lib/http/2/buffer.rb +34 -0
- data/lib/http/2/client.rb +51 -0
- data/lib/http/2/compressor.rb +557 -0
- data/lib/http/2/connection.rb +654 -0
- data/lib/http/2/emitter.rb +45 -0
- data/lib/http/2/error.rb +44 -0
- data/lib/http/2/flow_buffer.rb +67 -0
- data/lib/http/2/framer.rb +440 -0
- data/lib/http/2/huffman.rb +323 -0
- data/lib/http/2/huffman_statemachine.rb +272 -0
- data/lib/http/2/server.rb +132 -0
- data/lib/http/2/stream.rb +576 -0
- data/lib/http/2/version.rb +3 -0
- data/lib/http/2.rb +13 -0
- data/lib/tasks/generate_huffman_table.rb +166 -0
- data/spec/buffer_spec.rb +21 -0
- data/spec/client_spec.rb +92 -0
- data/spec/compressor_spec.rb +535 -0
- data/spec/connection_spec.rb +581 -0
- data/spec/emitter_spec.rb +54 -0
- data/spec/framer_spec.rb +487 -0
- data/spec/helper.rb +128 -0
- data/spec/hpack_test_spec.rb +79 -0
- data/spec/huffman_spec.rb +68 -0
- data/spec/server_spec.rb +51 -0
- data/spec/stream_spec.rb +794 -0
- metadata +116 -0
data/spec/stream_spec.rb
ADDED
@@ -0,0 +1,794 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
RSpec.describe HTTP2::Stream do
|
4
|
+
before(:each) do
|
5
|
+
@client = Client.new
|
6
|
+
@stream = @client.new_stream
|
7
|
+
end
|
8
|
+
|
9
|
+
context 'stream states' do
|
10
|
+
it 'should initiliaze all streams to IDLE' do
|
11
|
+
expect(@stream.state).to eq :idle
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should set custom stream priority' do
|
15
|
+
stream = @client.new_stream(weight: 3, dependency: 2, exclusive: true)
|
16
|
+
expect(stream.weight).to eq 3
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'idle' do
|
20
|
+
it 'should transition to open on sent HEADERS' do
|
21
|
+
@stream.send HEADERS.deep_dup
|
22
|
+
expect(@stream.state).to eq :open
|
23
|
+
end
|
24
|
+
it 'should transition to open on received HEADERS' do
|
25
|
+
@stream.receive HEADERS
|
26
|
+
expect(@stream.state).to eq :open
|
27
|
+
end
|
28
|
+
it 'should transition to reserved (local) on sent PUSH_PROMISE' do
|
29
|
+
@stream.send PUSH_PROMISE.deep_dup
|
30
|
+
expect(@stream.state).to eq :reserved_local
|
31
|
+
end
|
32
|
+
it 'should transition to reserved (remote) on received PUSH_PROMISE' do
|
33
|
+
@stream.receive PUSH_PROMISE
|
34
|
+
expect(@stream.state).to eq :reserved_remote
|
35
|
+
end
|
36
|
+
it 'should reprioritize stream on sent PRIORITY' do
|
37
|
+
expect { @stream.send PRIORITY.dup }.to_not raise_error
|
38
|
+
expect(@stream.weight).to eq 20
|
39
|
+
end
|
40
|
+
it 'should reprioritize stream on received PRIORITY' do
|
41
|
+
expect { @stream.send PRIORITY.dup }.to_not raise_error
|
42
|
+
expect(@stream.weight).to eq 20
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'reserved (local)' do
|
47
|
+
before(:each) { @stream.send PUSH_PROMISE.deep_dup }
|
48
|
+
|
49
|
+
it 'should transition on sent PUSH_PROMISE' do
|
50
|
+
expect(@stream.state).to eq :reserved_local
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should allow HEADERS to be sent' do
|
54
|
+
expect { @stream.send HEADERS.deep_dup }.to_not raise_error
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should raise error if sending invalid frames' do
|
58
|
+
(FRAME_TYPES - [HEADERS, RST_STREAM]).each do |type|
|
59
|
+
expect { @stream.dup.send type }.to raise_error InternalError
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should raise error on receipt of invalid frames' do
|
64
|
+
what_types = (FRAME_TYPES - [PRIORITY, RST_STREAM, WINDOW_UPDATE])
|
65
|
+
what_types.each do |type|
|
66
|
+
expect { @stream.dup.receive type }.to raise_error InternalError
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should transition to half closed (remote) on sent HEADERS' do
|
71
|
+
@stream.send HEADERS.deep_dup
|
72
|
+
expect(@stream.state).to eq :half_closed_remote
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should transition to closed on sent RST_STREAM' do
|
76
|
+
@stream.close
|
77
|
+
expect(@stream.state).to eq :closed
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should transition to closed on received RST_STREAM' do
|
81
|
+
@stream.receive RST_STREAM
|
82
|
+
expect(@stream.state).to eq :closed
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should reprioritize stream on PRIORITY' do
|
86
|
+
expect { @stream.receive PRIORITY }.to_not raise_error
|
87
|
+
expect(@stream.weight).to eq 20
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should increment remote_window on received WINDOW_UPDATE' do
|
91
|
+
expect { @stream.receive WINDOW_UPDATE }.to_not raise_error
|
92
|
+
expect(@stream.remote_window).to eq DEFAULT_FLOW_WINDOW + WINDOW_UPDATE[:increment]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'reserved (remote)' do
|
97
|
+
before(:each) { @stream.receive PUSH_PROMISE }
|
98
|
+
|
99
|
+
it 'should transition on received PUSH_PROMISE' do
|
100
|
+
expect(@stream.state).to eq :reserved_remote
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should raise error if sending invalid frames' do
|
104
|
+
(FRAME_TYPES - [PRIORITY, RST_STREAM, WINDOW_UPDATE]).each do |type|
|
105
|
+
expect { @stream.dup.send type }.to raise_error InternalError
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'should raise error on receipt of invalid frames' do
|
110
|
+
(FRAME_TYPES - [HEADERS, RST_STREAM]).each do |type|
|
111
|
+
expect { @stream.dup.receive type }.to raise_error InternalError
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'should transition to half closed (local) on received HEADERS' do
|
116
|
+
@stream.receive HEADERS
|
117
|
+
expect(@stream.state).to eq :half_closed_local
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'should transition to closed on sent RST_STREAM' do
|
121
|
+
@stream.close
|
122
|
+
expect(@stream.state).to eq :closed
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'should transition to closed on received RST_STREAM' do
|
126
|
+
@stream.receive RST_STREAM
|
127
|
+
expect(@stream.state).to eq :closed
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'should reprioritize stream on PRIORITY' do
|
131
|
+
expect { @stream.send PRIORITY.dup }.to_not raise_error
|
132
|
+
expect(@stream.weight).to eq 20
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'should increment local_window on sent WINDOW_UPDATE' do
|
136
|
+
expect { @stream.send WINDOW_UPDATE.dup }.to_not raise_error
|
137
|
+
expect(@stream.local_window).to eq DEFAULT_FLOW_WINDOW + WINDOW_UPDATE[:increment]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context 'open' do
|
142
|
+
before(:each) { @stream.receive HEADERS }
|
143
|
+
|
144
|
+
it 'should allow any valid frames types to be sent' do
|
145
|
+
(FRAME_TYPES - [PING, GOAWAY, SETTINGS]).each do |type|
|
146
|
+
expect { @stream.dup.send type.deep_dup }.to_not raise_error
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'should allow frames of any type to be received' do
|
151
|
+
FRAME_TYPES.each do |type|
|
152
|
+
expect { @stream.dup.receive type }.to_not raise_error
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'should transition to half closed (local) if sending END_STREAM' do
|
157
|
+
[DATA, HEADERS].each do |frame|
|
158
|
+
s, f = @stream.dup, frame.deep_dup
|
159
|
+
f[:flags] = [:end_stream]
|
160
|
+
|
161
|
+
s.send f
|
162
|
+
expect(s.state).to eq :half_closed_local
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'should transition to half closed (remote) if receiving END_STREAM' do
|
167
|
+
[DATA, HEADERS].each do |frame|
|
168
|
+
s, f = @stream.dup, frame.dup
|
169
|
+
f[:flags] = [:end_stream]
|
170
|
+
|
171
|
+
s.receive f
|
172
|
+
expect(s.state).to eq :half_closed_remote
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'should transition to half closed if remote opened with END_STREAM' do
|
177
|
+
s = @client.new_stream
|
178
|
+
hclose = HEADERS.dup
|
179
|
+
hclose[:flags] = [:end_stream]
|
180
|
+
|
181
|
+
s.receive hclose
|
182
|
+
expect(s.state).to eq :half_closed_remote
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'should transition to half closed if local opened with END_STREAM' do
|
186
|
+
s = @client.new_stream
|
187
|
+
hclose = HEADERS.deep_dup
|
188
|
+
hclose[:flags] = [:end_stream]
|
189
|
+
|
190
|
+
s.send hclose
|
191
|
+
expect(s.state).to eq :half_closed_local
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'should transition to closed if sending RST_STREAM' do
|
195
|
+
@stream.close
|
196
|
+
expect(@stream.state).to eq :closed
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'should transition to closed if receiving RST_STREAM' do
|
200
|
+
@stream.receive RST_STREAM
|
201
|
+
expect(@stream.state).to eq :closed
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'should emit :active on open transition' do
|
205
|
+
openp, openr = false, false
|
206
|
+
sp = @client.new_stream
|
207
|
+
sr = @client.new_stream
|
208
|
+
sp.on(:active) { openp = true }
|
209
|
+
sr.on(:active) { openr = true }
|
210
|
+
|
211
|
+
sp.receive HEADERS
|
212
|
+
sr.send HEADERS.deep_dup
|
213
|
+
|
214
|
+
expect(openp).to be_truthy
|
215
|
+
expect(openr).to be_truthy
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'should not emit :active on transition from open' do
|
219
|
+
order, stream = [], @client.new_stream
|
220
|
+
|
221
|
+
stream.on(:active) { order << :active }
|
222
|
+
stream.on(:half_close) { order << :half_close }
|
223
|
+
stream.on(:close) { order << :close }
|
224
|
+
|
225
|
+
req = HEADERS.deep_dup
|
226
|
+
req[:flags] = [:end_headers]
|
227
|
+
|
228
|
+
stream.send req
|
229
|
+
stream.send DATA.dup
|
230
|
+
expect(order).to eq [:active, :half_close]
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'should emit :close on close transition' do
|
234
|
+
closep, closer = false, false
|
235
|
+
sp, sr = @stream.dup, @stream.dup
|
236
|
+
|
237
|
+
sp.on(:close) { closep = true }
|
238
|
+
sr.on(:close) { closer = true }
|
239
|
+
|
240
|
+
sp.receive RST_STREAM
|
241
|
+
sr.close
|
242
|
+
|
243
|
+
expect(closep).to be_truthy
|
244
|
+
expect(closer).to be_truthy
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'should emit :close after frame is processed' do
|
248
|
+
order, stream = [], @client.new_stream
|
249
|
+
|
250
|
+
stream.on(:active) { order << :active }
|
251
|
+
stream.on(:data) { order << :data }
|
252
|
+
stream.on(:half_close) { order << :half_close }
|
253
|
+
stream.on(:close) { order << :close }
|
254
|
+
|
255
|
+
req = HEADERS.deep_dup
|
256
|
+
req[:flags] = [:end_stream, :end_headers]
|
257
|
+
|
258
|
+
stream.send req
|
259
|
+
stream.receive HEADERS
|
260
|
+
stream.receive DATA
|
261
|
+
|
262
|
+
expect(order).to eq [:active, :half_close, :data, :close]
|
263
|
+
end
|
264
|
+
|
265
|
+
it 'should emit :close with reason' do
|
266
|
+
reason = nil
|
267
|
+
@stream.on(:close) { |r| reason = r }
|
268
|
+
@stream.receive RST_STREAM
|
269
|
+
expect(reason).not_to be_nil
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'should reprioritize stream on sent PRIORITY' do
|
273
|
+
expect { @stream.send PRIORITY.dup }.to_not raise_error
|
274
|
+
expect(@stream.weight).to eq 20
|
275
|
+
end
|
276
|
+
it 'should reprioritize stream on received PRIORITY' do
|
277
|
+
expect { @stream.receive PRIORITY }.to_not raise_error
|
278
|
+
expect(@stream.weight).to eq 20
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
context 'half closed (local)' do
|
283
|
+
before(:each) { @stream.send HEADERS_END_STREAM.deep_dup }
|
284
|
+
|
285
|
+
it 'should raise error on attempt to send invalid frames' do
|
286
|
+
(FRAME_TYPES - [PRIORITY, RST_STREAM, WINDOW_UPDATE]).each do |frame|
|
287
|
+
expect { @stream.dup.send frame }.to raise_error InternalError
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
it 'should transition to closed on receipt of END_STREAM flag' do
|
292
|
+
[DATA, HEADERS, CONTINUATION].each do |frame|
|
293
|
+
s, f = @stream.dup, frame.dup
|
294
|
+
f[:flags] = [:end_stream]
|
295
|
+
|
296
|
+
s.receive f
|
297
|
+
expect(s.state).to eq :closed
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
it 'should transition to closed on receipt of RST_STREAM frame' do
|
302
|
+
@stream.receive RST_STREAM
|
303
|
+
expect(@stream.state).to eq :closed
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'should transition to closed if RST_STREAM frame is sent' do
|
307
|
+
@stream.send RST_STREAM.deep_dup
|
308
|
+
expect(@stream.state).to eq :closed
|
309
|
+
end
|
310
|
+
|
311
|
+
it 'should ignore received WINDOW_UPDATE frames' do
|
312
|
+
expect { @stream.receive WINDOW_UPDATE }.to_not raise_error
|
313
|
+
expect(@stream.state).to eq :half_closed_local
|
314
|
+
end
|
315
|
+
|
316
|
+
it 'should ignore received PRIORITY frames' do
|
317
|
+
expect { @stream.receive PRIORITY }.to_not raise_error
|
318
|
+
expect(@stream.state).to eq :half_closed_local
|
319
|
+
end
|
320
|
+
|
321
|
+
it 'should reprioritize stream on sent PRIORITY' do
|
322
|
+
expect { @stream.send PRIORITY.dup }.to_not raise_error
|
323
|
+
expect(@stream.weight).to eq 20
|
324
|
+
end
|
325
|
+
|
326
|
+
it 'should reprioritize stream (and decendants) on received PRIORITY' do
|
327
|
+
expect { @stream.receive PRIORITY }.to_not raise_error
|
328
|
+
expect(@stream.weight).to eq 20
|
329
|
+
end
|
330
|
+
|
331
|
+
it 'should increment local_window on sent WINDOW_UPDATE' do
|
332
|
+
expect { @stream.send WINDOW_UPDATE.dup }.to_not raise_error
|
333
|
+
expect(@stream.local_window).to eq DEFAULT_FLOW_WINDOW + WINDOW_UPDATE[:increment]
|
334
|
+
end
|
335
|
+
|
336
|
+
it 'should emit :half_close event on transition' do
|
337
|
+
order = []
|
338
|
+
stream = @client.new_stream
|
339
|
+
stream.on(:active) { order << :active }
|
340
|
+
stream.on(:half_close) { order << :half_close }
|
341
|
+
|
342
|
+
req = HEADERS.deep_dup
|
343
|
+
req[:flags] = [:end_stream, :end_headers]
|
344
|
+
|
345
|
+
stream.send req
|
346
|
+
expect(order).to eq [:active, :half_close]
|
347
|
+
end
|
348
|
+
|
349
|
+
it 'should emit :close event on transition to closed' do
|
350
|
+
closed = false
|
351
|
+
@stream.on(:close) { closed = true }
|
352
|
+
@stream.receive RST_STREAM
|
353
|
+
|
354
|
+
expect(@stream.state).to eq :closed
|
355
|
+
expect(closed).to be_truthy
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
context 'half closed (remote)' do
|
360
|
+
before(:each) { @stream.receive HEADERS_END_STREAM }
|
361
|
+
|
362
|
+
it 'should raise STREAM_CLOSED error on reciept of frames' do
|
363
|
+
(FRAME_TYPES - [PRIORITY, RST_STREAM, WINDOW_UPDATE]).each do |frame|
|
364
|
+
expect do
|
365
|
+
@stream.dup.receive frame
|
366
|
+
end.to raise_error(StreamClosed)
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
it 'should transition to closed if END_STREAM flag is sent' do
|
371
|
+
[DATA, HEADERS].each do |frame|
|
372
|
+
s, f = @stream.dup, frame.deep_dup
|
373
|
+
f[:flags] = [:end_stream]
|
374
|
+
|
375
|
+
s.on(:close) { expect(s.state).to eq :closed }
|
376
|
+
s.send f
|
377
|
+
expect(s.state).to eq :closed
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
it 'should transition to closed if RST_STREAM is sent' do
|
382
|
+
@stream.close
|
383
|
+
expect(@stream.state).to eq :closed
|
384
|
+
end
|
385
|
+
|
386
|
+
it 'should transition to closed on reciept of RST_STREAM frame' do
|
387
|
+
@stream.receive RST_STREAM
|
388
|
+
expect(@stream.state).to eq :closed
|
389
|
+
end
|
390
|
+
|
391
|
+
it 'should ignore sent WINDOW_UPDATE frames' do
|
392
|
+
expect { @stream.send WINDOW_UPDATE.dup }.to_not raise_error
|
393
|
+
expect(@stream.state).to eq :half_closed_remote
|
394
|
+
end
|
395
|
+
|
396
|
+
it 'should increment remote_window on received WINDOW_UPDATE' do
|
397
|
+
expect { @stream.receive WINDOW_UPDATE }.to_not raise_error
|
398
|
+
expect(@stream.remote_window).to eq DEFAULT_FLOW_WINDOW + WINDOW_UPDATE[:increment]
|
399
|
+
end
|
400
|
+
|
401
|
+
it 'should reprioritize stream on sent PRIORITY' do
|
402
|
+
expect { @stream.send PRIORITY.dup }.to_not raise_error
|
403
|
+
expect(@stream.weight).to eq 20
|
404
|
+
end
|
405
|
+
it 'should reprioritize stream on received PRIORITY' do
|
406
|
+
expect { @stream.receive PRIORITY }.to_not raise_error
|
407
|
+
expect(@stream.weight).to eq 20
|
408
|
+
end
|
409
|
+
|
410
|
+
it 'should emit :half_close event on transition' do
|
411
|
+
order = []
|
412
|
+
stream = @client.new_stream
|
413
|
+
stream.on(:active) { order << :active }
|
414
|
+
stream.on(:half_close) { order << :half_close }
|
415
|
+
|
416
|
+
req = HEADERS.dup
|
417
|
+
req[:flags] = [:end_stream, :end_headers]
|
418
|
+
|
419
|
+
stream.receive req
|
420
|
+
expect(order).to eq [:active, :half_close]
|
421
|
+
end
|
422
|
+
|
423
|
+
it 'should emit :close event on close transition' do
|
424
|
+
closed = false
|
425
|
+
@stream.on(:close) { closed = true }
|
426
|
+
@stream.close
|
427
|
+
|
428
|
+
expect(@stream.state).to eq :closed
|
429
|
+
expect(closed).to be_truthy
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
context 'closed' do
|
434
|
+
context 'remote closed stream' do
|
435
|
+
before(:each) do
|
436
|
+
@stream.send HEADERS_END_STREAM.deep_dup # half closed local
|
437
|
+
@stream.receive HEADERS_END_STREAM # closed by remote
|
438
|
+
end
|
439
|
+
|
440
|
+
it 'should raise STREAM_CLOSED on attempt to send frames' do
|
441
|
+
(FRAME_TYPES - [PRIORITY, RST_STREAM]).each do |frame|
|
442
|
+
expect do
|
443
|
+
@stream.dup.send frame
|
444
|
+
end.to raise_error(StreamClosed)
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
it 'should raise STREAM_CLOSED on receipt of frame' do
|
449
|
+
(FRAME_TYPES - [PRIORITY, RST_STREAM, WINDOW_UPDATE]).each do |frame|
|
450
|
+
expect do
|
451
|
+
@stream.dup.receive frame
|
452
|
+
end.to raise_error(StreamClosed)
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
it 'should allow PRIORITY, RST_STREAM to be sent' do
|
457
|
+
expect { @stream.send PRIORITY.dup }.to_not raise_error
|
458
|
+
expect { @stream.send RST_STREAM.dup }.to_not raise_error
|
459
|
+
end
|
460
|
+
|
461
|
+
it 'should allow PRIORITY, RST_STREAM to be received' do
|
462
|
+
expect { @stream.receive PRIORITY }.to_not raise_error
|
463
|
+
expect { @stream.receive RST_STREAM }.to_not raise_error
|
464
|
+
end
|
465
|
+
|
466
|
+
it 'should reprioritize stream on sent PRIORITY' do
|
467
|
+
expect { @stream.send PRIORITY.dup }.to_not raise_error
|
468
|
+
expect(@stream.weight).to eq 20
|
469
|
+
end
|
470
|
+
it 'should reprioritize stream on received PRIORITY' do
|
471
|
+
expect { @stream.receive PRIORITY }.to_not raise_error
|
472
|
+
expect(@stream.weight).to eq 20
|
473
|
+
end
|
474
|
+
|
475
|
+
it 'should ignore received WINDOW_UPDATE frames' do
|
476
|
+
expect { @stream.receive WINDOW_UPDATE }.to_not raise_error
|
477
|
+
expect(@stream.state).to eq :closed
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
context 'local closed via RST_STREAM frame' do
|
482
|
+
before(:each) do
|
483
|
+
@stream.send HEADERS.deep_dup # open
|
484
|
+
@stream.send RST_STREAM.deep_dup # closed by local
|
485
|
+
end
|
486
|
+
|
487
|
+
it 'should ignore received frames' do
|
488
|
+
(FRAME_TYPES - [PUSH_PROMISE]).each do |frame|
|
489
|
+
expect do
|
490
|
+
cb = []
|
491
|
+
@stream.on(:data) { cb << :data }
|
492
|
+
@stream.on(:headers) { cb << :headers }
|
493
|
+
@stream.dup.receive frame.dup
|
494
|
+
expect(cb).to be_empty
|
495
|
+
end.to_not raise_error
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
# it "should transition to reserved remote on PUSH_PROMISE" do
|
500
|
+
# An endpoint might receive a PUSH_PROMISE frame after it sends
|
501
|
+
# RST_STREAM. PUSH_PROMISE causes a stream to become "reserved".
|
502
|
+
# ...
|
503
|
+
# We're auto RST'ing PUSH streams in connection class, hence
|
504
|
+
# skipping this transition for now.
|
505
|
+
# end
|
506
|
+
end
|
507
|
+
|
508
|
+
# FIXME: Isn't this test same as "half closed (local)"?
|
509
|
+
# context "local closed via END_STREAM flag" do
|
510
|
+
# before(:each) do
|
511
|
+
# @stream.send HEADERS # open
|
512
|
+
# @stream.send DATA # contains end_stream flag
|
513
|
+
# end
|
514
|
+
|
515
|
+
# it "should ignore received frames" do
|
516
|
+
# FRAME_TYPES.each do |frame|
|
517
|
+
# expect { @stream.dup.receive frame }.to_not raise_error
|
518
|
+
# end
|
519
|
+
# end
|
520
|
+
# end
|
521
|
+
end
|
522
|
+
end # end stream states
|
523
|
+
|
524
|
+
# TODO: add test cases to ensure on(:priority) emitted after close
|
525
|
+
|
526
|
+
context 'flow control' do
|
527
|
+
it 'should initialize to default flow control window' do
|
528
|
+
expect(@stream.remote_window).to eq DEFAULT_FLOW_WINDOW
|
529
|
+
end
|
530
|
+
|
531
|
+
it 'should update window size on DATA frames only' do
|
532
|
+
@stream.send HEADERS.deep_dup # go to open
|
533
|
+
expect(@stream.remote_window).to eq DEFAULT_FLOW_WINDOW
|
534
|
+
|
535
|
+
(FRAME_TYPES - [DATA, PING, GOAWAY, SETTINGS]).each do |frame|
|
536
|
+
s = @stream.dup
|
537
|
+
s.send frame.deep_dup
|
538
|
+
expect(s.remote_window).to eq DEFAULT_FLOW_WINDOW
|
539
|
+
end
|
540
|
+
|
541
|
+
@stream.send DATA.dup
|
542
|
+
expect(@stream.remote_window).to eq DEFAULT_FLOW_WINDOW - DATA[:payload].bytesize
|
543
|
+
end
|
544
|
+
|
545
|
+
it 'should update window size on receipt of WINDOW_UPDATE' do
|
546
|
+
@stream.send HEADERS.deep_dup
|
547
|
+
@stream.send DATA.dup
|
548
|
+
@stream.receive WINDOW_UPDATE
|
549
|
+
|
550
|
+
expect(@stream.remote_window).to eq(
|
551
|
+
DEFAULT_FLOW_WINDOW - DATA[:payload].bytesize + WINDOW_UPDATE[:increment],
|
552
|
+
)
|
553
|
+
end
|
554
|
+
|
555
|
+
it 'should observe session flow control' do
|
556
|
+
settings, data = SETTINGS.dup, DATA.dup
|
557
|
+
settings[:payload] = [[:settings_initial_window_size, 1000]]
|
558
|
+
settings[:stream] = 0
|
559
|
+
|
560
|
+
framer = Framer.new
|
561
|
+
@client << framer.generate(settings)
|
562
|
+
|
563
|
+
s1 = @client.new_stream
|
564
|
+
s1.send HEADERS.deep_dup
|
565
|
+
s1.send data.merge(payload: 'x' * 900, flags: [])
|
566
|
+
expect(s1.remote_window).to eq 100
|
567
|
+
|
568
|
+
s1.send data.merge(payload: 'x' * 200)
|
569
|
+
expect(s1.remote_window).to eq 0
|
570
|
+
expect(s1.buffered_amount).to eq 100
|
571
|
+
|
572
|
+
@client << framer.generate(WINDOW_UPDATE.merge(stream: s1.id, increment: 1000))
|
573
|
+
expect(s1.buffered_amount).to eq 0
|
574
|
+
expect(s1.remote_window).to eq 900
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
578
|
+
context 'client API' do
|
579
|
+
it '.reprioritize should emit PRIORITY frame' do
|
580
|
+
expect(@stream).to receive(:send) do |frame|
|
581
|
+
expect(frame[:type]).to eq :priority
|
582
|
+
expect(frame[:weight]).to eq 30
|
583
|
+
end
|
584
|
+
|
585
|
+
@stream.reprioritize weight: 30
|
586
|
+
end
|
587
|
+
|
588
|
+
it '.reprioritize should raise error if invoked by server' do
|
589
|
+
srv = Server.new
|
590
|
+
stream = srv.new_stream
|
591
|
+
|
592
|
+
expect { stream.reprioritize(weight: 10) }.to raise_error(InternalError)
|
593
|
+
end
|
594
|
+
|
595
|
+
it '.headers should emit HEADERS frames' do
|
596
|
+
payload = {
|
597
|
+
':method' => 'GET',
|
598
|
+
':scheme' => 'http',
|
599
|
+
':host' => 'www.example.org',
|
600
|
+
':path' => '/resource',
|
601
|
+
'custom' => 'value',
|
602
|
+
}
|
603
|
+
|
604
|
+
expect(@stream).to receive(:send) do |frame|
|
605
|
+
expect(frame[:type]).to eq :headers
|
606
|
+
expect(frame[:payload]).to eq payload.to_a
|
607
|
+
expect(frame[:flags]).to eq [:end_headers]
|
608
|
+
end
|
609
|
+
|
610
|
+
@stream.headers(payload, end_stream: false, end_headers: true)
|
611
|
+
end
|
612
|
+
|
613
|
+
it '.data should emit DATA frames' do
|
614
|
+
expect(@stream).to receive(:send) do |frame|
|
615
|
+
expect(frame[:type]).to eq :data
|
616
|
+
expect(frame[:payload]).to eq 'text'
|
617
|
+
expect(frame[:flags]).to be_empty
|
618
|
+
end
|
619
|
+
@stream.data('text', end_stream: false)
|
620
|
+
|
621
|
+
expect(@stream).to receive(:send) do |frame|
|
622
|
+
expect(frame[:flags]).to eq [:end_stream]
|
623
|
+
end
|
624
|
+
@stream.data('text')
|
625
|
+
end
|
626
|
+
|
627
|
+
it '.data should split large DATA frames' do
|
628
|
+
data = 'x' * 16_384 * 2
|
629
|
+
|
630
|
+
want = [
|
631
|
+
{ type: :data, flags: [], length: 16_384 },
|
632
|
+
{ type: :data, flags: [], length: 16_384 },
|
633
|
+
{ type: :data, flags: [:end_stream], length: 1 },
|
634
|
+
]
|
635
|
+
want.each do |w|
|
636
|
+
expect(@stream).to receive(:send) do |frame|
|
637
|
+
expect(frame[:type]).to eq w[:type]
|
638
|
+
expect(frame[:flags]).to eq w[:flags]
|
639
|
+
expect(frame[:payload].length).to eq w[:length]
|
640
|
+
end
|
641
|
+
end
|
642
|
+
|
643
|
+
@stream.data(data + 'x')
|
644
|
+
end
|
645
|
+
|
646
|
+
it '.cancel should reset stream with cancel error code' do
|
647
|
+
expect(@stream).to receive(:send) do |frame|
|
648
|
+
expect(frame[:type]).to eq :rst_stream
|
649
|
+
expect(frame[:error]).to eq :cancel
|
650
|
+
end
|
651
|
+
|
652
|
+
@stream.cancel
|
653
|
+
end
|
654
|
+
|
655
|
+
it '.refuse should reset stream with refused stream error code' do
|
656
|
+
expect(@stream).to receive(:send) do |frame|
|
657
|
+
expect(frame[:type]).to eq :rst_stream
|
658
|
+
expect(frame[:error]).to eq :refused_stream
|
659
|
+
end
|
660
|
+
|
661
|
+
@stream.refuse
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
665
|
+
context 'server API' do
|
666
|
+
before(:each) do
|
667
|
+
@srv = Server.new
|
668
|
+
@frm = Framer.new
|
669
|
+
|
670
|
+
@client.on(:frame) { |bytes| @srv << bytes }
|
671
|
+
@client_stream = @client.new_stream
|
672
|
+
end
|
673
|
+
|
674
|
+
it 'should emit received headers via on(:headers)' do
|
675
|
+
headers, recv = [%w(header value)], nil
|
676
|
+
@srv.on(:stream) do |stream|
|
677
|
+
stream.on(:headers) { |h| recv = h }
|
678
|
+
end
|
679
|
+
|
680
|
+
@client_stream.headers(headers)
|
681
|
+
expect(recv).to eq headers
|
682
|
+
end
|
683
|
+
|
684
|
+
it 'should emit received payload via on(:data)' do
|
685
|
+
payload = 'some-payload'
|
686
|
+
@srv.on(:stream) do |stream|
|
687
|
+
stream.on(:data) do |recv|
|
688
|
+
expect(recv).to eq payload
|
689
|
+
end
|
690
|
+
end
|
691
|
+
|
692
|
+
@client_stream.headers('key' => 'value')
|
693
|
+
@client_stream.data(payload)
|
694
|
+
end
|
695
|
+
|
696
|
+
it 'should emit received priority parameters via on(:priority)' do
|
697
|
+
new_weight, new_dependency = 15, @client_stream.id + 2
|
698
|
+
callback_called = false
|
699
|
+
@srv.on(:stream) do |stream|
|
700
|
+
stream.on(:priority) do |pri|
|
701
|
+
callback_called = true
|
702
|
+
expect(pri.is_a?(Hash)).to be
|
703
|
+
expect(pri[:weight]).to eq new_weight
|
704
|
+
expect(pri[:dependency]).to eq new_dependency
|
705
|
+
end
|
706
|
+
end
|
707
|
+
|
708
|
+
@client_stream.headers('key' => 'value')
|
709
|
+
@client_stream.reprioritize(weight: new_weight, dependency: new_dependency)
|
710
|
+
expect(callback_called).to be
|
711
|
+
end
|
712
|
+
|
713
|
+
context 'push' do
|
714
|
+
before(:each) do
|
715
|
+
@srv.on(:frame) { |bytes| @client << bytes }
|
716
|
+
@srv.on(:stream) do |stream|
|
717
|
+
@server_stream = stream
|
718
|
+
end
|
719
|
+
|
720
|
+
@client_stream.headers('key' => 'value')
|
721
|
+
end
|
722
|
+
|
723
|
+
it '.promise should emit server initiated stream' do
|
724
|
+
push = nil
|
725
|
+
@server_stream.promise('key' => 'val') { |pstream| push = pstream }
|
726
|
+
expect(push.id).to eq 2
|
727
|
+
end
|
728
|
+
|
729
|
+
it '.promise push stream should have parent stream' do
|
730
|
+
push = nil
|
731
|
+
@server_stream.promise('key' => 'val') { |pstream| push = pstream }
|
732
|
+
|
733
|
+
expect(push.state).to eq :reserved_local
|
734
|
+
expect(push.parent.id).to eq @server_stream.id
|
735
|
+
end
|
736
|
+
|
737
|
+
context 'stream states' do
|
738
|
+
it 'server: active > half close > close' do
|
739
|
+
order = []
|
740
|
+
@server_stream.promise('key' => 'val') do |push|
|
741
|
+
stream = push
|
742
|
+
|
743
|
+
expect(push.state).to eq :reserved_local
|
744
|
+
order << :reserved
|
745
|
+
|
746
|
+
push.on(:active) { order << :active }
|
747
|
+
push.on(:half_close) { order << :half_close }
|
748
|
+
push.on(:close) { order << :close }
|
749
|
+
|
750
|
+
push.headers('key2' => 'val2')
|
751
|
+
push.send DATA.merge(stream: stream.id)
|
752
|
+
end
|
753
|
+
|
754
|
+
expect(order).to eq [:reserved, :active, :half_close, :close]
|
755
|
+
end
|
756
|
+
|
757
|
+
it 'client: headers > active > headers > .. > data > close' do
|
758
|
+
order, headers = [], []
|
759
|
+
@client.on(:promise) do |push|
|
760
|
+
order << :reserved
|
761
|
+
|
762
|
+
push.on(:active) { order << :active }
|
763
|
+
push.on(:data) { order << :data }
|
764
|
+
push.on(:half_close) { order << :half_close }
|
765
|
+
push.on(:close) { order << :close }
|
766
|
+
|
767
|
+
push.on(:headers) do |h|
|
768
|
+
order << :headers
|
769
|
+
headers += h
|
770
|
+
end
|
771
|
+
|
772
|
+
expect(push.id).to be_even
|
773
|
+
end
|
774
|
+
|
775
|
+
@server_stream.promise('key' => 'val') do |push|
|
776
|
+
push.headers('key2' => 'val2')
|
777
|
+
push.data('somedata')
|
778
|
+
end
|
779
|
+
|
780
|
+
expect(headers).to eq([%w(key val), %w(key2 val2)])
|
781
|
+
expect(order).to eq [
|
782
|
+
:reserved,
|
783
|
+
:headers,
|
784
|
+
:active,
|
785
|
+
:headers,
|
786
|
+
:half_close,
|
787
|
+
:data,
|
788
|
+
:close,
|
789
|
+
]
|
790
|
+
end
|
791
|
+
end
|
792
|
+
end
|
793
|
+
end
|
794
|
+
end
|