http-2 0.10.2 → 0.12.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +0 -2
  3. data/lib/http/2/buffer.rb +6 -4
  4. data/lib/http/2/client.rb +5 -1
  5. data/lib/http/2/compressor.rb +44 -36
  6. data/lib/http/2/connection.rb +76 -89
  7. data/lib/http/2/emitter.rb +4 -1
  8. data/lib/http/2/error.rb +2 -0
  9. data/lib/http/2/flow_buffer.rb +8 -3
  10. data/lib/http/2/framer.rb +83 -94
  11. data/lib/http/2/huffman.rb +19 -17
  12. data/lib/http/2/server.rb +9 -7
  13. data/lib/http/2/stream.rb +48 -48
  14. data/lib/http/2/version.rb +3 -1
  15. data/lib/http/2.rb +2 -0
  16. metadata +10 -63
  17. data/.autotest +0 -20
  18. data/.coveralls.yml +0 -1
  19. data/.gitignore +0 -20
  20. data/.gitmodules +0 -3
  21. data/.rspec +0 -5
  22. data/.rubocop.yml +0 -93
  23. data/.rubocop_todo.yml +0 -122
  24. data/.travis.yml +0 -14
  25. data/Gemfile +0 -16
  26. data/Guardfile +0 -18
  27. data/Guardfile.h2spec +0 -12
  28. data/Rakefile +0 -49
  29. data/example/Gemfile +0 -3
  30. data/example/README.md +0 -44
  31. data/example/client.rb +0 -122
  32. data/example/helper.rb +0 -19
  33. data/example/keys/server.crt +0 -20
  34. data/example/keys/server.key +0 -27
  35. data/example/server.rb +0 -139
  36. data/example/upgrade_client.rb +0 -153
  37. data/example/upgrade_server.rb +0 -203
  38. data/http-2.gemspec +0 -22
  39. data/lib/tasks/generate_huffman_table.rb +0 -166
  40. data/spec/buffer_spec.rb +0 -28
  41. data/spec/client_spec.rb +0 -188
  42. data/spec/compressor_spec.rb +0 -666
  43. data/spec/connection_spec.rb +0 -665
  44. data/spec/emitter_spec.rb +0 -54
  45. data/spec/framer_spec.rb +0 -487
  46. data/spec/h2spec/h2spec.darwin +0 -0
  47. data/spec/h2spec/output/non_secure.txt +0 -317
  48. data/spec/helper.rb +0 -147
  49. data/spec/hpack_test_spec.rb +0 -84
  50. data/spec/huffman_spec.rb +0 -68
  51. data/spec/server_spec.rb +0 -52
  52. data/spec/stream_spec.rb +0 -878
  53. data/spec/support/deep_dup.rb +0 -55
  54. data/spec/support/duplicable.rb +0 -98
