http-2 0.9.0 → 0.11.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.
@@ -1,6 +1,7 @@
1
1
  require 'helper'
2
2
 
3
3
  RSpec.describe HTTP2::Connection do
4
+ include FrameHelpers
4
5
  before(:each) do
5
6
  @conn = Client.new
6
7
  end
@@ -8,18 +9,21 @@ RSpec.describe HTTP2::Connection do
8
9
  let(:f) { Framer.new }
9
10
 
10
11
  context 'initialization and settings' do
11
- it 'should raise error if first frame is not SETTINGS' do
12
- (FRAME_TYPES - [SETTINGS]).each do |frame|
13
- frame = set_stream_id(f.generate(frame.deep_dup), 0x0)
14
- expect { @conn.dup << frame }.to raise_error(ProtocolError)
12
+ it 'should raise error if first frame is not settings' do
13
+ (frame_types - [settings_frame]).each do |frame|
14
+ expect { @conn << frame }.to raise_error(ProtocolError)
15
+ expect(@conn).to be_closed
15
16
  end
17
+ end
16
18
 
17
- expect { @conn << f.generate(SETTINGS.dup) }.to_not raise_error
19
+ it 'should not raise error if first frame is SETTINGS' do
20
+ expect { @conn << f.generate(settings_frame) }.to_not raise_error
18
21
  expect(@conn.state).to eq :connected
22
+ expect(@conn).to_not be_closed
19
23
  end
20
24
 
21
25
  it 'should raise error if SETTINGS stream != 0' do
22
- frame = set_stream_id(f.generate(SETTINGS.dup), 0x1)
26
+ frame = set_stream_id(f.generate(settings_frame), 0x1)
23
27
  expect { @conn << frame }.to raise_error(ProtocolError)
24
28
  end
25
29
  end
@@ -38,7 +42,7 @@ RSpec.describe HTTP2::Connection do
38
42
 
39
43
  it 'should reflect incoming settings when SETTINGS is received' do
40
44
  expect(@conn.remote_settings[:settings_header_table_size]).to eq 4096
41
- settings = SETTINGS.dup
45
+ settings = settings_frame
42
46
  settings[:payload] = [[:settings_header_table_size, 256]]
43
47
 
44
48
  @conn << f.generate(settings)
@@ -46,17 +50,39 @@ RSpec.describe HTTP2::Connection do
46
50
  expect(@conn.remote_settings[:settings_header_table_size]).to eq 256
47
51
  end
48
52
 
53
+ it 'should reflect settings_max_frame_size recevied from peer' do
54
+ settings = settings_frame
55
+ settings[:payload] = [[:settings_max_frame_size, 16_385]]
56
+
57
+ @conn << f.generate(settings)
58
+
59
+ frame = {
60
+ length: 16_385,
61
+ type: :data,
62
+ flags: [:end_stream],
63
+ stream: 1,
64
+ payload: 'a' * 16_385,
65
+ }
66
+ expect { @conn.send(frame) }.not_to raise_error(CompressionError)
67
+ end
68
+
49
69
  it 'should send SETTINGS ACK when SETTINGS is received' do
50
- settings = SETTINGS.dup
70
+ settings = settings_frame
51
71
  settings[:payload] = [[:settings_header_table_size, 256]]
52
72
 
53
- expect(@conn).to receive(:send) do |frame|
54
- expect(frame[:type]).to eq :settings
55
- expect(frame[:flags]).to eq [:ack]
56
- expect(frame[:payload]).to eq []
73
+ # We should expect two frames here (append .twice) - one for the connection setup, and one for the settings ack.
74
+ frames = []
75
+ expect(@conn).to receive(:send).twice do |frame|
76
+ frames << frame
57
77
  end
58
78
 
79
+ @conn.send_connection_preface
59
80
  @conn << f.generate(settings)
81
+
82
+ frame = frames.last
83
+ expect(frame[:type]).to eq :settings
84
+ expect(frame[:flags]).to eq [:ack]
85
+ expect(frame[:payload]).to eq []
60
86
  end
61
87
  end
62
88
 
