http-2 0.11.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -9
  3. data/lib/http/2/base64.rb +45 -0
  4. data/lib/http/2/client.rb +19 -6
  5. data/lib/http/2/connection.rb +235 -163
  6. data/lib/http/2/emitter.rb +7 -5
  7. data/lib/http/2/error.rb +24 -1
  8. data/lib/http/2/extensions.rb +53 -0
  9. data/lib/http/2/flow_buffer.rb +91 -33
  10. data/lib/http/2/framer.rb +184 -157
  11. data/lib/http/2/header/compressor.rb +157 -0
  12. data/lib/http/2/header/decompressor.rb +144 -0
  13. data/lib/http/2/header/encoding_context.rb +337 -0
  14. data/lib/http/2/{huffman.rb → header/huffman.rb} +25 -19
  15. data/lib/http/2/{huffman_statemachine.rb → header/huffman_statemachine.rb} +2 -0
  16. data/lib/http/2/header.rb +35 -0
  17. data/lib/http/2/server.rb +47 -20
  18. data/lib/http/2/stream.rb +130 -61
  19. data/lib/http/2/version.rb +3 -1
  20. data/lib/http/2.rb +14 -13
  21. data/sig/client.rbs +9 -0
  22. data/sig/connection.rbs +93 -0
  23. data/sig/emitter.rbs +13 -0
  24. data/sig/error.rbs +35 -0
  25. data/sig/extensions.rbs +5 -0
  26. data/sig/flow_buffer.rbs +21 -0
  27. data/sig/frame_buffer.rbs +13 -0
  28. data/sig/framer.rbs +54 -0
  29. data/sig/header/compressor.rbs +27 -0
  30. data/sig/header/decompressor.rbs +22 -0
  31. data/sig/header/encoding_context.rbs +34 -0
  32. data/sig/header/huffman.rbs +9 -0
  33. data/sig/header.rbs +27 -0
  34. data/sig/next.rbs +101 -0
  35. data/sig/server.rbs +12 -0
  36. data/sig/stream.rbs +91 -0
  37. metadata +38 -79
  38. data/.autotest +0 -20
  39. data/.coveralls.yml +0 -1
  40. data/.gitignore +0 -20
  41. data/.gitmodules +0 -3
  42. data/.rspec +0 -5
  43. data/.rubocop.yml +0 -93
  44. data/.rubocop_todo.yml +0 -131
  45. data/.travis.yml +0 -17
  46. data/Gemfile +0 -16
  47. data/Guardfile +0 -18
  48. data/Guardfile.h2spec +0 -12
  49. data/LICENSE +0 -21
  50. data/Rakefile +0 -49
  51. data/example/Gemfile +0 -3
  52. data/example/README.md +0 -44
  53. data/example/client.rb +0 -122
  54. data/example/helper.rb +0 -19
  55. data/example/keys/server.crt +0 -20
  56. data/example/keys/server.key +0 -27
  57. data/example/server.rb +0 -139
  58. data/example/upgrade_client.rb +0 -153
  59. data/example/upgrade_server.rb +0 -203
  60. data/http-2.gemspec +0 -22
  61. data/lib/http/2/buffer.rb +0 -76
  62. data/lib/http/2/compressor.rb +0 -572
  63. data/lib/tasks/generate_huffman_table.rb +0 -166
  64. data/spec/buffer_spec.rb +0 -28
  65. data/spec/client_spec.rb +0 -188
  66. data/spec/compressor_spec.rb +0 -666
  67. data/spec/connection_spec.rb +0 -681
  68. data/spec/emitter_spec.rb +0 -54
  69. data/spec/framer_spec.rb +0 -487
  70. data/spec/h2spec/h2spec.darwin +0 -0
  71. data/spec/h2spec/output/non_secure.txt +0 -317
  72. data/spec/helper.rb +0 -147
  73. data/spec/hpack_test_spec.rb +0 -84
  74. data/spec/huffman_spec.rb +0 -68
  75. data/spec/server_spec.rb +0 -52
  76. data/spec/stream_spec.rb +0 -878
  77. data/spec/support/deep_dup.rb +0 -55
  78. data/spec/support/duplicable.rb +0 -98
