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