http-2 0.9.0 → 0.11.0

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