http-2 0.6.3 → 0.7.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.
@@ -24,14 +24,50 @@ describe HTTP2::Connection do
24
24
  end
25
25
  end
26
26
 
27
+ context "settings synchronization" do
28
+ it "should reflect outgoing settings when ack is received" do
29
+ @conn.local_settings[:settings_header_table_size].should eq 4096
30
+ @conn.settings(settings_header_table_size: 256)
31
+ @conn.local_settings[:settings_header_table_size].should eq 4096
32
+
33
+ ack = { type: :settings, stream: 0, payload: [], flags: [:ack] }
34
+ @conn << f.generate(ack)
35
+
36
+ @conn.local_settings[:settings_header_table_size].should eq 256
37
+ end
38
+
39
+ it "should reflect incoming settings when SETTINGS is received" do
40
+ @conn.remote_settings[:settings_header_table_size].should eq 4096
41
+ settings = SETTINGS.dup
42
+ settings[:payload] = [[:settings_header_table_size, 256]]
43
+
44
+ @conn << f.generate(settings)
45
+
46
+ @conn.remote_settings[:settings_header_table_size].should eq 256
47
+ end
48
+
49
+ it "should send SETTINGS ACK when SETTINGS is received" do
50
+ settings = SETTINGS.dup
51
+ settings[:payload] = [[:settings_header_table_size, 256]]
52
+
53
+ @conn.should_receive(:send) do |frame|
54
+ frame[:type].should eq :settings
55
+ frame[:flags].should eq [:ack]
56
+ frame[:payload].should eq []
57
+ end
58
+
59
+ @conn << f.generate(settings)
60
+ end
61
+ end
62
+
27
63
  context "stream management" do
28
64
  it "should initialize to default stream limit (100)" do
29
- @conn.stream_limit.should eq 100
65
+ @conn.local_settings[:settings_max_concurrent_streams].should eq 100
30
66
  end
31
67
 
32
68
  it "should change stream limit to received SETTINGS value" do
33
69
  @conn << f.generate(SETTINGS)
34
- @conn.stream_limit.should eq 10
70
+ @conn.remote_settings[:settings_max_concurrent_streams].should eq 10
35
71
  end
36
72
 
37
73
  it "should count open streams against stream limit" do
@@ -78,62 +114,104 @@ describe HTTP2::Connection do
78
114
  @conn << f.generate(SETTINGS)
79
115
 
80
116
  stream, headers = nil, HEADERS.dup
81
- headers[:priority] = 20
117
+ headers[:weight] = 20
118
+ headers[:stream_dependency] = 0
119
+ headers[:exclusive] = false
82
120
 
83
121
  @conn.on(:stream) {|s| stream = s }
84
122
  @conn << f.generate(headers)
85
123
 
86
- stream.priority.should eq 20
124
+ stream.weight.should eq 20
125
+ end
126
+ end
127
+
128
+ context "Headers pre/post processing" do
129
+ it "should not concatenate multiple occurences of a header field with the same name" do
130
+ input = [
131
+ ["Content-Type", "text/html"],
132
+ ["Cache-Control", "max-age=60, private"],
133
+ ["Cache-Control", "must-revalidate"],
134
+ ]
135
+ expected = [
136
+ ["content-type", "text/html"],
137
+ ["cache-control", "max-age=60, private"],
138
+ ["cache-control", "must-revalidate"],
139
+ ]
140
+ headers = []
141
+ @conn.on(:frame) do |bytes|
142
+ bytes.force_encoding('binary')
143
+ # bytes[3]: frame's type field
144
+ [1,5,9].include?(bytes[3].ord) and headers << f.parse(bytes)
145
+ end
146
+
147
+ stream = @conn.new_stream
148
+ stream.headers(input)
149
+
150
+ headers.size.should eq 1
151
+ emitted = Decompressor.new.decode(headers.first[:payload])
152
+ emitted.should match_array(expected)
153
+ end
154
+
155
+ it "should not split zero-concatenated header field values" do
156
+ input = [
157
+ ["cache-control", "max-age=60, private\0must-revalidate"],
158
+ ["content-type", "text/html"],
159
+ ["cookie", "a=b\0c=d; e=f"],
160
+ ]
161
+ expected = [
162
+ ["cache-control", "max-age=60, private\0must-revalidate"],
163
+ ["content-type", "text/html"],
164
+ ["cookie", "a=b\0c=d; e=f"],
165
+ ]
166
+
167
+ result = nil
168
+ @conn.on(:stream) do |stream|
169
+ stream.on(:headers) {|h| result = h}
170
+ end
171
+
172
+ srv = Server.new
173
+ srv.on(:frame) {|bytes| @conn << bytes}
174
+ stream = srv.new_stream
175
+ stream.headers(input)
176
+
177
+ result.should eq expected
178
+
87
179
  end
