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.
@@ -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