data/spec/emitter_spec.rb DELETED
@@ -1,54 +0,0 @@
1
- require 'helper'
2
-
3
- RSpec.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
- expect(@cnt).to 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
- expect(args).to eq 123
32
- end
33
-
34
- it 'should pass emitted callbacks to listeners' do
35
- @w.on(:a) { |&block| block.call }
36
- @w.once(:a) { |&block| block.call }
37
- @w.emit(:a) { @cnt += 1 }
38
-
39
- expect(@cnt).to eq 2
40
- end
41
-
42
- it 'should allow events with no callbacks' do
43
- expect { @w.emit(:missing) }.to_not raise_error
44
- end
45
-
46
- it 'should execute callback exactly once' do
47
- @w.on(:a) { @cnt += 1 }
48
- @w.once(:a) { @cnt += 1 }
49
- @w.emit(:a)
50
- @w.emit(:a)
51
-
52
- expect(@cnt).to eq 3
53
- end
54
- end
data/spec/framer_spec.rb DELETED
@@ -1,487 +0,0 @@
1
- require 'helper'
2
-
3
- RSpec.describe HTTP2::Framer do
4
- let(:f) { Framer.new }
5
-
6
- context 'common header' do
7
- let(:frame) do
8
- {
9
- length: 4,
10
- type: :headers,
11
- flags: [:end_stream, :end_headers],
12
- stream: 15,
13
- }
14
- end
15
-
16
- let(:bytes) { [0, 0x04, 0x01, 0x5, 0x0000000F].pack('CnCCN') }
17
-
18
- it 'should generate common 9 byte header' do
19
- expect(f.common_header(frame)).to eq bytes
20
- end
21
-
22
- it 'should parse common 9 byte header' do
23
- expect(f.read_common_header(Buffer.new(bytes))).to eq frame
24
- end
25
-
26
- it 'should generate a large frame' do
27
- f = Framer.new
28
- f.max_frame_size = 2**24 - 1
29
- frame = {
30
- length: 2**18 + 2**16 + 17,
31
- type: :headers,
32
- flags: [:end_stream, :end_headers],
33
- stream: 15,
34
- }
35
- bytes = [5, 17, 0x01, 0x5, 0x0000000F].pack('CnCCN')
36
- expect(f.common_header(frame)).to eq bytes
37
- expect(f.read_common_header(Buffer.new(bytes))).to eq frame
38
- end
39
-
40
- it 'should raise exception on invalid frame type when sending' do
41
- expect do
42
- frame[:type] = :bogus
43
- f.common_header(frame)
44
- end.to raise_error(CompressionError, /invalid.*type/i)
45
- end
46
-
47
- it 'should raise exception on invalid stream ID' do
48
- expect do
49
- frame[:stream] = Framer::MAX_STREAM_ID + 1
50
- f.common_header(frame)
51
- end.to raise_error(CompressionError, /stream/i)
52
- end
53
-
54
- it 'should raise exception on invalid frame flag' do
55
- expect do
56
- frame[:flags] = [:bogus]
57
- f.common_header(frame)
58
- end.to raise_error(CompressionError, /frame flag/)
59
- end
60
-
61
- it 'should raise exception on invalid frame size' do
62
- expect do
63
- frame[:length] = 2**24
64
- f.common_header(frame)
65
- end.to raise_error(CompressionError, /too large/)
66
- end
67
- end
68
-
69
- context 'DATA' do
70
- it 'should generate and parse bytes' do
71
- frame = {
72
- length: 4,
73
- type: :data,
74
- flags: [:end_stream],
75
- stream: 1,
76
- payload: 'text',
77
- }
78
-
79
- bytes = f.generate(frame)
80
- expect(bytes).to eq [0, 0x4, 0x0, 0x1, 0x1, *'text'.bytes].pack('CnCCNC*')
81
-
82
- expect(f.parse(bytes)).to eq frame
83
- end
84
- end
85
-
86
- context 'HEADERS' do
87
- it 'should generate and parse bytes' do
88
- frame = {
89
- length: 12,
90
- type: :headers,
91
- flags: [:end_stream, :end_headers],
92
- stream: 1,
93
- payload: 'header-block',
94
- }
95
-
96
- bytes = f.generate(frame)
97
- expect(bytes).to eq [0, 0xc, 0x1, 0x5, 0x1, *'header-block'.bytes].pack('CnCCNC*')
98
- expect(f.parse(bytes)).to eq frame
99
- end
100
-
101
- it 'should carry an optional stream priority' do
102
- frame = {
103
- length: 16,
104
- type: :headers,
105
- flags: [:end_headers],
106
- stream: 1,
107
- stream_dependency: 15,
108
- weight: 12,
109
- exclusive: false,
110
- payload: 'header-block',
111
- }
112
-
113
- bytes = f.generate(frame)
114
- expect(bytes).to eq [0, 0x11, 0x1, 0x24, 0x1, 0xf, 0xb, *'header-block'.bytes].pack('CnCCNNCC*')
115
- expect(f.parse(bytes)).to eq frame
116
- end
117
- end
118
-
119
- context 'PRIORITY' do
120
- it 'should generate and parse bytes' do
121
- frame = {
122
- length: 5,
123
- type: :priority,
124
- stream: 1,
125
- stream_dependency: 15,
126
- weight: 12,
127
- exclusive: true,
128
- }
129
-
130
- bytes = f.generate(frame)
131
- expect(bytes).to eq [0, 0x5, 0x2, 0x0, 0x1, 0x8000000f, 0xb].pack('CnCCNNC')
132
- expect(f.parse(bytes)).to eq frame
133
- end
134
- end
135
-
136
- context 'RST_STREAM' do
137
- it 'should generate and parse bytes' do
138
- frame = {
139
- length: 4,
140
- type: :rst_stream,
141
- stream: 1,
142
- error: :stream_closed,
143
- }
144
-
145
- bytes = f.generate(frame)
146
- expect(bytes).to eq [0, 0x4, 0x3, 0x0, 0x1, 0x5].pack('CnCCNN')
147
- expect(f.parse(bytes)).to eq frame
148
- end
149
- end
150
-
151
- context 'SETTINGS' do
152
- let(:frame) do
153
- {
154
- type: :settings,
155
- flags: [],
156
- stream: 0,
157
- payload: [
158
- [:settings_max_concurrent_streams, 10],
159
- [:settings_header_table_size, 2048],
160
- ],
161
- }
162
- end
163
-
164
- it 'should generate and parse bytes' do
165
- bytes = f.generate(frame)
166
- expect(bytes).to eq [0, 12, 0x4, 0x0, 0x0, 3, 10, 1, 2048].pack('CnCCNnNnN')
167
- parsed = f.parse(bytes)
168
- parsed.delete(:length)
169
- frame.delete(:length)
170
- expect(parsed).to eq frame
171
- end
172
-
173
- it 'should generate settings when id is given as an integer' do
174
- frame[:payload][1][0] = 1
175
- bytes = f.generate(frame)
176
- expect(bytes).to eq [0, 12, 0x4, 0x0, 0x0, 3, 10, 1, 2048].pack('CnCCNnNnN')
177
- end
178
-
179
- it 'should ignore custom settings when sending' do
180
- frame[:payload] = [
181
- [:settings_max_concurrent_streams, 10],
182
- [:settings_initial_window_size, 20],
183
- [55, 30],
184
- ]
185
-
186
- buf = f.generate(frame)
187
- frame[:payload].slice!(2) # cut off the extension
188
- frame[:length] = 12 # frame length should be computed WITHOUT extensions
189
- expect(f.parse(buf)).to eq frame
190
- end
191
-
192
- it 'should ignore custom settings when receiving' do
193
- frame[:payload] = [
194
- [:settings_max_concurrent_streams, 10],
195
- [:settings_initial_window_size, 20],
196
- ]
197
-
198
- buf = f.generate(frame)
199
- buf.setbyte(2, 18) # add 6 to the frame length
200
- buf << "\x00\x37\x00\x00\x00\x1e"
201
- parsed = f.parse(buf)
202
- parsed.delete(:length)
203
- frame.delete(:length)
204
- expect(parsed).to eq frame
205
- end
206
-
207
- it 'should raise exception on sending invalid stream ID' do
208
- expect do
209
- frame[:stream] = 1
210
- f.generate(frame)
211
- end.to raise_error(CompressionError, /Invalid stream ID/)
212
- end
213
-
214
- it 'should raise exception on receiving invalid stream ID' do
215
- expect do
216
- buf = f.generate(frame)
217
- buf.setbyte(8, 1)
218
- f.parse(buf)
219
- end.to raise_error(ProtocolError, /Invalid stream ID/)
220
- end
221
-
222
- it 'should raise exception on sending invalid setting' do
223
- expect do
224
- frame[:payload] = [[:random, 23]]
225
- f.generate(frame)
226
- end.to raise_error(CompressionError, /Unknown settings ID/)
227
- end
228
-
229
- it 'should raise exception on receiving invalid payload length' do
230
- expect do
231
- buf = f.generate(frame)
232
- buf.setbyte(2, 11) # change payload length
233
- f.parse(buf)
234
- end.to raise_error(ProtocolError, /Invalid settings payload length/)
235
- end
236
- end
237
-
238
- context 'PUSH_PROMISE' do
239
- it 'should generate and parse bytes' do
240
- frame = {
241
- length: 11,
242
- type: :push_promise,
243
- flags: [:end_headers],
244
- stream: 1,
245
- promise_stream: 2,
246
- payload: 'headers',
247
- }
248
-
249
- bytes = f.generate(frame)
250
- expect(bytes).to eq [0, 0xb, 0x5, 0x4, 0x1, 0x2, *'headers'.bytes].pack('CnCCNNC*')
251
- expect(f.parse(bytes)).to eq frame
252
- end
253
- end
254
-
255
- context 'PING' do
256
- let(:frame) do
257
- {
258
- length: 8,
259
- stream: 1,
260
- type: :ping,
261
- flags: [:ack],
262
- payload: '12345678',
263
- }
264
- end
265
-
266
- it 'should generate and parse bytes' do
267
- bytes = f.generate(frame)
268
- expect(bytes).to eq [0, 0x8, 0x6, 0x1, 0x1, *'12345678'.bytes].pack('CnCCNC*')
269
- expect(f.parse(bytes)).to eq frame
270
- end
271
-
272
- it 'should raise exception on invalid payload' do
273
- expect do
274
- frame[:payload] = '1234'
275
- f.generate(frame)
276
- end.to raise_error(CompressionError, /Invalid payload size/)
277
- end
278
- end
279
-
280
- context 'GOAWAY' do
281
- let(:frame) do
282
- {
283
- length: 13,
284
- stream: 1,
285
- type: :goaway,
286
- last_stream: 2,
287
- error: :no_error,
288
- payload: 'debug',
289
- }
290
- end
291
-
292
- it 'should generate and parse bytes' do
293
- bytes = f.generate(frame)
294
- expect(bytes).to eq [0, 0xd, 0x7, 0x0, 0x1, 0x2, 0x0, *'debug'.bytes].pack('CnCCNNNC*')
295
- expect(f.parse(bytes)).to eq frame
296
- end
297
-
298
- it 'should treat debug payload as optional' do
299
- frame.delete :payload
300
- frame[:length] = 0x8
301
-
302
- bytes = f.generate(frame)
303
- expect(bytes).to eq [0, 0x8, 0x7, 0x0, 0x1, 0x2, 0x0].pack('CnCCNNN')
304
- expect(f.parse(bytes)).to eq frame
305
- end
306
- end
307
-
308
- context 'WINDOW_UPDATE' do
309
- it 'should generate and parse bytes' do
310
- frame = {
311
- length: 4,
312
- type: :window_update,
313
- increment: 10,
314
- }
315
-
316
- bytes = f.generate(frame)
317
- expect(bytes).to eq [0, 0x4, 0x8, 0x0, 0x0, 0xa].pack('CnCCNN')
318
- expect(f.parse(bytes)).to eq frame
319
- end
320
- end
321
-
322
- context 'CONTINUATION' do
323
- it 'should generate and parse bytes' do
324
- frame = {
325
- length: 12,
326
- type: :continuation,
327
- stream: 1,
328
- flags: [:end_headers],
329
- payload: 'header-block',
330
- }
331
-
332
- bytes = f.generate(frame)
333
- expect(bytes).to eq [0, 0xc, 0x9, 0x4, 0x1, *'header-block'.bytes].pack('CnCCNC*')
334
- expect(f.parse(bytes)).to eq frame
335
- end
336
- end
337
-
338
- context 'ALTSVC' do
339
- it 'should generate and parse bytes' do
340
- frame = {
341
- length: 44,
342
- type: :altsvc,
343
- stream: 1,
344
- max_age: 1_402_290_402, # 4
345
- port: 8080, # 2
346
- proto: 'h2-13', # 1 + 5
347
- host: 'www.example.com', # 1 + 15
348
- origin: 'www.example.com', # 15
349
- }
350
- bytes = f.generate(frame)
351
- expected = [0, 43, 0xa, 0, 1, 1_402_290_402, 8080].pack('CnCCNNn')
352
- expected << [5, *'h2-13'.bytes].pack('CC*')
353
- expected << [15, *'www.example.com'.bytes].pack('CC*')
354
- expected << [*'www.example.com'.bytes].pack('C*')
355
- expect(bytes).to eq expected
356
- expect(f.parse(bytes)).to eq frame
357
- end
358
- end
359
-
360
- context 'Padding' do
361
- [:data, :headers, :push_promise].each do |type|
362
- [1, 256].each do |padlen|
363
- context "generating #{type} frame padded #{padlen}" do
364
- before do
365
- @frame = {
366
- length: 12,
367
- type: type,
368
- stream: 1,
369
- payload: 'example data',
370
- }
371
- @frame[:promise_stream] = 2 if type == :push_promise
372
- @normal = f.generate(@frame)
373
- @padded = f.generate(@frame.merge(padding: padlen))
374
- end
375
- it 'should generate a frame with padding' do
376
- expect(@padded.bytesize).to eq @normal.bytesize + padlen
377
- end
378
- it 'should fill padded octets with zero' do
379
- trailer_len = padlen - 1
380
- expect(@padded[-trailer_len, trailer_len]).to match(/\A\0*\z/)
381
- end
382
- it 'should parse a frame with padding' do
383
- expect(f.parse(Buffer.new(@padded))).to eq \
384
- f.parse(Buffer.new(@normal)).merge(padding: padlen)
385
- end
386
- it 'should preserve payload' do
387
- expect(f.parse(Buffer.new(@padded))[:payload]).to eq @frame[:payload]
388
- end
389
- end
390
- end
391
- end
392
- context 'generating with invalid padding length' do
393
- before do
394
- @frame = {
395
- length: 12,
396
- type: :data,
397
- stream: 1,
398
- payload: 'example data',
399
- }
400
- end
401
- [0, 257, 1334].each do |padlen|
402
- it "should raise error on trying to generate data frame padded with invalid #{padlen}" do
403
- expect do
404
- f.generate(@frame.merge(padding: padlen))
405
- end.to raise_error(CompressionError, /padding/i)
406
- end
407
- end
408
- it 'should raise error when adding a padding would make frame too large' do
409
- @frame[:payload] = 'q' * (f.max_frame_size - 200)
410
- @frame[:length] = @frame[:payload].size
411
- @frame[:padding] = 210 # would exceed 4096
412
- expect do
413
- f.generate(@frame)
414
- end.to raise_error(CompressionError, /padding/i)
415
- end
416
- end
417
- context 'parsing frames with invalid paddings' do
418
- before do
419
- @frame = {
420
- length: 12,
421
- type: :data,
422
- stream: 1,
423
- payload: 'example data',
424
- }
425
- @padlen = 123
426
- @padded = f.generate(@frame.merge(padding: @padlen))
427
- end
428
- it 'should raise exception when the given padding is longer than the payload' do
429
- @padded.setbyte(9, 240)
430
- expect { f.parse(Buffer.new(@padded)) }.to raise_error(ProtocolError, /padding/)
431
- end
432
- end
433
- end
434
-
435
- it 'should determine frame length' do
436
- frames = [
437
- [{ type: :data, stream: 1, flags: [:end_stream], payload: 'abc' }, 3],
438
- [{ type: :headers, stream: 1, payload: 'abc' }, 3],
439
- [{ type: :priority, stream: 3, stream_dependency: 30, exclusive: false, weight: 1 }, 5],
440
- [{ type: :rst_stream, stream: 3, error: 100 }, 4],
441
- [{ type: :settings, payload: [[:settings_max_concurrent_streams, 10]] }, 6],
442
- [{ type: :push_promise, promise_stream: 5, payload: 'abc' }, 7],
443
- [{ type: :ping, payload: 'blob' * 2 }, 8],
444
- [{ type: :goaway, last_stream: 5, error: 20, payload: 'blob' }, 12],
445
- [{ type: :window_update, stream: 1, increment: 1024 }, 4],
446
- [{ type: :continuation, stream: 1, payload: 'abc' }, 3],
447
- ]
448
-
449
- frames.each do |(frame, size)|
450
- bytes = f.generate(frame)
451
- expect(bytes.slice(1, 2).unpack('n').first).to eq size
452
- expect(bytes.readbyte(0)).to eq 0
453
- end
454
- end
455
-
456
- it 'should parse single frame at a time' do
457
- frames = [
458
- { type: :headers, stream: 1, payload: 'headers' },
459
- { type: :data, stream: 1, flags: [:end_stream], payload: 'abc' },
460
- ]
461
-
462
- buf = f.generate(frames[0]) << f.generate(frames[1])
463
-
464
- expect(f.parse(buf)).to eq frames[0]
465
- expect(f.parse(buf)).to eq frames[1]
466
- end
467
-
468
- it 'should process full frames only' do
469
- frame = { type: :headers, stream: 1, payload: 'headers' }
470
- bytes = f.generate(frame)
471
-
472
- expect(f.parse(bytes[0...-1])).to be_nil
473
- expect(f.parse(bytes)).to eq frame
474
- expect(bytes).to be_empty
475
- end
476
-
477
- it 'should ignore unknown extension frames' do
478
- frame = { type: :headers, stream: 1, payload: 'headers' }
479
- bytes = f.generate(frame)
480
- bytes = Buffer.new(bytes + bytes) # Two HEADERS frames in bytes
481
- bytes.setbyte(3, 42) # Make the first unknown type 42
482
-
483
- expect(f.parse(bytes)).to be_nil # first frame should be ignored
484
- expect(f.parse(bytes)).to eq frame # should generate only one HEADERS
485
- expect(bytes).to be_empty
486
- end
487
- end
Binary file