@@ -66,44 +92,47 @@ RSpec.describe HTTP2::Connection do
66
92
  end
67
93
 
68
94
  it 'should change stream limit to received SETTINGS value' do
69
- @conn << f.generate(SETTINGS.dup)
95
+ @conn << f.generate(settings_frame)
70
96
  expect(@conn.remote_settings[:settings_max_concurrent_streams]).to eq 10
71
97
  end
72
98
 
73
99
  it 'should count open streams against stream limit' do
74
100
  s = @conn.new_stream
75
101
  expect(@conn.active_stream_count).to eq 0
76
- s.receive HEADERS
102
+ s.receive headers_frame
77
103
  expect(@conn.active_stream_count).to eq 1
78
104
  end
79
105
 
80
106
  it 'should not count reserved streams against stream limit' do
81
107
  s1 = @conn.new_stream
82
- s1.receive PUSH_PROMISE
108
+ s1.receive push_promise_frame
83
109
  expect(@conn.active_stream_count).to eq 0
84
110
 
85
111
  s2 = @conn.new_stream
86
- s2.send PUSH_PROMISE.deep_dup
112
+ s2.send push_promise_frame
87
113
  expect(@conn.active_stream_count).to eq 0
88
114
 
89
115
  # transition to half closed
90
- s1.receive HEADERS
91
- s2.send HEADERS.deep_dup
116
+ s1.receive headers_frame
117
+ s2.send headers_frame
92
118
  expect(@conn.active_stream_count).to eq 2
93
119
 
94
120
  # transition to closed
95
- s1.receive DATA
96
- s2.send DATA.dup
121
+ s1.receive data_frame
122
+ s2.send data_frame
97
123
  expect(@conn.active_stream_count).to eq 0
124
+
125
+ expect(s1).to be_closed
126
+ expect(s2).to be_closed
98
127
  end
99
128
 
100
129
  it 'should not exceed stream limit set by peer' do
101
- @conn << f.generate(SETTINGS.dup)
130
+ @conn << f.generate(settings_frame)
102
131
 
103
132
  expect do
104
133
  10.times do
105
134
  s = @conn.new_stream
106
- s.send HEADERS.deep_dup
135
+ s.send headers_frame
107
136
  end
108
137
  end.to_not raise_error
109
138
 
@@ -111,9 +140,9 @@ RSpec.describe HTTP2::Connection do
111
140
  end
112
141
 
113
142
  it 'should initialize stream with HEADERS priority value' do
114
- @conn << f.generate(SETTINGS.dup)
143
+ @conn << f.generate(settings_frame)
115
144
 
116
- stream, headers = nil, HEADERS.dup
145
+ stream, headers = nil, headers_frame
117
146
  headers[:weight] = 20
118
147
  headers[:stream_dependency] = 0
119
148
  headers[:exclusive] = false
@@ -125,16 +154,33 @@ RSpec.describe HTTP2::Connection do
125
154
  end
126
155
 
127
156
  it 'should initialize idle stream on PRIORITY frame' do
128
- @conn << f.generate(SETTINGS.dup)
157
+ @conn << f.generate(settings_frame)
129
158
 
130
159
  stream = nil
131
160
  @conn.on(:stream) { |s| stream = s }
132
- @conn << f.generate(PRIORITY.dup)
161
+ @conn << f.generate(priority_frame)
133
162
 
134
163
  expect(stream.state).to eq :idle
135
164
  end
136
165
  end
137
166
 
167
+ context 'cleanup_recently_closed' do
168
+ it 'should cleanup old connections' do
169
+ now_ts = Time.now.to_i
170
+ stream_ids = Array.new(4) { @conn.new_stream.id }
171
+ expect(@conn.instance_variable_get('@streams').size).to eq(4)
172
+
173
+ # Assume that the first 3 streams were closed in different time
174
+ recently_closed = stream_ids[0, 3].zip([now_ts - 100, now_ts - 50, now_ts - 5]).to_h
175
+ @conn.instance_variable_set('@streams_recently_closed', recently_closed)
176
+
177
+ # Cleanup should delete streams that were closed earlier than 15s ago
178
+ @conn.__send__(:cleanup_recently_closed)
179
+ expect(@conn.instance_variable_get('@streams').size).to eq(2)
180
+ expect(@conn.instance_variable_get('@streams_recently_closed')).to eq(stream_ids[2] => now_ts - 5)
181
+ end
182
+ end
183
+
138
184
  context 'Headers pre/post processing' do