88
180
  end
89
181
 
90
182
  context "flow control" do
91
183
  it "should initialize to default flow window" do
92
- @conn.window.should eq DEFAULT_FLOW_WINDOW
184
+ @conn.remote_window.should eq DEFAULT_FLOW_WINDOW
93
185
  end
94
186
 
95
187
  it "should update connection and stream windows on SETTINGS" do
96
188
  settings, data = SETTINGS.dup, DATA.dup
97
- settings[:payload] = { settings_initial_window_size: 1024 }
189
+ settings[:payload] = [[:settings_initial_window_size, 1024]]
98
190
  data[:payload] = 'x'*2048
99
191
 
100
192
  stream = @conn.new_stream
101
193
 
102
194
  stream.send HEADERS
103
195
  stream.send data
104
- stream.window.should eq (DEFAULT_FLOW_WINDOW - 2048)
105
- @conn.window.should eq (DEFAULT_FLOW_WINDOW - 2048)
196
+ stream.remote_window.should eq (DEFAULT_FLOW_WINDOW - 2048)
197
+ @conn.remote_window.should eq (DEFAULT_FLOW_WINDOW - 2048)
106
198
 
107
199
  @conn << f.generate(settings)
108
- @conn.window.should eq -1024
109
- stream.window.should eq -1024
200
+ @conn.remote_window.should eq -1024
201
+ stream.remote_window.should eq -1024
110
202
  end
111
203
 
112
204
  it "should initialize streams with window specified by peer" do
113
205
  settings = SETTINGS.dup
114
- settings[:payload] = { settings_initial_window_size: 1024 }
206
+ settings[:payload] = [[:settings_initial_window_size, 1024]]
115
207
 
116
208
  @conn << f.generate(settings)
117
- @conn.new_stream.window.should eq 1024
118
- end
119
-
120
- it "should support global disable of flow control" do
121
- @conn << f.generate(SETTINGS)
122
- @conn.window.should eq Float::INFINITY
123
- end
124
-
125
- it "should raise error on flow control after disabling it" do
126
- expect { @conn << f.generate(SETTINGS) }.to_not raise_error
127
- expect {
128
- [WINDOW_UPDATE, SETTINGS].each do |frame|
129
- @conn.dup << f.generate(frame)
130
- end
131
- }.to raise_error(FlowControlError)
209
+ @conn.new_stream.remote_window.should eq 1024
132
210
  end
133
211
 
134
212
  it "should observe connection flow control" do
135
213
  settings, data = SETTINGS.dup, DATA.dup
136
- settings[:payload] = { settings_initial_window_size: 1000 }
214
+ settings[:payload] = [[:settings_initial_window_size, 1000]]
137
215
 
138
216
  @conn << f.generate(settings)
139
217
  s1 = @conn.new_stream
@@ -141,32 +219,32 @@ describe HTTP2::Connection do
141
219
 
142
220
  s1.send HEADERS
143
221
  s1.send data.merge({payload: "x" * 900})
144
- @conn.window.should eq 100
222
+ @conn.remote_window.should eq 100
145
223
 
146
224
  s2.send HEADERS