@@ -1,665 +0,0 @@
1
- require 'helper'
2
-
3
- RSpec.describe HTTP2::Connection do
4
- include FrameHelpers
5
- before(:each) do
6
- @conn = Client.new
7
- end
8
-
9
- let(:f) { Framer.new }
10
-
11
- context 'initialization and settings' do
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
16
- end
17
- end
18
-
19
- it 'should not raise error if first frame is SETTINGS' do
20
- expect { @conn << f.generate(settings_frame) }.to_not raise_error
21
- expect(@conn.state).to eq :connected
22
- expect(@conn).to_not be_closed
23
- end
24
-
25
- it 'should raise error if SETTINGS stream != 0' do
26
- frame = set_stream_id(f.generate(settings_frame), 0x1)
27
- expect { @conn << frame }.to raise_error(ProtocolError)
28
- end
29
- end
30
-
31
- context 'settings synchronization' do
32
- it 'should reflect outgoing settings when ack is received' do
33
- expect(@conn.local_settings[:settings_header_table_size]).to eq 4096
34
- @conn.settings(settings_header_table_size: 256)
35
- expect(@conn.local_settings[:settings_header_table_size]).to eq 4096
36
-
37
- ack = { type: :settings, stream: 0, payload: [], flags: [:ack] }
38
- @conn << f.generate(ack)
39
-
40
- expect(@conn.local_settings[:settings_header_table_size]).to eq 256
41
- end
42
-
43
- it 'should reflect incoming settings when SETTINGS is received' do
44
- expect(@conn.remote_settings[:settings_header_table_size]).to eq 4096
45
- settings = settings_frame
46
- settings[:payload] = [[:settings_header_table_size, 256]]
47
-
48
- @conn << f.generate(settings)
49
-
50
- expect(@conn.remote_settings[:settings_header_table_size]).to eq 256
51
- end
52
-
53
- it 'should send SETTINGS ACK when SETTINGS is received' do
54
- settings = settings_frame
55
- settings[:payload] = [[:settings_header_table_size, 256]]
56
-
57
- # We should expect two frames here (append .twice) - one for the connection setup, and one for the settings ack.
58
- frames = []
59
- expect(@conn).to receive(:send).twice do |frame|
60
- frames << frame
61
- end
62
-
63
- @conn.send_connection_preface
64
- @conn << f.generate(settings)
65
-
66
- frame = frames.last
67
- expect(frame[:type]).to eq :settings
68
- expect(frame[:flags]).to eq [:ack]
69
- expect(frame[:payload]).to eq []
70
- end
71
- end
72
-
73
- context 'stream management' do
74
- it 'should initialize to default stream limit (100)' do
75
- expect(@conn.local_settings[:settings_max_concurrent_streams]).to eq 100
76
- end
77
-
78
- it 'should change stream limit to received SETTINGS value' do
79
- @conn << f.generate(settings_frame)
80
- expect(@conn.remote_settings[:settings_max_concurrent_streams]).to eq 10
81
- end
82
-
83
- it 'should count open streams against stream limit' do
84
- s = @conn.new_stream
85
- expect(@conn.active_stream_count).to eq 0
86
- s.receive headers_frame
87
- expect(@conn.active_stream_count).to eq 1
88
- end
89
-
90
- it 'should not count reserved streams against stream limit' do
91
- s1 = @conn.new_stream
92
- s1.receive push_promise_frame
93
- expect(@conn.active_stream_count).to eq 0
94
-
95
- s2 = @conn.new_stream
96
- s2.send push_promise_frame
97
- expect(@conn.active_stream_count).to eq 0
98
-
99
- # transition to half closed
100
- s1.receive headers_frame
101
- s2.send headers_frame
102
- expect(@conn.active_stream_count).to eq 2
103
-
104
- # transition to closed
105
- s1.receive data_frame
106
- s2.send data_frame
107
- expect(@conn.active_stream_count).to eq 0
108
-
109
- expect(s1).to be_closed
110
- expect(s2).to be_closed
111
- end
112
-
113
- it 'should not exceed stream limit set by peer' do
114
- @conn << f.generate(settings_frame)
115
-
116
- expect do
117
- 10.times do
118
- s = @conn.new_stream
119
- s.send headers_frame
120
- end
121
- end.to_not raise_error
122
-
123
- expect { @conn.new_stream }.to raise_error(StreamLimitExceeded)
124
- end
125
-
126
- it 'should initialize stream with HEADERS priority value' do
127
- @conn << f.generate(settings_frame)
128
-
129
- stream, headers = nil, headers_frame
130
- headers[:weight] = 20
131
- headers[:stream_dependency] = 0
132
- headers[:exclusive] = false
133
-
134
- @conn.on(:stream) { |s| stream = s }
135
- @conn << f.generate(headers)
136
-
137
- expect(stream.weight).to eq 20
138
- end
139
-
140
- it 'should initialize idle stream on PRIORITY frame' do
141
- @conn << f.generate(settings_frame)
142
-
143
- stream = nil
144
- @conn.on(:stream) { |s| stream = s }
145
- @conn << f.generate(priority_frame)
146
-
147
- expect(stream.state).to eq :idle
148
- end
149
- end
150
-
151
- context 'cleanup_recently_closed' do
152
- it 'should cleanup old connections' do
153
- now_ts = Time.now.to_i
154
- stream_ids = Array.new(4) { @conn.new_stream.id }
155
- expect(@conn.instance_variable_get('@streams').size).to eq(4)
156
-
157
- # Assume that the first 3 streams were closed in different time
158
- recently_closed = stream_ids[0, 3].zip([now_ts - 100, now_ts - 50, now_ts - 5]).to_h
159
- @conn.instance_variable_set('@streams_recently_closed', recently_closed)
160
-
161
- # Cleanup should delete streams that were closed earlier than 15s ago
162
- @conn.__send__(:cleanup_recently_closed)
163
- expect(@conn.instance_variable_get('@streams').size).to eq(2)
164
- expect(@conn.instance_variable_get('@streams_recently_closed')).to eq(stream_ids[2] => now_ts - 5)
165
- end
166
- end
167
-
168
- context 'Headers pre/post processing' do
169
- it 'should not concatenate multiple occurences of a header field with the same name' do
170
- input = [
171
- ['Content-Type', 'text/html'],
172
- ['Cache-Control', 'max-age=60, private'],
173
- ['Cache-Control', 'must-revalidate'],
174
- ]
175
- expected = [
176
- ['content-type', 'text/html'],
177
- ['cache-control', 'max-age=60, private'],
178
- ['cache-control', 'must-revalidate'],
179
- ]
180
- headers = []
181
- @conn.on(:frame) do |bytes|
182
- headers << f.parse(bytes) if [1, 5, 9].include?(bytes[3].ord)
183
- end
184
-
185
- stream = @conn.new_stream
186
- stream.headers(input)
187
-
188
- expect(headers.size).to eq 1
189
- emitted = Decompressor.new.decode(headers.first[:payload])
190
- expect(emitted).to match_array(expected)
191
- end
192
-
193
- it 'should not split zero-concatenated header field values' do
194
- input = [*RESPONSE_HEADERS,
195
- ['cache-control', "max-age=60, private\0must-revalidate"],
196
- ['content-type', 'text/html'],
197
- ['cookie', "a=b\0c=d; e=f"]]
198
- expected = [*RESPONSE_HEADERS,
199
- ['cache-control', "max-age=60, private\0must-revalidate"],
200
- ['content-type', 'text/html'],
201
- ['cookie', "a=b\0c=d; e=f"]]
202
-
203
- result = nil
204
- @conn.on(:stream) do |stream|
205
- stream.on(:headers) { |h| result = h }
206
- end
207
-
208
- srv = Server.new
209
- srv.on(:frame) { |bytes| @conn << bytes }
210
- stream = srv.new_stream
211
- stream.headers(input)
212
-
213
- expect(result).to eq expected
214
- end
215
- end
216
-
217
- context 'flow control' do
218
- it 'should initialize to default flow window' do
219
- expect(@conn.remote_window).to eq DEFAULT_FLOW_WINDOW
220
- end
221
-
222
- it 'should update connection and stream windows on SETTINGS' do
223
- settings, data = settings_frame, data_frame
224
- settings[:payload] = [[:settings_initial_window_size, 1024]]
225
- data[:payload] = 'x' * 2048
226
-
227
- stream = @conn.new_stream
228
-
229
- stream.send headers_frame
230
- stream.send data
231
- expect(stream.remote_window).to eq(DEFAULT_FLOW_WINDOW - 2048)
232
- expect(@conn.remote_window).to eq(DEFAULT_FLOW_WINDOW - 2048)
233
-
234
- @conn << f.generate(settings)
235
- expect(@conn.remote_window).to eq(-1024)
236
- expect(stream.remote_window).to eq(-1024)
237
- end
238
-
239
- it 'should initialize streams with window specified by peer' do
240
- settings = settings_frame
241
- settings[:payload] = [[:settings_initial_window_size, 1024]]
242
-
243
- @conn << f.generate(settings)
244
- expect(@conn.new_stream.remote_window).to eq 1024
245
- end
246
-
247
- it 'should observe connection flow control' do
248
- settings, data = settings_frame, data_frame
249
- settings[:payload] = [[:settings_initial_window_size, 1000]]
250
-
251
- @conn << f.generate(settings)
252
- s1 = @conn.new_stream
253
- s2 = @conn.new_stream
254
-
255
- s1.send headers_frame
256
- s1.send data.merge(payload: 'x' * 900)
257
- expect(@conn.remote_window).to eq 100
258
-
259
- s2.send headers_frame
260
- s2.send data.merge(payload: 'x' * 200)
261
- expect(@conn.remote_window).to eq 0
262
- expect(@conn.buffered_amount).to eq 100
263
-
264
- @conn << f.generate(window_update_frame.merge(stream: 0, increment: 1000))
265
- expect(@conn.buffered_amount).to eq 0
266
- expect(@conn.remote_window).to eq 900
267
- end
268
-
269
- it 'should update window when data received is over half of the maximum local window size' do
270
- settings, data = settings_frame, data_frame
271
- conn = Client.new(settings_initial_window_size: 500)
272
-
273
- conn.receive f.generate(settings)
274
- s1 = conn.new_stream
275
- s2 = conn.new_stream
276
-
277
- s1.send headers_frame
278
- s2.send headers_frame
279
- expect(conn).to receive(:send) do |frame|
280
- expect(frame[:type]).to eq :window_update
281
- expect(frame[:stream]).to eq 0
282
- expect(frame[:increment]).to eq 400
283
- end
284
- conn.receive f.generate(data.merge(payload: 'x' * 200, end_stream: false, stream: s1.id))
285
- conn.receive f.generate(data.merge(payload: 'x' * 200, end_stream: false, stream: s2.id))
286
- expect(s1.local_window).to eq 300
287
- expect(s2.local_window).to eq 300
288
- expect(conn.local_window).to eq 500
289
- end
290
- end
291
-
292
- context 'framing' do
293
- it 'should buffer incomplete frames' do
294
- settings = settings_frame
295
- settings[:payload] = [[:settings_initial_window_size, 1000]]
296
- @conn << f.generate(settings)
297
-
298
- frame = f.generate(window_update_frame.merge(stream: 0, increment: 1000))
299
- @conn << frame
300
- expect(@conn.remote_window).to eq 2000
301
-
302
- @conn << frame.slice!(0, 1)
303
- @conn << frame
304
- expect(@conn.remote_window).to eq 3000
305
- end
306
-
307
- it 'should decompress header blocks regardless of stream state' do
308
- req_headers = [
309
- ['content-length', '20'],
310
- ['x-my-header', 'first'],
311
- ]
312
-
313
- cc = Compressor.new
314
- headers = headers_frame
315
- headers[:payload] = cc.encode(req_headers)
316
-
317
- @conn << f.generate(settings_frame)
318
- @conn.on(:stream) do |stream|
319
- expect(stream).to receive(:<<) do |frame|
320
- expect(frame[:payload]).to eq req_headers
321
- end
322
- end
323
-
324
- @conn << f.generate(headers)
325
- end
326
-
327
- it 'should decode non-contiguous header blocks' do
328
- req_headers = [
329
- ['content-length', '15'],
330
- ['x-my-header', 'first'],
331
- ]
332
-
333
- cc = Compressor.new
334
- h1, h2 = headers_frame, continuation_frame
335
-
336
- # Header block fragment might not complete for decompression
337
- payload = cc.encode(req_headers)
338
- h1[:payload] = payload.slice!(0, payload.size / 2) # first half
339
- h1[:stream] = 5
340
- h1[:flags] = []
341
-
342
- h2[:payload] = payload # the remaining
343
- h2[:stream] = 5
344
-
345
- @conn << f.generate(settings_frame)
346
- @conn.on(:stream) do |stream|
347
- expect(stream).to receive(:<<) do |frame|
348
- expect(frame[:payload]).to eq req_headers
349
- end
350
- end
351
-
352
- @conn << f.generate(h1)
353
- @conn << f.generate(h2)
354
- end
355
-
356
- it 'should require that split header blocks are a contiguous sequence' do
357
- headers = headers_frame
358
- headers[:flags] = []
359
-
360
- @conn << f.generate(settings_frame)
361
- @conn << f.generate(headers)
362
- (frame_types - [continuation_frame]).each do |frame|
363
- expect { @conn << f.generate(frame.deep_dup) }.to raise_error(ProtocolError)
364
- end
365
- end
366
-
367
- it 'should raise compression error on encode of invalid frame' do
368
- @conn << f.generate(settings_frame)
369
- stream = @conn.new_stream
370
-
371
- expect do
372
- stream.headers('name' => Float::INFINITY)
373
- end.to raise_error(CompressionError)
374
- end
375
-
376
- it 'should raise connection error on decode of invalid frame' do
377
- @conn << f.generate(settings_frame)
378
- frame = f.generate(data_frame) # Receiving DATA on unopened stream 1 is an error.
379
- # Connection errors emit protocol error frames
380
- expect { @conn << frame }.to raise_error(ProtocolError)
381
- end
382
-
383
- it 'should emit encoded frames via on(:frame)' do
384
- bytes = nil
385
- @conn.on(:frame) { |d| bytes = d }
386
- @conn.settings(settings_max_concurrent_streams: 10,
387
- settings_initial_window_size: 0x7fffffff)
388
-
389
- expect(bytes).to eq f.generate(settings_frame)
390
- end
391
-
392
- it 'should compress stream headers' do
393
- @conn.on(:frame) do |bytes|
394
- expect(bytes).not_to include('get')
395
- expect(bytes).not_to include('http')
396
- expect(bytes).not_to include('www.example.org') # should be huffman encoded
397
- end
398
-
399
- stream = @conn.new_stream
400
- stream.headers(':method' => 'get',
401
- ':scheme' => 'http',
402
- ':authority' => 'www.example.org',
403
- ':path' => '/resource')
404
- end
405
-
406
- it 'should generate CONTINUATION if HEADERS is too long' do
407
- headers = []
408
- @conn.on(:frame) do |bytes|
409
- # bytes[3]: frame's type field
410
- headers << f.parse(bytes) if [1, 5, 9].include?(bytes[3].ord)
411
- end
412
-
413
- stream = @conn.new_stream
414
- stream.headers({
415
- ':method' => 'get',
416
- ':scheme' => 'http',
417
- ':authority' => 'www.example.org',
418
- ':path' => '/resource',
419
- 'custom' => 'q' * 44_000,
420
- }, end_stream: true)
421
- expect(headers.size).to eq 3
422
- expect(headers[0][:type]).to eq :headers
423
- expect(headers[1][:type]).to eq :continuation
424
- expect(headers[2][:type]).to eq :continuation
425
- expect(headers[0][:flags]).to eq [:end_stream]
426
- expect(headers[1][:flags]).to eq []
427
- expect(headers[2][:flags]).to eq [:end_headers]
428
- end
429
-
430
- it 'should not generate CONTINUATION if HEADERS fits exactly in a frame' do
431
- headers = []
432
- @conn.on(:frame) do |bytes|
433
- # bytes[3]: frame's type field
434
- headers << f.parse(bytes) if [1, 5, 9].include?(bytes[3].ord)
435
- end
436
-
437
- stream = @conn.new_stream
438
- stream.headers({
439
- ':method' => 'get',
440
- ':scheme' => 'http',
441
- ':authority' => 'www.example.org',
442
- ':path' => '/resource',
443
- 'custom' => 'q' * 18_682, # this number should be updated when Huffman table is changed
444
- }, end_stream: true)
445
- expect(headers[0][:length]).to eq @conn.remote_settings[:settings_max_frame_size]
446
- expect(headers.size).to eq 1
447
- expect(headers[0][:type]).to eq :headers
448
- expect(headers[0][:flags]).to include(:end_headers)
449
- expect(headers[0][:flags]).to include(:end_stream)
450
- end
451
-
452
- it 'should not generate CONTINUATION if HEADERS fits exactly in a frame' do
453
- headers = []
454
- @conn.on(:frame) do |bytes|
455
- # bytes[3]: frame's type field
456
- headers << f.parse(bytes) if [1, 5, 9].include?(bytes[3].ord)
457
- end
458
-
459
- stream = @conn.new_stream
460
- stream.headers({
461
- ':method' => 'get',
462
- ':scheme' => 'http',
463
- ':authority' => 'www.example.org',
464
- ':path' => '/resource',
465
- 'custom' => 'q' * 18_682, # this number should be updated when Huffman table is changed
466
- }, end_stream: true)
467
- expect(headers[0][:length]).to eq @conn.remote_settings[:settings_max_frame_size]
468
- expect(headers.size).to eq 1
469
- expect(headers[0][:type]).to eq :headers
470
- expect(headers[0][:flags]).to include(:end_headers)
471
- expect(headers[0][:flags]).to include(:end_stream)
472
- end
473
-
474
- it 'should generate CONTINUATION if HEADERS exceed the max payload by one byte' do
475
- headers = []
476
- @conn.on(:frame) do |bytes|
477
- headers << f.parse(bytes) if [1, 5, 9].include?(bytes[3].ord)
478
- end
479
-
480
- stream = @conn.new_stream
481
- stream.headers({
482
- ':method' => 'get',
483
- ':scheme' => 'http',
484
- ':authority' => 'www.example.org',
485
- ':path' => '/resource',
486
- 'custom' => 'q' * 18_683, # this number should be updated when Huffman table is changed
487
- }, end_stream: true)
488
- expect(headers[0][:length]).to eq @conn.remote_settings[:settings_max_frame_size]
489
- expect(headers[1][:length]).to eq 1
490
- expect(headers.size).to eq 2
491
- expect(headers[0][:type]).to eq :headers
492
- expect(headers[1][:type]).to eq :continuation
493
- expect(headers[0][:flags]).to eq [:end_stream]
494
- expect(headers[1][:flags]).to eq [:end_headers]
495
- end
496
- end
497
-
498
- context 'connection management' do
499
- it 'should raise error on invalid connection header' do
500
- srv = Server.new
501
- expect { srv << f.generate(settings_frame) }.to raise_error(HandshakeError)
502
-
503
- srv = Server.new
504
- expect do
505
- srv << CONNECTION_PREFACE_MAGIC
506
- srv << f.generate(settings_frame)
507
- end.to_not raise_error
508
- end
509
-
510
- it 'should respond to PING frames' do
511
- @conn << f.generate(settings_frame)
512
- expect(@conn).to receive(:send) do |frame|
513
- expect(frame[:type]).to eq :ping
514
- expect(frame[:flags]).to eq [:ack]
515
- expect(frame[:payload]).to eq '12345678'
516
- end
517
-
518
- @conn << f.generate(ping_frame)
519
- end
520
-
521
- it 'should fire callback on PONG' do
522
- @conn << f.generate(settings_frame)
523
-
524
- pong = nil
525
- @conn.ping('12345678') { |d| pong = d }
526
- @conn << f.generate(pong_frame)
527
- expect(pong).to eq '12345678'
528
- end
529
-
530
- it 'should fire callback on receipt of GOAWAY' do
531
- last_stream, payload, error = nil
532
- @conn << f.generate(settings_frame)
533
- @conn.on(:goaway) do |s, e, p|
534
- last_stream = s
535
- error = e
536
- payload = p
537
- end
538
- @conn << f.generate(goaway_frame.merge(last_stream: 17, payload: 'test'))
539
-
540
- expect(last_stream).to eq 17
541
- expect(error).to eq :no_error
542
- expect(payload).to eq 'test'
543
-
544
- expect(@conn).to be_closed
545
- end
546
-
547
- it 'should raise error when opening new stream after sending GOAWAY' do
548
- @conn.goaway
549
- expect(@conn).to be_closed
550
-
551
- expect { @conn.new_stream }.to raise_error(ConnectionClosed)
552
- end
553
-
554
- it 'should raise error when opening new stream after receiving GOAWAY' do
555
- @conn << f.generate(settings_frame)
556
- @conn << f.generate(goaway_frame)
557
- expect { @conn.new_stream }.to raise_error(ConnectionClosed)
558
- end
559
-
560
- it 'should not raise error when receiving connection management frames immediately after emitting goaway' do
561
- @conn.goaway
562
- expect(@conn).to be_closed
563
-
564
- expect { @conn << f.generate(settings_frame) }.not_to raise_error(ProtocolError)
565
- expect { @conn << f.generate(ping_frame) }.not_to raise_error(ProtocolError)
566
- expect { @conn << f.generate(goaway_frame) }.not_to raise_error(ProtocolError)
567
- end
568
-
569
- it 'should process connection management frames after GOAWAY' do
570
- @conn << f.generate(settings_frame)
571
- @conn << f.generate(headers_frame)
572
- @conn << f.generate(goaway_frame)
573
- @conn << f.generate(headers_frame.merge(stream: 7))
574
- @conn << f.generate(push_promise_frame)
575
-
576
- expect(@conn.active_stream_count).to eq 1
577
- end
578
-
579
- it 'should raise error on frame for invalid stream ID' do
580
- @conn << f.generate(settings_frame)
581
-
582
- expect do
583
- @conn << f.generate(data_frame.merge(stream: 31))
584
- end.to raise_error(ProtocolError)
585
- end
586
-
587
- it 'should not raise an error on frame for a closed stream ID' do
588
- srv = Server.new
589
- srv << CONNECTION_PREFACE_MAGIC
590
-
591
- stream = srv.new_stream
592
- stream.send headers_frame
593
- stream.send data_frame
594
- stream.close
595
-
596
- expect do
597
- srv << f.generate(rst_stream_frame.merge(stream: stream.id))
598
- end.to_not raise_error
599
- end
600
-
601
- it 'should send GOAWAY frame on connection error' do
602
- stream = @conn.new_stream
603
-
604
- expect(@conn).to receive(:encode) do |frame|
605
- expect(frame[:type]).to eq :settings
606
- [frame]
607
- end
608
- expect(@conn).to receive(:encode) do |frame|
609
- expect(frame[:type]).to eq :goaway
610
- expect(frame[:last_stream]).to eq stream.id
611
- expect(frame[:error]).to eq :protocol_error
612
- [frame]
613
- end
614
-
615
- expect { @conn << f.generate(data_frame) }.to raise_error(ProtocolError)
616
- end
617
- end
618
-
619
- context 'API' do
620
- it '.settings should emit SETTINGS frames' do
621
- expect(@conn).to receive(:send) do |frame|
622
- expect(frame[:type]).to eq :settings
623
- expect(frame[:payload]).to eq([
624
- [:settings_max_concurrent_streams, 10],
625
- [:settings_initial_window_size, 0x7fffffff],
626
- ])
627
- expect(frame[:stream]).to eq 0
628
- end
629
-
630
- @conn.settings(settings_max_concurrent_streams: 10,
631
- settings_initial_window_size: 0x7fffffff)
632
- end
633
-
634
- it '.ping should generate PING frames' do
635
- expect(@conn).to receive(:send) do |frame|
636
- expect(frame[:type]).to eq :ping
637
- expect(frame[:payload]).to eq 'somedata'
638
- end
639
-
640
- @conn.ping('somedata')
641
- end
642
-
643
- it '.goaway should generate GOAWAY frame with last processed stream ID' do
644
- @conn << f.generate(settings_frame)
645
- @conn << f.generate(headers_frame.merge(stream: 17))
646
-
647
- expect(@conn).to receive(:send) do |frame|
648
- expect(frame[:type]).to eq :goaway
649
- expect(frame[:last_stream]).to eq 17
650
- expect(frame[:error]).to eq :internal_error
651
- expect(frame[:payload]).to eq 'payload'
652
- end
653
-
654
- @conn.goaway(:internal_error, 'payload')
655
- end
656
- it '.window_update should emit WINDOW_UPDATE frames' do
657
- expect(@conn).to receive(:send) do |frame|
658
- expect(frame[:type]).to eq :window_update
659
- expect(frame[:increment]).to eq 20
660
- expect(frame[:stream]).to eq 0
661
- end
662
- @conn.window_update(20)
663
- end
664
- end
665
- end