139
185
  it 'should not concatenate multiple occurences of a header field with the same name' do
140
186
  input = [
@@ -161,16 +207,14 @@ RSpec.describe HTTP2::Connection do
161
207
  end
162
208
 
163
209
  it 'should not split zero-concatenated header field values' do
164
- input = [
165
- ['cache-control', "max-age=60, private\0must-revalidate"],
166
- ['content-type', 'text/html'],
167
- ['cookie', "a=b\0c=d; e=f"],
168
- ]
169
- expected = [
170
- ['cache-control', "max-age=60, private\0must-revalidate"],
171
- ['content-type', 'text/html'],
172
- ['cookie', "a=b\0c=d; e=f"],
173
- ]
210
+ input = [*RESPONSE_HEADERS,
211
+ ['cache-control', "max-age=60, private\0must-revalidate"],
212
+ ['content-type', 'text/html'],
213
+ ['cookie', "a=b\0c=d; e=f"]]
214
+ expected = [*RESPONSE_HEADERS,
215
+ ['cache-control', "max-age=60, private\0must-revalidate"],
216
+ ['content-type', 'text/html'],
217
+ ['cookie', "a=b\0c=d; e=f"]]
174
218
 
175
219
  result = nil
176
220
  @conn.on(:stream) do |stream|
@@ -192,13 +236,13 @@ RSpec.describe HTTP2::Connection do
192
236
  end
193
237
 
194
238
  it 'should update connection and stream windows on SETTINGS' do
195
- settings, data = SETTINGS.dup, DATA.dup
239
+ settings, data = settings_frame, data_frame
196
240
  settings[:payload] = [[:settings_initial_window_size, 1024]]
197
241
  data[:payload] = 'x' * 2048
198
242
 
199
243
  stream = @conn.new_stream
200
244
 
201
- stream.send HEADERS.deep_dup
245
+ stream.send headers_frame
202
246
  stream.send data
203
247
  expect(stream.remote_window).to eq(DEFAULT_FLOW_WINDOW - 2048)
204
248
  expect(@conn.remote_window).to eq(DEFAULT_FLOW_WINDOW - 2048)
@@ -209,7 +253,7 @@ RSpec.describe HTTP2::Connection do
209
253
  end
210
254
 
211
255
  it 'should initialize streams with window specified by peer' do
212
- settings = SETTINGS.dup
256
+ settings = settings_frame
213
257
  settings[:payload] = [[:settings_initial_window_size, 1024]]
214
258
 
215
259
  @conn << f.generate(settings)
@@ -217,37 +261,37 @@ RSpec.describe HTTP2::Connection do
217
261
  end
218
262
 
219
263
  it 'should observe connection flow control' do
220
- settings, data = SETTINGS.dup, DATA.dup
264
+ settings, data = settings_frame, data_frame
221
265
  settings[:payload] = [[:settings_initial_window_size, 1000]]
222
266
 
223
267
  @conn << f.generate(settings)
224
268
  s1 = @conn.new_stream
225
269
  s2 = @conn.new_stream
226
270
 
227
- s1.send HEADERS.deep_dup
271
+ s1.send headers_frame
228
272
  s1.send data.merge(payload: 'x' * 900)
229
273
  expect(@conn.remote_window).to eq 100
230
274
 
231
- s2.send HEADERS.deep_dup
275
+ s2.send headers_frame
232
276
  s2.send data.merge(payload: 'x' * 200)
233
277
  expect(@conn.remote_window).to eq 0
234
278
  expect(@conn.buffered_amount).to eq 100
235
279
 
236
- @conn << f.generate(WINDOW_UPDATE.merge(stream: 0, increment: 1000))
280
+ @conn << f.generate(window_update_frame.merge(stream: 0, increment: 1000))
237
281
  expect(@conn.buffered_amount).to eq 0
238
282
  expect(@conn.remote_window).to eq 900
239
283
  end
240
284
 
241
285
  it 'should update window when data received is over half of the maximum local window size' do
242
- settings, data = SETTINGS.dup, DATA.dup
286
+ settings, data = settings_frame, data_frame
243
287
  conn = Client.new(settings_initial_window_size: 500)
244
288
 
245
289
  conn.receive f.generate(settings)
246
290
  s1 = conn.new_stream
247
291
  s2 = conn.new_stream
248
292
 
249
- s1.send HEADERS.deep_dup
250
- s2.send HEADERS.deep_dup
293
+ s1.send headers_frame
294
+ s2.send headers_frame
251
295
  expect(conn).to receive(:send) do |frame|
252
296
  expect(frame[:type]).to eq :window_update
253
297
  expect(frame[:stream]).to eq 0
@@ -263,11 +307,11 @@ RSpec.describe HTTP2::Connection do
263
307
 
264
308
  context 'framing' do
265
309
  it 'should buffer incomplete frames' do
266
- settings = SETTINGS.dup
310
+ settings = settings_frame
267
311
  settings[:payload] = [[:settings_initial_window_size, 1000]]
268
312
  @conn << f.generate(settings)
269
313
 
270
- frame = f.generate(WINDOW_UPDATE.merge(stream: 0, increment: 1000))
314
+ frame = f.generate(window_update_frame.merge(stream: 0, increment: 1000))
271
315
  @conn << frame
272
316
  expect(@conn.remote_window).to eq 2000
273
317
 
@@ -283,10 +327,10 @@ RSpec.describe HTTP2::Connection do
283
327
  ]