147
225
  s2.send data.merge({payload: "x" * 200})
148
- @conn.window.should eq 0
226
+ @conn.remote_window.should eq 0
149
227
  @conn.buffered_amount.should eq 100
150
228
 
151
229
  @conn << f.generate(WINDOW_UPDATE.merge({stream: 0, increment: 1000}))
152
230
  @conn.buffered_amount.should eq 0
153
- @conn.window.should eq 900
231
+ @conn.remote_window.should eq 900
154
232
  end
155
233
  end
156
234
 
157
235
  context "framing" do
158
236
  it "should buffer incomplete frames" do
159
237
  settings = SETTINGS.dup
160
- settings[:payload] = { settings_initial_window_size: 1000 }
238
+ settings[:payload] = [[:settings_initial_window_size, 1000]]
161
239
  @conn << f.generate(settings)
162
240
 
163
241
  frame = f.generate(WINDOW_UPDATE.merge({stream: 0, increment: 1000}))
164
242
  @conn << frame
165
- @conn.window.should eq 2000
243
+ @conn.remote_window.should eq 2000
166
244
 
167
245
  @conn << frame.slice!(0,1)
168
246
  @conn << frame
169
- @conn.window.should eq 3000
247
+ @conn.remote_window.should eq 3000
170
248
  end
171
249
 
172
250
  it "should decompress header blocks regardless of stream state" do
@@ -175,7 +253,7 @@ describe HTTP2::Connection do
175
253
  ["x-my-header", "first"]
176
254
  ]
177
255
 
178
- cc = Compressor.new(:response)
256
+ cc = Compressor.new
179
257
  headers = HEADERS.dup
180
258
  headers[:payload] = cc.encode(req_headers)
181
259
 
@@ -195,13 +273,16 @@ describe HTTP2::Connection do
195
273
  ["x-my-header", "first"]
196
274
  ]
197
275
 
198
- cc = Compressor.new(:response)
276
+ cc = Compressor.new
199
277
  h1, h2 = HEADERS.dup, CONTINUATION.dup
200
- h1[:payload] = cc.encode([req_headers.first])
278
+
279
+ # Header block fragment might not complete for decompression
280
+ payload = cc.encode(req_headers)
281
+ h1[:payload] = payload.slice!(0, payload.size/2) # first half
201
282
  h1[:stream] = 5
202
283
  h1[:flags] = []
203
284
 
204
- h2[:payload] = cc.encode([req_headers.last])
285
+ h2[:payload] = payload # the remaining
205
286
  h2[:stream] = 5
206
287
 
207
288
  @conn << f.generate(SETTINGS)
@@ -226,7 +307,7 @@ describe HTTP2::Connection do
226
307
  end
227
308
  end
228
309
 
229
- it "should raise connection error on encode exception" do
310
+ it "should raise compression error on encode of invalid frame" do
230
311
  @conn << f.generate(SETTINGS)
231
312
  stream = @conn.new_stream
232
313
 
@@ -235,48 +316,143 @@ describe HTTP2::Connection do
235
316
  }.to raise_error(CompressionError)
236
317
  end
237
318
 
238
- it "should raise connection error on decode exception" do
319
+ it "should raise connection error on decode of invalid frame" do
239
320
  @conn << f.generate(SETTINGS)
240
- frame = f.generate(HEADERS.dup)
241
- frame[1] = 0.chr
242
-
321
+ frame = f.generate(DATA.dup) # Receiving DATA on unopened stream 1 is an error.
322
+ # Connection errors emit protocol error frames
243
323
  expect { @conn << frame }.to raise_error(ProtocolError)
244
324
  end
245
325
 
246
326
  it "should emit encoded frames via on(:frame)" do
247
327
  bytes = nil
248
328
  @conn.on(:frame) {|d| bytes = d }
249
- @conn.settings(stream_limit: 10, window_limit: Float::INFINITY)
329
+ @conn.settings(settings_max_concurrent_streams: 10,
330
+ settings_initial_window_size: 0x7fffffff)
250
331
 
