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,448 @@
1
+ require "helper"
2
+
3
+ describe HTTP2::Connection do
4
+ before(:each) do
5
+ @conn = Connection.new
6
+ end
7
+
8
+ let(:f) { Framer.new }
9
+
10
+ context "initialization and settings" do
11
+ it "should return odd ids for client requests" do
12
+ @conn = Connection.new(:client)
13
+ @conn.new_stream.id.should_not be_even
14
+
15
+ @conn = Connection.new(:server)
16
+ @conn.new_stream.id.should be_even
17
+ end
18
+
19
+ it "should raise error if first frame is not SETTINGS" do
20
+ (FRAME_TYPES - [SETTINGS]).each do |frame|
21
+ frame = set_stream_id(f.generate(frame), 0x0)
22
+ expect { @conn.dup << frame }.to raise_error(ProtocolError)
23
+ end
24
+
25
+ expect { @conn << f.generate(SETTINGS) }.to_not raise_error
26
+ @conn.state.should eq :connected
27
+ end
28
+
29
+ it "should raise error if SETTINGS stream != 0" do
30
+ frame = set_stream_id(f.generate(SETTINGS), 0x1)
31
+ expect { @conn << frame }.to raise_error(ProtocolError)
32
+ end
33
+ end
34
+
35
+ context "stream management" do
36
+ it "should initialize to default stream limit (infinite)" do
37
+ @conn.stream_limit.should eq Float::INFINITY
38
+ end
39
+
40
+ it "should change stream limit to received SETTINGS value" do
41
+ @conn << f.generate(SETTINGS)
42
+ @conn.stream_limit.should eq 10
43
+ end
44
+
45
+ it "should count open streams against stream limit" do
46
+ s = @conn.new_stream
47
+ @conn.active_stream_count.should eq 0
48
+ s.receive HEADERS
49
+ @conn.active_stream_count.should eq 1
50
+ end
51
+
52
+ it "should not count reserved streams against stream limit" do
53
+ s1 = @conn.new_stream
54
+ s1.receive PUSH_PROMISE
55
+ @conn.active_stream_count.should eq 0
56
+
57
+ s2 = @conn.new_stream
58
+ s2.send PUSH_PROMISE
59
+ @conn.active_stream_count.should eq 0
60
+
61
+ # transition to half closed
62
+ s1.receive HEADERS
63
+ s2.send HEADERS
64
+ @conn.active_stream_count.should eq 2
65
+
66
+ # transition to closed
67
+ s1.receive DATA
68
+ s2.send DATA
69
+ @conn.active_stream_count.should eq 0
70
+ end
71
+
72
+ it "should not exceed stream limit set by peer" do
73
+ @conn << f.generate(SETTINGS)
74
+
75
+ expect {
76
+ 10.times do
77
+ s = @conn.new_stream
78
+ s.send HEADERS
79
+ end
80
+ }.to_not raise_error
81
+
82
+ expect { @conn.new_stream }.to raise_error(StreamLimitExceeded)
83
+ end
84
+
85
+ it "should initialize stream with HEADERS priority value" do
86
+ @conn << f.generate(SETTINGS)
87
+
88
+ stream, headers = nil, HEADERS.dup
89
+ headers[:priority] = 20
90
+
91
+ @conn.on(:stream) {|s| stream = s }
92
+ @conn << f.generate(headers)
93
+
94
+ stream.priority.should eq 20
95
+ end
96
+
97
+ context "push" do
98
+ it "should raise error on PUSH_PROMISE against stream 0" do
99
+ expect {
100
+ @conn << set_stream_id(f.generate(PUSH_PROMISE), 0)
101
+ }.to raise_error(ProtocolError)
102
+ end
103
+
104
+ it "should raise error on PUSH_PROMISE against bogus stream" do
105
+ expect {
106
+ @conn << set_stream_id(f.generate(PUSH_PROMISE), 31415)
107
+ }.to raise_error(ProtocolError)
108
+ end
109
+
110
+ it "should raise error on PUSH_PROMISE against non-idle stream" do
111
+ expect {
112
+ s = @conn.new_stream
113
+ s.send HEADERS
114
+
115
+ @conn << set_stream_id(f.generate(PUSH_PROMISE), s.id)
116
+ @conn << set_stream_id(f.generate(PUSH_PROMISE), s.id)
117
+ }.to raise_error(ProtocolError)
118
+ end
119
+
120
+ it "should emit stream object for received PUSH_PROMISE" do
121
+ s = @conn.new_stream
122
+ s.send HEADERS
123
+
124
+ promise = nil
125
+ @conn.on(:promise) {|s| promise = s }
126
+ @conn << set_stream_id(f.generate(PUSH_PROMISE), s.id)
127
+
128
+ promise.id.should eq 2
129
+ promise.state.should eq :reserved_remote
130
+ end
131
+
132
+ it "should auto RST_STREAM promises against locally-RST stream" do
133
+ s = @conn.new_stream
134
+ s.send HEADERS
135
+ s.close
136
+
137
+ @conn.stub(:send)
138
+ @conn.should_receive(:send) do |frame|
139
+ frame[:type].should eq :rst_stream
140
+ frame[:stream].should eq 2
141
+ end
142
+
143
+ @conn << set_stream_id(f.generate(PUSH_PROMISE), s.id)
144
+ end
145
+ end
146
+ end
147
+
148
+ context "flow control" do
149
+ it "should initialize to default flow window" do
150
+ @conn.window.should eq DEFAULT_FLOW_WINDOW
151
+ end
152
+
153
+ it "should update connection and stream windows on SETTINGS" do
154
+ settings, data = SETTINGS.dup, DATA.dup
155
+ settings[:payload] = { settings_initial_window_size: 1024 }
156
+ data[:payload] = 'x'*2048
157
+
158
+ stream = @conn.new_stream
159
+
160
+ stream.send HEADERS
161
+ stream.send data
162
+ stream.window.should eq (DEFAULT_FLOW_WINDOW - 2048)
163
+ @conn.window.should eq (DEFAULT_FLOW_WINDOW - 2048)
164
+
165
+ @conn << f.generate(settings)
166
+ @conn.window.should eq -1024
167
+ stream.window.should eq -1024
168
+ end
169
+
170
+ it "should initialize streams with window specified by peer" do
171
+ settings = SETTINGS.dup
172
+ settings[:payload] = { settings_initial_window_size: 1024 }
173
+
174
+ @conn << f.generate(settings)
175
+ @conn.new_stream.window.should eq 1024
176
+ end
177
+
178
+ it "should support global disable of flow control" do
179
+ @conn << f.generate(SETTINGS)
180
+ @conn.window.should eq Float::INFINITY
181
+ end
182
+
183
+ it "should raise error on flow control after disabling it" do
184
+ expect { @conn << f.generate(SETTINGS) }.to_not raise_error
185
+ expect {
186
+ [WINDOW_UPDATE, SETTINGS].each do |frame|
187
+ @conn.dup << f.generate(frame)
188
+ end
189
+ }.to raise_error(FlowControlError)
190
+ end
191
+
192
+ it "should observe connection flow control" do
193
+ settings, data = SETTINGS.dup, DATA.dup
194
+ settings[:payload] = { settings_initial_window_size: 1000 }
195
+
196
+ @conn << f.generate(settings)
197
+ s1 = @conn.new_stream
198
+ s2 = @conn.new_stream
199
+
200
+ s1.send HEADERS
201
+ s1.send data.merge({payload: "x" * 900})
202
+ @conn.window.should eq 100
203
+
204
+ s2.send HEADERS
205
+ s2.send data.merge({payload: "x" * 200})
206
+ @conn.window.should eq 0
207
+ @conn.buffered_amount.should eq 100
208
+
209
+ @conn << f.generate(WINDOW_UPDATE.merge({stream: 0, increment: 1000}))
210
+ @conn.buffered_amount.should eq 0
211
+ @conn.window.should eq 900
212
+ end
213
+ end
214
+
215
+ context "framing" do
216
+ it "should buffer incomplete frames" do
217
+ settings = SETTINGS.dup
218
+ settings[:payload] = { settings_initial_window_size: 1000 }
219
+ @conn << f.generate(settings)
220
+
221
+ frame = f.generate(WINDOW_UPDATE.merge({stream: 0, increment: 1000}))
222
+ @conn << frame
223
+ @conn.window.should eq 2000
224
+
225
+ @conn << frame.slice!(0,1)
226
+ @conn << frame
227
+ @conn.window.should eq 3000
228
+ end
229
+
230
+ it "should decompress header blocks regardless of stream state" do
231
+ req_headers = [
232
+ ["content-length", "20"],
233
+ ["x-my-header", "first"]
234
+ ]
235
+
236
+ cc = Compressor.new(:response)
237
+ headers = HEADERS.dup
238
+ headers[:payload] = cc.encode(req_headers)
239
+
240
+ @conn << f.generate(SETTINGS)
241
+ @conn.on(:stream) do |stream|
242
+ stream.should_receive(:<<) do |frame|
243
+ frame[:payload].should eq req_headers
244
+ end
245
+ end
246
+
247
+ @conn << f.generate(headers)
248
+ end
249
+
250
+ it "should decode non-contiguous header blocks" do
251
+ req_headers = [
252
+ ["content-length", "15"],
253
+ ["x-my-header", "first"]
254
+ ]
255
+
256
+ cc = Compressor.new(:response)
257
+ h1, h2 = HEADERS.dup, CONTINUATION.dup
258
+ h1[:payload] = cc.encode([req_headers.first])
259
+ h1[:stream] = 5
260
+ h1[:flags] = []
261
+
262
+ h2[:payload] = cc.encode([req_headers.last])
263
+ h2[:stream] = 5
264
+
265
+ @conn << f.generate(SETTINGS)
266
+ @conn.on(:stream) do |stream|
267
+ stream.should_receive(:<<) do |frame|
268
+ frame[:payload].should eq req_headers
269
+ end
270
+ end
271
+
272
+ @conn << f.generate(h1)
273
+ @conn << f.generate(h2)
274
+ end
275
+
276
+ it "should require that split header blocks are a contiguous sequence" do
277
+ headers, continutation = HEADERS.dup, CONTINUATION.dup
278
+ headers[:flags] = []
279
+
280
+ @conn << f.generate(SETTINGS)
281
+ @conn << f.generate(headers)
282
+ (FRAME_TYPES - [CONTINUATION]).each do |frame|
283
+ expect { @conn << f.generate(frame) }.to raise_error(ProtocolError)
284
+ end
285
+ end
286
+
287
+ it "should raise connection error on encode exception" do
288
+ @conn << f.generate(SETTINGS)
289
+ stream = @conn.new_stream
290
+
291
+ expect {
292
+ stream.headers({"name" => Float::INFINITY})
293
+ }.to raise_error(CompressionError)
294
+ end
295
+
296
+ it "should raise connection error on decode exception" do
297
+ @conn << f.generate(SETTINGS)
298
+
299
+ headers = HEADERS.dup
300
+ headers[:payload] = [0x44, 0x16].pack("C*")
301
+ expect { @conn << f.generate(headers) }.to raise_error(CompressionError)
302
+ end
303
+
304
+ it "should emit encoded frames via on(:frame)" do
305
+ bytes = nil
306
+ @conn.on(:frame) {|d| bytes = d }
307
+ @conn.settings({
308
+ settings_max_concurrent_streams: 10,
309
+ settings_flow_control_options: 1
310
+ })
311
+
312
+ bytes.should eq f.generate(SETTINGS)
313
+ end
314
+
315
+ it "should compress stream headers" do
316
+ @conn.on(:frame) do |bytes|
317
+ bytes.force_encoding('binary')
318
+ bytes.should_not match('get')
319
+ bytes.should_not match('http')
320
+ bytes.should match('www.example.org')
321
+ end
322
+
323
+ stream = @conn.new_stream
324
+ stream.headers({
325
+ ':method' => 'get',
326
+ ':scheme' => 'http',
327
+ ':host' => 'www.example.org',
328
+ ':path' => '/resource'
329
+ })
330
+ end
331
+ end
332
+
333
+ context "connection management" do
334
+ it "should respond to PING frames" do
335
+ @conn << f.generate(SETTINGS)
336
+ @conn.should_receive(:send) do |frame|
337
+ frame[:type].should eq :ping
338
+ frame[:flags].should eq [:pong]
339
+ frame[:payload].should eq "12345678"
340
+ end
341
+
342
+ @conn << f.generate(PING)
343
+ end
344
+
345
+ it "should fire callback on PONG" do
346
+ @conn << f.generate(SETTINGS)
347
+
348
+ pong = nil
349
+ @conn.ping("12345678") {|d| pong = d }
350
+ @conn << f.generate(PONG)
351
+ pong.should eq "12345678"
352
+ end
353
+
354
+ it "should fire callback on receipt of GOAWAY" do
355
+ last_stream, payload, error = nil
356
+ @conn << f.generate(SETTINGS)
357
+ @conn.on(:goaway) {|s,e,p| last_stream = s; error = e; payload = p}
358
+ @conn << f.generate(GOAWAY.merge({last_stream: 17, payload: "test"}))
359
+
360
+ last_stream.should eq 17
361
+ error.should eq :no_error
362
+ payload.should eq "test"
363
+ end
364
+
365
+ it "should raise error when opening new stream after sending GOAWAY" do
366
+ @conn.goaway
367
+ expect { @conn.new_stream }.to raise_error(ConnectionClosed)
368
+ end
369
+
370
+ it "should raise error when opening new stream after receiving GOAWAY" do
371
+ @conn << f.generate(SETTINGS)
372
+ @conn << f.generate(GOAWAY)
373
+ expect { @conn.new_stream }.to raise_error(ConnectionClosed)
374
+ end
375
+
376
+ it "should process connection management frames after GOAWAY" do
377
+ @conn << f.generate(SETTINGS)
378
+ @conn << f.generate(HEADERS)
379
+ @conn << f.generate(GOAWAY)
380
+ @conn << f.generate(HEADERS.merge({stream: 7}))
381
+ @conn << f.generate(PUSH_PROMISE)
382
+
383
+ @conn.active_stream_count.should eq 1
384
+ end
385
+
386
+ it "should raise error on frame for invalid stream ID" do
387
+ @conn << f.generate(SETTINGS)
388
+
389
+ expect {
390
+ @conn << f.generate(DATA.dup.merge({:stream => 31}))
391
+ }.to raise_error(ProtocolError)
392
+ end
393
+
394
+ it "should send GOAWAY frame on connection error" do
395
+ stream = @conn.new_stream
396
+
397
+ @conn.stub(:encode)
398
+ @conn.should_receive(:encode) do |frame|
399
+ frame[:type].should eq :goaway
400
+ frame[:last_stream].should eq stream.id
401
+ frame[:error].should eq :protocol_error
402
+ end
403
+
404
+ @conn << f.generate(SETTINGS)
405
+ expect { @conn << f.generate(DATA) }.to raise_error(ProtocolError)
406
+ end
407
+ end
408
+
409
+ context "API" do
410
+ it ".settings should emit SETTINGS frames" do
411
+ settings = {
412
+ settings_max_concurrent_streams: 10,
413
+ settings_flow_control_options: 1
414
+ }
415
+
416
+ @conn.should_receive(:send) do |frame|
417
+ frame[:type].should eq :settings
418
+ frame[:payload].should eq settings
419
+ frame[:stream].should eq 0
420
+ end
421
+
422
+ @conn.settings settings
423
+ end
424
+
425
+ it ".ping should generate PING frames" do
426
+ @conn.should_receive(:send) do |frame|
427
+ frame[:type].should eq :ping
428
+ frame[:payload].should eq "somedata"
429
+ end
430
+
431
+ @conn.ping("somedata")
432
+ end
433
+
434
+ it ".goaway should generate GOAWAY frame with last processed stream ID" do
435
+ @conn << f.generate(SETTINGS)
436
+ @conn << f.generate(HEADERS.merge({stream: 17}))
437
+
438
+ @conn.should_receive(:send) do |frame|
439
+ frame[:type].should eq :goaway
440
+ frame[:last_stream].should eq 17
441
+ frame[:error].should eq :internal_error
442
+ frame[:payload].should eq "payload"
443
+ end
444
+
445
+ @conn.goaway(:internal_error, "payload")
446
+ end
447
+ end
448
+ end
@@ -0,0 +1,46 @@
1
+ require "helper"
2
+
3
+ describe HTTP2::Emitter do
4
+ class Worker
5
+ include Emitter
6
+ end
7
+
8
+ before(:each) do
9
+ @w = Worker.new
10
+ @cnt = 0
11
+ end
12
+
13
+ it "should raise error on missing callback" do
14
+ expect { @w.on(:a) {} }.to_not raise_error
15
+ expect { @w.on(:a) }.to raise_error
16
+ end
17
+
18
+ it "should allow multiple callbacks on single event" do
19
+ @w.on(:a) { @cnt += 1 }
20
+ @w.on(:a) { @cnt += 1 }
21
+ @w.emit(:a)
22
+
23
+ @cnt.should eq 2
24
+ end
25
+
26
+ it "should execute callback with optional args" do
27
+ args = nil
28
+ @w.on(:a) { |a| args = a }
29
+ @w.emit(:a, 123)
30
+
31
+ args.should eq 123
32
+ end
33
+
34
+ it "should allow events with no callbacks" do
35
+ expect { @w.emit(:missing) }.to_not raise_error
36
+ end
37
+
38
+ it "should execute callback exactly once" do
39
+ @w.on(:a) { @cnt += 1 }
40
+ @w.once(:a) { @cnt += 1 }
41
+ @w.emit(:a)
42
+ @w.emit(:a)
43
+
44
+ @cnt.should eq 3
45
+ end
46
+ end