284
328
 
285
329
  cc = Compressor.new
286
- headers = HEADERS.dup
330
+ headers = headers_frame
287
331
  headers[:payload] = cc.encode(req_headers)
288
332
 
289
- @conn << f.generate(SETTINGS.dup)
333
+ @conn << f.generate(settings_frame)
290
334
  @conn.on(:stream) do |stream|
291
335
  expect(stream).to receive(:<<) do |frame|
292
336
  expect(frame[:payload]).to eq req_headers
@@ -303,7 +347,7 @@ RSpec.describe HTTP2::Connection do
303
347
  ]
304
348
 
305
349
  cc = Compressor.new
306
- h1, h2 = HEADERS.dup, CONTINUATION.dup
350
+ h1, h2 = headers_frame, continuation_frame
307
351
 
308
352
  # Header block fragment might not complete for decompression
309
353
  payload = cc.encode(req_headers)
@@ -314,7 +358,7 @@ RSpec.describe HTTP2::Connection do
314
358
  h2[:payload] = payload # the remaining
315
359
  h2[:stream] = 5
316
360
 
317
- @conn << f.generate(SETTINGS.dup)
361
+ @conn << f.generate(settings_frame)
318
362
  @conn.on(:stream) do |stream|
319
363
  expect(stream).to receive(:<<) do |frame|
320
364
  expect(frame[:payload]).to eq req_headers
@@ -326,28 +370,28 @@ RSpec.describe HTTP2::Connection do
326
370
  end
327
371
 
328
372
  it 'should require that split header blocks are a contiguous sequence' do
329
- headers = HEADERS.dup
373
+ headers = headers_frame
330
374
  headers[:flags] = []
331
375
 
332
- @conn << f.generate(SETTINGS.dup)
376
+ @conn << f.generate(settings_frame)
333
377
  @conn << f.generate(headers)
334
- (FRAME_TYPES - [CONTINUATION]).each do |frame|
378
+ (frame_types - [continuation_frame]).each do |frame|
335
379
  expect { @conn << f.generate(frame.deep_dup) }.to raise_error(ProtocolError)
336
380
  end
337
381
  end
338
382
 
339
383
  it 'should raise compression error on encode of invalid frame' do
340
- @conn << f.generate(SETTINGS.dup)
384
+ @conn << f.generate(settings_frame)
341
385
  stream = @conn.new_stream
342
386
 
343
387
  expect do