251
332
  bytes.should eq f.generate(SETTINGS)
252
333
  end
253
334
 
254
335
  it "should compress stream headers" do
255
- @conn.ping("12345678")
256
336
  @conn.on(:frame) do |bytes|
257
337
  bytes.force_encoding('binary')
258
338
  bytes.should_not match('get')
259
339
  bytes.should_not match('http')
260
- bytes.should match('www.example.org')
340
+ bytes.should_not match('www.example.org') # should be huffman encoded
261
341
  end
262
342
 
263
343
  stream = @conn.new_stream
264
344
  stream.headers({
265
345
  ':method' => 'get',
266
346
  ':scheme' => 'http',
267
- ':host' => 'www.example.org',
347
+ ':authority' => 'www.example.org',
268
348
  ':path' => '/resource'
269
349
  })
270
350
  end
351
+
352
+ it "should generate CONTINUATION if HEADERS is too long" do
353
+ headers = []
354
+ @conn.on(:frame) do |bytes|
355
+ bytes.force_encoding('binary')
356
+ # bytes[3]: frame's type field
357
+ [1,5,9].include?(bytes[3].ord) and headers << f.parse(bytes)
358
+ end
359
+
360
+ stream = @conn.new_stream
361
+ stream.headers({
362
+ ':method' => 'get',
363
+ ':scheme' => 'http',
364
+ ':authority' => 'www.example.org',
365
+ ':path' => '/resource',
366
+ 'custom' => 'q' * 44000,
367
+ }, end_stream: true)
368
+ headers.size.should eq 3
369
+ headers[0][:type].should eq :headers
370
+ headers[1][:type].should eq :continuation
371
+ headers[2][:type].should eq :continuation
372
+ headers[0][:flags].should eq [:end_stream]
373
+ headers[1][:flags].should eq []
374
+ headers[2][:flags].should eq [:end_headers]
375
+ end
376
+
377
+ it "should not generate CONTINUATION if HEADERS fits exactly in a frame" do
378
+ headers = []
379
+ @conn.on(:frame) do |bytes|
380
+ bytes.force_encoding('binary')
381
+ # bytes[3]: frame's type field
382
+ [1,5,9].include?(bytes[3].ord) and headers << f.parse(bytes)
383
+ end
384
+
385
+ stream = @conn.new_stream
386
+ stream.headers({
387
+ ':method' => 'get',
388
+ ':scheme' => 'http',
389
+ ':authority' => 'www.example.org',
390
+ ':path' => '/resource',
391
+ 'custom' => 'q' * 18682, # this number should be updated when Huffman table is changed
392
+ }, end_stream: true)
393
+ headers[0][:length].should eq @conn.remote_settings[:settings_max_frame_size]
394
+ headers.size.should eq 1
395
+ headers[0][:type].should eq :headers
396
+ headers[0][:flags].should include(:end_headers)
397
+ headers[0][:flags].should include(:end_stream)
398
+ end
399
+
400
+ it "should not generate CONTINUATION if HEADERS fits exactly in a frame" do
401
+ headers = []
402
+ @conn.on(:frame) do |bytes|
403
+ bytes.force_encoding('binary')
404
+ # bytes[3]: frame's type field
405
+ [1,5,9].include?(bytes[3].ord) and headers << f.parse(bytes)
406
+ end
407
+
408
+ stream = @conn.new_stream
409
+ stream.headers({
410
+ ':method' => 'get',
411
+ ':scheme' => 'http',
412
+ ':authority' => 'www.example.org',
413
+ ':path' => '/resource',
414
+ 'custom' => 'q' * 18682, # this number should be updated when Huffman table is changed
415
+ }, end_stream: true)
416
+ headers[0][:length].should eq @conn.remote_settings[:settings_max_frame_size]
417
+ headers.size.should eq 1
418
+ headers[0][:type].should eq :headers
419
+ headers[0][:flags].should include(:end_headers)
420
+ headers[0][:flags].should include(:end_stream)
421
+ end
422
+
423
+ it "should generate CONTINUATION if HEADERS exceed the max payload by one byte" do
424
+ headers = []
425
+ @conn.on(:frame) do |bytes|
426
+ bytes.force_encoding('binary')
427
+ [1,5,9].include?(bytes[3].ord) and headers << f.parse(bytes)
428
+ end
429
+
430
+ stream = @conn.new_stream
431
+ stream.headers({
432
+ ':method' => 'get',
433
+ ':scheme' => 'http',
434
+ ':authority' => 'www.example.org',
435
+ ':path' => '/resource',
436
+ 'custom' => 'q' * 18683, # this number should be updated when Huffman table is changed
437
+ }, end_stream: true)
438
+ headers[0][:length].should eq @conn.remote_settings[:settings_max_frame_size]
439
+ headers[1][:length].should eq 1
440
+ headers.size.should eq 2
441
+ headers[0][:type].should eq :headers
442
+ headers[1][:type].should eq :continuation
443
+ headers[0][:flags].should eq [:end_stream]
444
+ headers[1][:flags].should eq [:end_headers]
445
+ end
271
446
  end
