http-2 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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