344
- stream.headers('name' => Float::INFINITY)
388
+ stream.headers({ 'name' => Float::INFINITY })
345
389
  end.to raise_error(CompressionError)
346
390
  end
347
391
 
348
392
  it 'should raise connection error on decode of invalid frame' do
349
- @conn << f.generate(SETTINGS.dup)
350
- frame = f.generate(DATA.dup) # Receiving DATA on unopened stream 1 is an error.
393
+ @conn << f.generate(settings_frame)
394
+ frame = f.generate(data_frame) # Receiving DATA on unopened stream 1 is an error.
351
395
  # Connection errors emit protocol error frames
352
396
  expect { @conn << frame }.to raise_error(ProtocolError)
353
397
  end
@@ -358,7 +402,7 @@ RSpec.describe HTTP2::Connection do
358
402
  @conn.settings(settings_max_concurrent_streams: 10,
359
403
  settings_initial_window_size: 0x7fffffff)
360
404
 
361
- expect(bytes).to eq f.generate(SETTINGS.dup)
405
+ expect(bytes).to eq f.generate(settings_frame)
362
406
  end
363
407
 
364
408
  it 'should compress stream headers' do
@@ -369,10 +413,10 @@ RSpec.describe HTTP2::Connection do
369
413
  end
370
414
 
371
415
  stream = @conn.new_stream
372
- stream.headers(':method' => 'get',
373
- ':scheme' => 'http',
374
- ':authority' => 'www.example.org',
375
- ':path' => '/resource')
416
+ stream.headers({ ':method' => 'get',
417
+ ':scheme' => 'http',
418
+ ':authority' => 'www.example.org',
419
+ ':path' => '/resource' })
376
420
  end
377
421
 
378
422
  it 'should generate CONTINUATION if HEADERS is too long' do
@@ -470,79 +514,106 @@ RSpec.describe HTTP2::Connection do
470
514
  context 'connection management' do
471
515
  it 'should raise error on invalid connection header' do
472
516
  srv = Server.new
473
- expect { srv << f.generate(SETTINGS.dup) }.to raise_error(HandshakeError)
517
+ expect { srv << f.generate(settings_frame) }.to raise_error(HandshakeError)
474
518
 
475
519
  srv = Server.new
476
520
  expect do
477
521
  srv << CONNECTION_PREFACE_MAGIC
478
- srv << f.generate(SETTINGS.dup)
522
+ srv << f.generate(settings_frame)
479
523
  end.to_not raise_error
480
524
  end
481
525
 
482
526
  it 'should respond to PING frames' do
483
- @conn << f.generate(SETTINGS.dup)
527
+ @conn << f.generate(settings_frame)
484
528
  expect(@conn).to receive(:send) do |frame|
485
529
  expect(frame[:type]).to eq :ping
486
530
  expect(frame[:flags]).to eq [:ack]
487
531
  expect(frame[:payload]).to eq '12345678'
488
532
  end
489
533
 
490
- @conn << f.generate(PING.dup)
534
+ @conn << f.generate(ping_frame)
491
535
  end
492
536
 
493
537
  it 'should fire callback on PONG' do
494
- @conn << f.generate(SETTINGS.dup)
538
+ @conn << f.generate(settings_frame)
495
539
 
496
540
  pong = nil
497
541
  @conn.ping('12345678') { |d| pong = d }
498
- @conn << f.generate(PONG.dup)
542
+ @conn << f.generate(pong_frame)
499
543
  expect(pong).to eq '12345678'
500
544
  end
501
545
 
502
546
  it 'should fire callback on receipt of GOAWAY' do
503
547
  last_stream, payload, error = nil
504
- @conn << f.generate(SETTINGS.dup)
548
+ @conn << f.generate(settings_frame)
505
549
  @conn.on(:goaway) do |s, e, p|
506
550
  last_stream = s
507
551
  error = e
508
552
  payload = p
509
553
  end
510
- @conn << f.generate(GOAWAY.merge(last_stream: 17, payload: 'test'))
554
+ @conn << f.generate(goaway_frame.merge(last_stream: 17, payload: 'test'))
511
555
 
512
556
  expect(last_stream).to eq 17
513
557
  expect(error).to eq :no_error