272
447
 
273
448
  context "connection management" do
274
449
  it "should raise error on invalid connection header" do
275
450
  srv = Server.new
276
- expect { srv.dup << f.generate(SETTINGS) }.to raise_error(HandshakeError)
451
+ expect { srv << f.generate(SETTINGS) }.to raise_error(HandshakeError)
277
452
 
453
+ srv = Server.new
278
454
  expect {
279
- srv << CONNECTION_HEADER
455
+ srv << CONNECTION_PREFACE_MAGIC
280
456
  srv << f.generate(SETTINGS)
281
457
  }.to_not raise_error
282
458
  end
@@ -285,7 +461,7 @@ describe HTTP2::Connection do
285
461
  @conn << f.generate(SETTINGS)
286
462
  @conn.should_receive(:send) do |frame|
287
463
  frame[:type].should eq :ping
288
- frame[:flags].should eq [:pong]
464
+ frame[:flags].should eq [:ack]
289
465
  frame[:payload].should eq "12345678"
290
466
  end
291
467
 
@@ -344,14 +520,17 @@ describe HTTP2::Connection do
344
520
  it "should send GOAWAY frame on connection error" do
345
521
  stream = @conn.new_stream
346
522
 
347
- @conn.stub(:encode)
523
+ @conn.should_receive(:encode) do |frame|
524
+ frame[:type].should eq :settings
525
+ [frame]
526
+ end
348
527
  @conn.should_receive(:encode) do |frame|
349
528
  frame[:type].should eq :goaway
350
529
  frame[:last_stream].should eq stream.id
351
530
  frame[:error].should eq :protocol_error
531
+ [frame]
352
532
  end
353
533
 
354
- @conn << f.generate(SETTINGS)
355
534
  expect { @conn << f.generate(DATA) }.to raise_error(ProtocolError)
356
535
  end
357
536
  end
@@ -360,14 +539,15 @@ describe HTTP2::Connection do
360
539
  it ".settings should emit SETTINGS frames" do
361
540
  @conn.should_receive(:send) do |frame|
362
541
  frame[:type].should eq :settings
363
- frame[:payload].should eq({
364
- settings_max_concurrent_streams: 10,
365
- settings_flow_control_options: 1
366
- })
542
+ frame[:payload].should eq([
543
+ [:settings_max_concurrent_streams, 10],
544
+ [:settings_initial_window_size, 0x7fffffff],
545
+ ])
367
546
  frame[:stream].should eq 0
368
547
  end
369
548
 
370
- @conn.settings(stream_limit: 10, window_limit: Float::INFINITY)
549
+ @conn.settings(settings_max_concurrent_streams: 10,
550
+ settings_initial_window_size: 0x7fffffff)
371
551
  end
372
552
 
373
553
  it ".ping should generate PING frames" do