http-2 0.6.1

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.
@@ -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