514
558
  expect(payload).to eq 'test'
559
+
560
+ expect(@conn).to be_closed
515
561
  end
516
562
 
517
563
  it 'should raise error when opening new stream after sending GOAWAY' do
518
564
  @conn.goaway
565
+ expect(@conn).to be_closed
566
+
519
567
  expect { @conn.new_stream }.to raise_error(ConnectionClosed)
520
568
  end
521
569
 
522
570
  it 'should raise error when opening new stream after receiving GOAWAY' do
523
- @conn << f.generate(SETTINGS.dup)
524
- @conn << f.generate(GOAWAY.dup)
571
+ @conn << f.generate(settings_frame)
572
+ @conn << f.generate(goaway_frame)
525
573
  expect { @conn.new_stream }.to raise_error(ConnectionClosed)
526
574
  end
527
575
 
576
+ it 'should not raise error when receiving connection management frames immediately after emitting goaway' do
577
+ @conn.goaway
578
+ expect(@conn).to be_closed
579
+
580
+ expect { @conn << f.generate(settings_frame) }.not_to raise_error(ProtocolError)
581
+ expect { @conn << f.generate(ping_frame) }.not_to raise_error(ProtocolError)
582
+ expect { @conn << f.generate(goaway_frame) }.not_to raise_error(ProtocolError)
583
+ end
584
+
528
585
  it 'should process connection management frames after GOAWAY' do
529
- @conn << f.generate(SETTINGS.dup)
530
- @conn << f.generate(HEADERS.dup)
531
- @conn << f.generate(GOAWAY.dup)
532
- @conn << f.generate(HEADERS.merge(stream: 7))
533
- @conn << f.generate(PUSH_PROMISE.dup)
586
+ @conn << f.generate(settings_frame)
587
+ @conn << f.generate(headers_frame)
588
+ @conn << f.generate(goaway_frame)
589
+ @conn << f.generate(headers_frame.merge(stream: 7))
590
+ @conn << f.generate(push_promise_frame)
534
591
 
535
592
  expect(@conn.active_stream_count).to eq 1
536
593
  end
537
594
 
538
595
  it 'should raise error on frame for invalid stream ID' do
539
- @conn << f.generate(SETTINGS.dup)
596
+ @conn << f.generate(settings_frame)
540
597
 
541
598
  expect do
542
- @conn << f.generate(DATA.dup.merge(stream: 31))
599
+ @conn << f.generate(data_frame.merge(stream: 31))
543
600
  end.to raise_error(ProtocolError)
544
601
  end
545
602
 
603
+ it 'should not raise an error on frame for a closed stream ID' do
604
+ srv = Server.new
605
+ srv << CONNECTION_PREFACE_MAGIC
606
+
607
+ stream = srv.new_stream
608
+ stream.send headers_frame
609
+ stream.send data_frame
610
+ stream.close
611
+
612
+ expect do
613
+ srv << f.generate(rst_stream_frame.merge(stream: stream.id))
614
+ end.to_not raise_error
615
+ end
616
+
546
617
  it 'should send GOAWAY frame on connection error' do
547
618
  stream = @conn.new_stream
548
619
 
@@ -557,7 +628,7 @@ RSpec.describe HTTP2::Connection do
557
628
  [frame]
558
629
  end
559
630
 
560
- expect { @conn << f.generate(DATA.dup) }.to raise_error(ProtocolError)
631
+ expect { @conn << f.generate(data_frame) }.to raise_error(ProtocolError)
561
632
  end
562
633
  end
563
634
 
@@ -586,8 +657,8 @@ RSpec.describe HTTP2::Connection do
586
657
  end
587
658
 
588
659
  it '.goaway should generate GOAWAY frame with last processed stream ID' do
589
- @conn << f.generate(SETTINGS.dup)
590
- @conn << f.generate(HEADERS.merge(stream: 17))
660
+ @conn << f.generate(settings_frame)
661
+ @conn << f.generate(headers_frame.merge(stream: 17))
591
662
 
592
663
  expect(@conn).to receive(:send) do |frame|
593
664
  expect(frame[:type]).to eq :goaway