http-2 0.6.3 → 0.7.0

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