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.
- checksums.yaml +4 -4
- data/.coveralls.yml +1 -0
- data/.gitmodules +3 -0
- data/Gemfile +5 -0
- data/README.md +5 -4
- data/Rakefile +1 -0
- data/example/client.rb +20 -5
- data/example/helper.rb +1 -1
- data/example/keys/mycert.pem +21 -22
- data/example/keys/mykey.pem +25 -25
- data/example/server.rb +10 -3
- data/http-2.gemspec +1 -1
- data/lib/http/2.rb +2 -0
- data/lib/http/2/client.rb +16 -10
- data/lib/http/2/compressor.rb +346 -286
- data/lib/http/2/connection.rb +254 -95
- data/lib/http/2/error.rb +0 -6
- data/lib/http/2/flow_buffer.rb +12 -10
- data/lib/http/2/framer.rb +203 -57
- data/lib/http/2/huffman.rb +332 -0
- data/lib/http/2/huffman_statemachine.rb +272 -0
- data/lib/http/2/server.rb +5 -4
- data/lib/http/2/stream.rb +72 -35
- data/lib/http/2/version.rb +1 -1
- data/lib/tasks/generate_huffman_table.rb +160 -0
- data/spec/client_spec.rb +3 -3
- data/spec/compressor_spec.rb +422 -281
- data/spec/connection_spec.rb +236 -56
- data/spec/framer_spec.rb +213 -45
- data/spec/helper.rb +42 -15
- data/spec/hpack_test_spec.rb +83 -0
- data/spec/huffman_spec.rb +68 -0
- data/spec/server_spec.rb +7 -6
- data/spec/stream_spec.rb +81 -54
- metadata +21 -11
data/lib/http/2/server.rb
CHANGED
@@ -22,11 +22,12 @@ module HTTP2
|
|
22
22
|
class Server < Connection
|
23
23
|
|
24
24
|
# Initialize new HTTP 2.0 server object.
|
25
|
-
def initialize(
|
25
|
+
def initialize(**settings)
|
26
26
|
@stream_id = 2
|
27
|
-
@state = :
|
28
|
-
|
29
|
-
@
|
27
|
+
@state = :waiting_magic
|
28
|
+
|
29
|
+
@local_role = :server
|
30
|
+
@remote_role = :client
|
30
31
|
|
31
32
|
super
|
32
33
|
end
|
data/lib/http/2/stream.rb
CHANGED
@@ -24,7 +24,7 @@ module HTTP2
|
|
24
24
|
# | | ,-------|:active |-------. | |
|
25
25
|
# | | H / ES | | ES \ H | |
|
26
26
|
# | v v +--------+ v v |
|
27
|
-
# | +-----------+ |
|
27
|
+
# | +-----------+ | +-----------+ |
|
28
28
|
# | |:half_close| | |:half_close| |
|
29
29
|
# | | (remote) | | | (local) | |
|
30
30
|
# | +-----------+ | +-----------+ |
|
@@ -49,10 +49,13 @@ module HTTP2
|
|
49
49
|
attr_reader :parent
|
50
50
|
|
51
51
|
# Stream priority as set by initiator.
|
52
|
-
attr_reader :
|
52
|
+
attr_reader :weight
|
53
|
+
attr_reader :dependency
|
53
54
|
|
54
55
|
# Size of current stream flow control window.
|
55
|
-
attr_reader :
|
56
|
+
attr_reader :local_window
|
57
|
+
attr_reader :remote_window
|
58
|
+
alias :window :local_window
|
56
59
|
|
57
60
|
# Reason why connection was closed.
|
58
61
|
attr_reader :closed
|
@@ -64,20 +67,26 @@ module HTTP2
|
|
64
67
|
# will emit new stream objects, when new stream frames are received.
|
65
68
|
#
|
66
69
|
# @param id [Integer]
|
67
|
-
# @param
|
70
|
+
# @param weight [Integer]
|
71
|
+
# @param dependency [Integer]
|
72
|
+
# @param exclusive [Boolean]
|
68
73
|
# @param window [Integer]
|
69
74
|
# @param parent [Stream]
|
70
|
-
def initialize(id,
|
71
|
-
@
|
72
|
-
@
|
73
|
-
@
|
75
|
+
def initialize(connection: nil, id: nil, weight: 16, dependency: 0, exclusive: false, parent: nil)
|
76
|
+
@connection = connection or raise ArgumentError.new("missing mandatory argument connection")
|
77
|
+
@id = id or raise ArgumentError.new("missing mandatory argument id")
|
78
|
+
@weight = weight
|
79
|
+
@dependency = dependency
|
80
|
+
process_priority({weight: weight, stream_dependency: dependency, exclusive: exclusive})
|
81
|
+
@remote_window = connection.remote_settings[:settings_initial_window_size]
|
74
82
|
@parent = parent
|
75
83
|
@state = :idle
|
76
84
|
@error = false
|
77
85
|
@closed = false
|
78
86
|
@send_buffer = []
|
79
87
|
|
80
|
-
on(:window) { |v| @
|
88
|
+
on(:window) { |v| @remote_window = v }
|
89
|
+
on(:local_window) { |v| @local_window = v }
|
81
90
|
end
|
82
91
|
|
83
92
|
# Processes incoming HTTP 2.0 frames. The frames must be decoded upstream.
|
@@ -88,19 +97,17 @@ module HTTP2
|
|
88
97
|
|
89
98
|
case frame[:type]
|
90
99
|
when :data
|
100
|
+
# TODO: when receiving DATA, keep track of local_window.
|
91
101
|
emit(:data, frame[:payload]) if !frame[:ignore]
|
92
102
|
when :headers, :push_promise
|
93
|
-
|
94
|
-
emit(:headers, Hash[*frame[:payload].flatten]) if !frame[:ignore]
|
95
|
-
else
|
96
|
-
emit(:headers, frame[:payload]) if !frame[:ignore]
|
97
|
-
end
|
103
|
+
emit(:headers, frame[:payload]) if !frame[:ignore]
|
98
104
|
when :priority
|
99
|
-
|
100
|
-
emit(:priority, @priority)
|
105
|
+
process_priority(frame)
|
101
106
|
when :window_update
|
102
|
-
@
|
107
|
+
@remote_window += frame[:increment]
|
103
108
|
send_data
|
109
|
+
when :altsvc, :blocked
|
110
|
+
emit(frame[:type], frame)
|
104
111
|
end
|
105
112
|
|
106
113
|
complete_transition(frame)
|
@@ -116,7 +123,7 @@ module HTTP2
|
|
116
123
|
transition(frame, true)
|
117
124
|
frame[:stream] ||= @id
|
118
125
|
|
119
|
-
|
126
|
+
process_priority(frame) if frame[:type] == :priority
|
120
127
|
|
121
128
|
if frame[:type] == :data
|
122
129
|
send_data(frame)
|
@@ -129,7 +136,7 @@ module HTTP2
|
|
129
136
|
|
130
137
|
# Sends a HEADERS frame containing HTTP response headers.
|
131
138
|
#
|
132
|
-
# @param headers [Hash]
|
139
|
+
# @param headers [Array or Hash] Array of key-value pairs or Hash
|
133
140
|
# @param end_headers [Boolean] indicates that no more headers will be sent
|
134
141
|
# @param end_stream [Boolean] indicates that no payload will be sent
|
135
142
|
def headers(headers, end_headers: true, end_stream: false)
|
@@ -140,20 +147,21 @@ module HTTP2
|
|
140
147
|
send({type: :headers, flags: flags, payload: headers.to_a})
|
141
148
|
end
|
142
149
|
|
143
|
-
def promise(headers,
|
150
|
+
def promise(headers, end_headers: true, &block)
|
144
151
|
raise Exception.new("must provide callback") if !block_given?
|
145
152
|
|
146
|
-
flags =
|
153
|
+
flags = end_headers ? [:end_headers] : []
|
147
154
|
emit(:promise, self, headers, flags, &block)
|
148
155
|
end
|
149
156
|
|
150
157
|
# Sends a PRIORITY frame with new stream priority value (can only be
|
151
158
|
# performed by the client).
|
152
159
|
#
|
153
|
-
# @param
|
154
|
-
|
160
|
+
# @param weight [Integer] new stream weight value
|
161
|
+
# @param dependency [Integer] new stream dependency stream
|
162
|
+
def reprioritize(weight: 16, dependency: 0, exclusive: false)
|
155
163
|
stream_error if @id.even?
|
156
|
-
send({type: :priority,
|
164
|
+
send({type: :priority, weight: weight, stream_dependency: dependency, exclusive: exclusive})
|
157
165
|
end
|
158
166
|
|
159
167
|
# Sends DATA frame containing response payload.
|
@@ -164,8 +172,11 @@ module HTTP2
|
|
164
172
|
flags = []
|
165
173
|
flags << :end_stream if end_stream
|
166
174
|
|
167
|
-
|
168
|
-
|
175
|
+
# Split data according to each frame is smaller enough
|
176
|
+
# TODO: consider padding?
|
177
|
+
max_size = @connection.remote_settings[:settings_max_frame_size]
|
178
|
+
while payload.bytesize > max_size do
|
179
|
+
chunk = payload.slice!(0, max_size)
|
169
180
|
send({type: :data, payload: chunk})
|
170
181
|
end
|
171
182
|
|
@@ -344,8 +355,11 @@ module HTTP2
|
|
344
355
|
# frame bearing the END_STREAM flag is sent.
|
345
356
|
when :half_closed_local
|
346
357
|
if sending
|
347
|
-
|
358
|
+
case frame[:type]
|
359
|
+
when :rst_stream
|
348
360
|
event(:local_rst)
|
361
|
+
when :priority
|
362
|
+
process_priority(frame)
|
349
363
|
else
|
350
364
|
stream_error
|
351
365
|
end
|
@@ -354,8 +368,10 @@ module HTTP2
|
|
354
368
|
when :data, :headers, :continuation
|
355
369
|
event(:remote_closed) if end_stream?(frame)
|
356
370
|
when :rst_stream then event(:remote_rst)
|
357
|
-
when :
|
358
|
-
frame
|
371
|
+
when :priority
|
372
|
+
process_priority(frame)
|
373
|
+
when :window_update
|
374
|
+
frame[:ignore] = true
|
359
375
|
end
|
360
376
|
end
|
361
377
|
|
@@ -380,6 +396,8 @@ module HTTP2
|
|
380
396
|
case frame[:type]
|
381
397
|
when :rst_stream then event(:remote_rst)
|
382
398
|
when :window_update then frame[:ignore] = true
|
399
|
+
when :priority
|
400
|
+
process_priority(frame)
|
383
401
|
else stream_error(:stream_closed); end
|
384
402
|
end
|
385
403
|
|
@@ -407,15 +425,21 @@ module HTTP2
|
|
407
425
|
if sending
|
408
426
|
case frame[:type]
|
409
427
|
when :rst_stream then # ignore
|
428
|
+
when :priority then
|
429
|
+
process_priority(frame)
|
410
430
|
else
|
411
431
|
stream_error(:stream_closed) if !(frame[:type] == :rst_stream)
|
412
432
|
end
|
413
433
|
else
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
434
|
+
if frame[:type] == :priority
|
435
|
+
process_priority(frame)
|
436
|
+
else
|
437
|
+
case @closed
|
438
|
+
when :remote_rst, :remote_closed
|
439
|
+
stream_error(:stream_closed) if !(frame[:type] == :rst_stream)
|
440
|
+
when :local_rst, :local_closed
|
441
|
+
frame[:ignore] = true
|
442
|
+
end
|
419
443
|
end
|
420
444
|
end
|
421
445
|
end
|
@@ -455,6 +479,20 @@ module HTTP2
|
|
455
479
|
end
|
456
480
|
end
|
457
481
|
|
482
|
+
def process_priority(frame)
|
483
|
+
@weight = frame[:weight]
|
484
|
+
@dependency = frame[:stream_dependency]
|
485
|
+
emit(:priority,
|
486
|
+
weight: frame[:weight],
|
487
|
+
dependency: frame[:stream_dependency],
|
488
|
+
exclusive: frame[:exclusive])
|
489
|
+
# TODO: implement dependency tree housekeeping
|
490
|
+
# Latest draft defines a fairly complex priority control.
|
491
|
+
# See https://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-5.3
|
492
|
+
# We currently have no prioritization among streams.
|
493
|
+
# We should add code here.
|
494
|
+
end
|
495
|
+
|
458
496
|
def end_stream?(frame)
|
459
497
|
case frame[:type]
|
460
498
|
when :data, :headers, :continuation
|
@@ -469,6 +507,5 @@ module HTTP2
|
|
469
507
|
klass = error.to_s.split('_').map(&:capitalize).join
|
470
508
|
raise Error.const_get(klass).new(msg)
|
471
509
|
end
|
472
|
-
|
473
510
|
end
|
474
511
|
end
|
data/lib/http/2/version.rb
CHANGED
@@ -0,0 +1,160 @@
|
|
1
|
+
desc "Generate Huffman precompiled table in huffman_statemachine.rb"
|
2
|
+
task :generate_table do
|
3
|
+
HuffmanTable::Node.generate_state_table
|
4
|
+
end
|
5
|
+
|
6
|
+
require_relative '../http/2/huffman'
|
7
|
+
|
8
|
+
# @private
|
9
|
+
module HuffmanTable
|
10
|
+
BITS_AT_ONCE = HTTP2::Header::Huffman::BITS_AT_ONCE
|
11
|
+
EOS = 256
|
12
|
+
|
13
|
+
class Node
|
14
|
+
attr_accessor :next, :emit, :final, :depth
|
15
|
+
attr_accessor :transitions
|
16
|
+
attr_accessor :id
|
17
|
+
@@id = 0
|
18
|
+
def initialize(depth)
|
19
|
+
@next = [nil, nil]
|
20
|
+
@id = @@id
|
21
|
+
@@id += 1
|
22
|
+
@final = false
|
23
|
+
@depth = depth
|
24
|
+
end
|
25
|
+
def add(code, len, chr)
|
26
|
+
chr == EOS && @depth <= 7 and self.final = true
|
27
|
+
if len == 0
|
28
|
+
@emit = chr
|
29
|
+
else
|
30
|
+
bit = (code & (1 << (len - 1))) == 0 ? 0 : 1
|
31
|
+
node = @next[bit] ||= Node.new(@depth + 1)
|
32
|
+
node.add(code, len - 1, chr)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class Transition
|
37
|
+
attr_accessor :emit, :node
|
38
|
+
def initialize(emit, node)
|
39
|
+
@emit = emit
|
40
|
+
@node = node
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.generate_tree
|
45
|
+
@root = new(0)
|
46
|
+
HTTP2::Header::Huffman::CODES.each_with_index do |c, chr|
|
47
|
+
code, len = c
|
48
|
+
@root.add(code, len, chr)
|
49
|
+
end
|
50
|
+
puts "#{@@id} nodes"
|
51
|
+
@root
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.generate_machine
|
55
|
+
generate_tree
|
56
|
+
togo = Set[@root]
|
57
|
+
@states = Set[@root]
|
58
|
+
|
59
|
+
until togo.empty?
|
60
|
+
node = togo.first
|
61
|
+
togo.delete(node)
|
62
|
+
|
63
|
+
next if node.transitions
|
64
|
+
node.transitions = Array[1 << BITS_AT_ONCE]
|
65
|
+
|
66
|
+
(1 << BITS_AT_ONCE).times do |input|
|
67
|
+
n = node
|
68
|
+
emit = ''
|
69
|
+
(BITS_AT_ONCE - 1).downto(0) do |i|
|
70
|
+
bit = (input & (1 << i)) == 0 ? 0 : 1
|
71
|
+
n = n.next[bit]
|
72
|
+
if n.emit
|
73
|
+
if n.emit == EOS
|
74
|
+
emit = EOS # cause error on decoding
|
75
|
+
else
|
76
|
+
emit << n.emit.chr('binary') unless emit == EOS
|
77
|
+
end
|
78
|
+
n = @root
|
79
|
+
end
|
80
|
+
end
|
81
|
+
node.transitions[input] = Transition.new(emit, n)
|
82
|
+
togo << n
|
83
|
+
@states << n
|
84
|
+
end
|
85
|
+
end
|
86
|
+
puts "#{@states.size} states"
|
87
|
+
@root
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.generate_state_table
|
91
|
+
generate_machine
|
92
|
+
state_id = {}
|
93
|
+
id_state = {}
|
94
|
+
state_id[@root] = 0
|
95
|
+
id_state[0] = @root
|
96
|
+
max_final = 0
|
97
|
+
id = 1
|
98
|
+
(@states - [@root]).sort_by{|s|s.final ? 0 : 1}.each do |s|
|
99
|
+
state_id[s] = id
|
100
|
+
id_state[id] = s
|
101
|
+
max_final = id if s.final
|
102
|
+
id += 1
|
103
|
+
end
|
104
|
+
|
105
|
+
File.open(File.expand_path("../http/2/huffman_statemachine.rb", File.dirname(__FILE__)), "w") do |f|
|
106
|
+
f.print <<HEADER
|
107
|
+
# Machine generated Huffman decoder state machine.
|
108
|
+
# DO NOT EDIT THIS FILE.
|
109
|
+
|
110
|
+
# The following task generates this file.
|
111
|
+
# rake generate_huffman_table
|
112
|
+
|
113
|
+
module HTTP2
|
114
|
+
module Header
|
115
|
+
class Huffman
|
116
|
+
# :nodoc:
|
117
|
+
MAX_FINAL_STATE = #{max_final}
|
118
|
+
MACHINE = [
|
119
|
+
HEADER
|
120
|
+
id.times do |i|
|
121
|
+
n = id_state[i]
|
122
|
+
f.print " ["
|
123
|
+
(1 << BITS_AT_ONCE).times do |t|
|
124
|
+
emit = n.transitions[t].emit
|
125
|
+
emit == EOS or emit = emit.dup.force_encoding('binary')
|
126
|
+
f.print %Q/[#{emit == '' ? "nil" : emit.inspect},#{state_id[n.transitions[t].node]}],/
|
127
|
+
end
|
128
|
+
f.print "],\n"
|
129
|
+
end
|
130
|
+
f.print <<TAILER
|
131
|
+
]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
TAILER
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.root
|
140
|
+
@root
|
141
|
+
end
|
142
|
+
|
143
|
+
# Test decoder
|
144
|
+
def self.decode(input)
|
145
|
+
emit = ''
|
146
|
+
n = root
|
147
|
+
nibbles = input.unpack("C*").flat_map{|b| [((b & 0xf0) >> 4), b & 0xf]}
|
148
|
+
until nibbles.empty?
|
149
|
+
nb = nibbles.shift
|
150
|
+
t = n.transitions[nb]
|
151
|
+
emit << t.emit
|
152
|
+
n = t.node
|
153
|
+
end
|
154
|
+
unless n.final && nibbles.all?{|x| x == 0xf}
|
155
|
+
puts "len = #{emit.size} n.final = #{n.final} nibbles = #{nibbles}"
|
156
|
+
end
|
157
|
+
emit
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
data/spec/client_spec.rb
CHANGED
@@ -17,20 +17,20 @@ describe HTTP2::Client do
|
|
17
17
|
@client.on(:frame) { |bytes| frames << bytes }
|
18
18
|
@client.ping("12345678")
|
19
19
|
|
20
|
-
frames[0].should eq
|
20
|
+
frames[0].should eq CONNECTION_PREFACE_MAGIC
|
21
21
|
f.parse(frames[1])[:type].should eq :settings
|
22
22
|
end
|
23
23
|
|
24
24
|
it "should initialize client with custom connection settings" do
|
25
25
|
frames = []
|
26
26
|
|
27
|
-
@client = Client.new(
|
27
|
+
@client = Client.new(:settings_max_concurrent_streams => 200)
|
28
28
|
@client.on(:frame) { |bytes| frames << bytes }
|
29
29
|
@client.ping("12345678")
|
30
30
|
|
31
31
|
frame = f.parse(frames[1])
|
32
32
|
frame[:type].should eq :settings
|
33
|
-
frame[:payload][:settings_max_concurrent_streams
|
33
|
+
frame[:payload].should include([:settings_max_concurrent_streams, 200])
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
data/spec/compressor_spec.rb
CHANGED
@@ -2,8 +2,8 @@ require "helper"
|
|
2
2
|
|
3
3
|
describe HTTP2::Header do
|
4
4
|
|
5
|
-
let(:c) { Compressor.new
|
6
|
-
let(:d) { Decompressor.new
|
5
|
+
let(:c) { Compressor.new }
|
6
|
+
let(:d) { Decompressor.new }
|
7
7
|
|
8
8
|
context "literal representation" do
|
9
9
|
context "integer" do
|
@@ -33,28 +33,39 @@ describe HTTP2::Header do
|
|
33
33
|
end
|
34
34
|
|
35
35
|
context "string" do
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
36
|
+
[ ['with huffman', :always, 0x80 ],
|
37
|
+
['without huffman', :never, 0] ].each do |desc, option, msb|
|
38
|
+
let (:trailer) { "trailer" }
|
39
|
+
|
40
|
+
[
|
41
|
+
['ascii codepoints', 'abcdefghij'],
|
42
|
+
['utf-8 codepoints', 'éáűőúöüó€'],
|
43
|
+
['long utf-8 strings', 'éáűőúöüó€'*100],
|
44
|
+
].each do |datatype, plain|
|
45
|
+
it "should handle #{datatype} #{desc}" do
|
46
|
+
# NOTE: don't put this new in before{} because of test case shuffling
|
47
|
+
@c = Compressor.new(huffman: option)
|
48
|
+
str = @c.string(plain)
|
49
|
+
(str.getbyte(0) & 0x80).should eq msb
|
50
|
+
|
51
|
+
buf = Buffer.new(str + trailer)
|
52
|
+
d.string(buf).should eq plain
|
53
|
+
buf.should eq trailer
|
54
|
+
end
|
55
|
+
end
|
50
56
|
end
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
57
|
+
context "choosing shorter representation" do
|
58
|
+
[ ['日本語', :plain],
|
59
|
+
['200', :huffman],
|
60
|
+
['xq', :plain], # prefer plain if equal size
|
61
|
+
].each do |string, choice|
|
62
|
+
before { @c = Compressor.new(huffman: :shorter) }
|
63
|
+
|
64
|
+
it "should return #{choice} representation" do
|
65
|
+
wire = @c.string(string)
|
66
|
+
(wire.getbyte(0) & 0x80).should eq (choice == :plain ? 0 : 0x80)
|
67
|
+
end
|
68
|
+
end
|
58
69
|
end
|
59
70
|
end
|
60
71
|
end
|
@@ -62,332 +73,462 @@ describe HTTP2::Header do
|
|
62
73
|
context "header representation" do
|
63
74
|
it "should handle indexed representation" do
|
64
75
|
h = {name: 10, type: :indexed}
|
65
|
-
|
76
|
+
wire = c.header(h)
|
77
|
+
(wire.readbyte(0) & 0x80).should eq 0x80
|
78
|
+
(wire.readbyte(0) & 0x7f).should eq h[:name] + 1
|
79
|
+
d.header(wire).should eq h
|
80
|
+
end
|
81
|
+
it "should raise when decoding indexed representation with index zero" do
|
82
|
+
h = {name: 10, type: :indexed}
|
83
|
+
wire = c.header(h)
|
84
|
+
wire[0] = 0x80.chr('binary')
|
85
|
+
expect { d.header(wire) }.to raise_error CompressionError
|
66
86
|
end
|
67
87
|
|
68
88
|
context "literal w/o indexing representation" do
|
69
89
|
it "should handle indexed header" do
|
70
90
|
h = {name: 10, value: "my-value", type: :noindex}
|
71
|
-
|
91
|
+
wire = c.header(h)
|
92
|
+
(wire.readbyte(0) & 0xf0).should eq 0x0
|
93
|
+
(wire.readbyte(0) & 0x0f).should eq h[:name] + 1
|
94
|
+
d.header(wire).should eq h
|
72
95
|
end
|
73
96
|
|
74
97
|
it "should handle literal header" do
|
75
98
|
h = {name: "x-custom", value: "my-value", type: :noindex}
|
76
|
-
|
99
|
+
wire = c.header(h)
|
100
|
+
(wire.readbyte(0) & 0xf0).should eq 0x0
|
101
|
+
(wire.readbyte(0) & 0x0f).should eq 0
|
102
|
+
d.header(wire).should eq h
|
77
103
|
end
|
78
104
|
end
|
79
105
|
|
80
106
|
context "literal w/ incremental indexing" do
|
81
107
|
it "should handle indexed header" do
|
82
108
|
h = {name: 10, value: "my-value", type: :incremental}
|
83
|
-
|
109
|
+
wire = c.header(h)
|
110
|
+
(wire.readbyte(0) & 0xc0).should eq 0x40
|
111
|
+
(wire.readbyte(0) & 0x3f).should eq h[:name] + 1
|
112
|
+
d.header(wire).should eq h
|
84
113
|
end
|
85
114
|
|
86
115
|
it "should handle literal header" do
|
87
116
|
h = {name: "x-custom", value: "my-value", type: :incremental}
|
88
|
-
|
117
|
+
wire = c.header(h)
|
118
|
+
(wire.readbyte(0) & 0xc0).should eq 0x40
|
119
|
+
(wire.readbyte(0) & 0x3f).should eq 0
|
120
|
+
d.header(wire).should eq h
|
89
121
|
end
|
90
122
|
end
|
91
123
|
|
92
|
-
context "literal
|
124
|
+
context "literal never indexed" do
|
93
125
|
it "should handle indexed header" do
|
94
|
-
h = {name:
|
95
|
-
|
126
|
+
h = {name: 10, value: "my-value", type: :neverindexed}
|
127
|
+
wire = c.header(h)
|
128
|
+
(wire.readbyte(0) & 0xf0).should eq 0x10
|
129
|
+
(wire.readbyte(0) & 0x0f).should eq h[:name] + 1
|
130
|
+
d.header(wire).should eq h
|
96
131
|
end
|
97
132
|
|
98
133
|
it "should handle literal header" do
|
99
|
-
h = {name: "x-
|
100
|
-
|
134
|
+
h = {name: "x-custom", value: "my-value", type: :neverindexed}
|
135
|
+
wire = c.header(h)
|
136
|
+
(wire.readbyte(0) & 0xf0).should eq 0x10
|
137
|
+
(wire.readbyte(0) & 0x0f).should eq 0
|
138
|
+
d.header(wire).should eq h
|
101
139
|
end
|
102
140
|
end
|
103
141
|
end
|
104
142
|
|
105
|
-
context "
|
106
|
-
|
107
|
-
before(:each) { @cc = EncodingContext.new(:request) }
|
108
|
-
|
109
|
-
it "should be initialized with pre-defined headers" do
|
110
|
-
cc = EncodingContext.new(:request)
|
111
|
-
cc.table.size.should eq 30
|
112
|
-
|
113
|
-
cc = EncodingContext.new(:response)
|
114
|
-
cc.table.size.should eq 30
|
115
|
-
end
|
116
|
-
|
117
|
-
it "should be initialized with empty working set" do
|
118
|
-
@cc.refset.should be_empty
|
119
|
-
end
|
120
|
-
|
121
|
-
it "should update working set based on prior state" do
|
122
|
-
@cc.refset.should be_empty
|
123
|
-
|
124
|
-
@cc.process({name: 0, type: :indexed})
|
125
|
-
@cc.refset.should eq [[0, [":scheme", "http"]]]
|
143
|
+
context "shared compression context" do
|
144
|
+
before(:each) { @cc = EncodingContext.new }
|
126
145
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
context "processing" do
|
132
|
-
it "should toggle index representation headers in working set" do
|
133
|
-
@cc.process({name: 0, type: :indexed})
|
134
|
-
@cc.refset.first.should eq [0, [":scheme", "http"]]
|
135
|
-
|
136
|
-
@cc.process({name: 0, type: :indexed})
|
137
|
-
@cc.refset.should be_empty
|
138
|
-
end
|
146
|
+
it "should be initialized with empty headers" do
|
147
|
+
cc = EncodingContext.new
|
148
|
+
cc.table.should be_empty
|
149
|
+
end
|
139
150
|
|
140
|
-
|
151
|
+
context "processing" do
|
152
|
+
[ ["no indexing", :noindex],
|
153
|
+
["never indexed", :neverindexed]].each do |desc, type|
|
154
|
+
context "#{desc}" do
|
141
155
|
it "should process indexed header with literal value" do
|
142
|
-
original_table = @cc.table
|
156
|
+
original_table = @cc.table.dup
|
143
157
|
|
144
|
-
emit = @cc.process({name:
|
158
|
+
emit = @cc.process({name: 4, value: "/path", type: type})
|
145
159
|
emit.should eq [":path", "/path"]
|
146
|
-
@cc.refset.should be_empty
|
147
|
-
@cc.table.should eq original_table
|
148
|
-
end
|
149
|
-
|
150
|
-
it "should process indexed header with default value" do
|
151
|
-
original_table = @cc.table
|
152
|
-
|
153
|
-
emit = @cc.process({name: 3, type: :noindex})
|
154
|
-
emit.should eq [":path", "/"]
|
155
160
|
@cc.table.should eq original_table
|
156
161
|
end
|
157
162
|
|
158
163
|
it "should process literal header with literal value" do
|
159
|
-
original_table = @cc.table
|
164
|
+
original_table = @cc.table.dup
|
160
165
|
|
161
|
-
emit = @cc.process({name: "x-custom", value: "random", type:
|
166
|
+
emit = @cc.process({name: "x-custom", value: "random", type: type})
|
162
167
|
emit.should eq ["x-custom", "random"]
|
163
|
-
@cc.refset.should be_empty
|
164
168
|
@cc.table.should eq original_table
|
165
169
|
end
|
166
170
|
end
|
171
|
+
end
|
167
172
|
|
168
|
-
|
169
|
-
|
170
|
-
|
173
|
+
context "incremental indexing" do
|
174
|
+
it "should process indexed header with literal value" do
|
175
|
+
original_table = @cc.table.dup
|
171
176
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
end
|
177
|
+
emit = @cc.process({name: 4, value: "/path", type: :incremental})
|
178
|
+
emit.should eq [":path", "/path"]
|
179
|
+
(@cc.table - original_table).should eq [[":path", "/path"]]
|
176
180
|
end
|
177
181
|
|
178
|
-
|
179
|
-
|
180
|
-
original_table = @cc.table.dup
|
181
|
-
idx = original_table.size-1
|
182
|
-
|
183
|
-
@cc.process({
|
184
|
-
name: "x-custom", value: "random",
|
185
|
-
index: idx, type: :substitution
|
186
|
-
})
|
182
|
+
it "should process literal header with literal value" do
|
183
|
+
original_table = @cc.table.dup
|
187
184
|
|
188
|
-
|
189
|
-
|
190
|
-
(original_table - @cc.table).should eq [["via", ""]]
|
191
|
-
end
|
192
|
-
|
193
|
-
it "should raise error on invalid substitution index" do
|
194
|
-
lambda {
|
195
|
-
@cc.process({
|
196
|
-
name: "x-custom", value: "random",
|
197
|
-
index: 1000, type: :substitution
|
198
|
-
})
|
199
|
-
}.should raise_error(HeaderException)
|
200
|
-
end
|
185
|
+
@cc.process({name: "x-custom", value: "random", type: :incremental})
|
186
|
+
(@cc.table - original_table).should eq [["x-custom", "random"]]
|
201
187
|
end
|
188
|
+
end
|
202
189
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
cc.process({
|
211
|
-
name: "x-custom",
|
212
|
-
value: "a" * (2048 - original_size),
|
213
|
-
type: :incremental
|
214
|
-
})
|
215
|
-
|
216
|
-
cc.table.last[0].should eq "x-custom"
|
217
|
-
cc.table.size.should eq original_table.size
|
218
|
-
end
|
219
|
-
|
220
|
-
it "should prepend on dropped substitution index" do
|
221
|
-
cc = EncodingContext.new(:request, 2048)
|
222
|
-
original_table = cc.table.dup
|
223
|
-
original_size = original_table.join.bytesize +
|
224
|
-
original_table.size * 32
|
225
|
-
|
226
|
-
cc.process({
|
227
|
-
name: "x-custom",
|
228
|
-
value: "a" * (2048 - original_size),
|
229
|
-
index: 0, type: :substitution
|
230
|
-
})
|
231
|
-
|
232
|
-
cc.table[0][0].should eq "x-custom"
|
233
|
-
cc.table[1][0].should eq ":scheme"
|
190
|
+
context "size bounds" do
|
191
|
+
it "should drop headers from end of table" do
|
192
|
+
cc = EncodingContext.new(table_size: 2048)
|
193
|
+
cc.instance_eval do
|
194
|
+
add_to_table(["test1", "1" * 1024])
|
195
|
+
add_to_table(["test2", "2" * 500])
|
234
196
|
end
|
235
|
-
end
|
236
197
|
|
237
|
-
|
238
|
-
|
198
|
+
original_table = cc.table.dup
|
199
|
+
original_size = original_table.join.bytesize +
|
200
|
+
original_table.size * 32
|
239
201
|
|
240
|
-
|
241
|
-
|
202
|
+
cc.process({
|
203
|
+
name: "x-custom",
|
204
|
+
value: "a" * (2048 - original_size),
|
205
|
+
type: :incremental
|
206
|
+
})
|
242
207
|
|
243
|
-
cc.
|
244
|
-
cc.
|
245
|
-
cc.table.should be_empty
|
246
|
-
|
247
|
-
cc.process(h)
|
248
|
-
cc.process(e.merge({type: :incremental}))
|
249
|
-
cc.table.should be_empty
|
208
|
+
cc.table.first[0].should eq "x-custom"
|
209
|
+
cc.table.size.should eq original_table.size # number of entries
|
250
210
|
end
|
251
211
|
end
|
252
|
-
end
|
253
212
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
{name: 11, value: "my-user-agent"},
|
261
|
-
{name: "mynewheader", value: "first"}
|
262
|
-
]
|
213
|
+
it "should clear table if entry exceeds table size" do
|
214
|
+
cc = EncodingContext.new(table_size: 2048)
|
215
|
+
cc.instance_eval do
|
216
|
+
add_to_table(["test1", "1" * 1024])
|
217
|
+
add_to_table(["test2", "2" * 500])
|
218
|
+
end
|
263
219
|
|
264
|
-
|
220
|
+
h = { name: "x-custom", value: "a", index: 0, type: :incremental }
|
221
|
+
e = { name: "large", value: "a" * 2048, index: 0}
|
265
222
|
|
266
|
-
|
267
|
-
|
268
|
-
|
223
|
+
cc.process(h)
|
224
|
+
cc.process(e.merge({type: :incremental}))
|
225
|
+
cc.table.should be_empty
|
269
226
|
end
|
270
227
|
|
271
|
-
it "should
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
@cc.table[31].should eq ["user-agent", "my-user-agent"]
|
282
|
-
@cc.table[32].should eq ["mynewheader", "first"]
|
283
|
-
@cc.table[33].should eq ["mynewheader", "second"]
|
228
|
+
it "should shrink table if set smaller size" do
|
229
|
+
cc = EncodingContext.new(table_size: 2048)
|
230
|
+
cc.instance_eval do
|
231
|
+
add_to_table(["test1", "1" * 1024])
|
232
|
+
add_to_table(["test2", "2" * 500])
|
233
|
+
end
|
234
|
+
|
235
|
+
cc.process({type: :changetablesize, value: 1500})
|
236
|
+
cc.table.size.should be 1
|
237
|
+
cc.table.first[0].should eq 'test2'
|
284
238
|
end
|
285
239
|
end
|
286
240
|
end
|
287
241
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
242
|
+
spec_examples = [
|
243
|
+
{ title: "D.3. Request Examples without Huffman",
|
244
|
+
type: :request,
|
245
|
+
table_size: 4096,
|
246
|
+
huffman: :never,
|
247
|
+
streams: [
|
248
|
+
{ wire: "8286 8441 0f77 7777 2e65 7861 6d70 6c65
|
249
|
+
2e63 6f6d",
|
250
|
+
emitted: [
|
251
|
+
[":method", "GET"],
|
252
|
+
[":scheme", "http"],
|
253
|
+
[":path", "/"],
|
254
|
+
[":authority", "www.example.com"],
|
255
|
+
],
|
256
|
+
table: [
|
257
|
+
[":authority", "www.example.com"],
|
258
|
+
],
|
259
|
+
table_size: 57,
|
260
|
+
},
|
261
|
+
{ wire: "8286 84be 5808 6e6f 2d63 6163 6865",
|
262
|
+
emitted: [
|
263
|
+
[":method", "GET"],
|
264
|
+
[":scheme", "http"],
|
265
|
+
[":path", "/"],
|
266
|
+
[":authority", "www.example.com"],
|
267
|
+
["cache-control", "no-cache"],
|
268
|
+
],
|
269
|
+
table: [
|
270
|
+
["cache-control", "no-cache"],
|
271
|
+
[":authority", "www.example.com"],
|
272
|
+
],
|
273
|
+
table_size: 110,
|
274
|
+
},
|
275
|
+
{ wire: "8287 85bf 400a 6375 7374 6f6d 2d6b 6579
|
276
|
+
0c63 7573 746f 6d2d 7661 6c75 65",
|
277
|
+
emitted: [
|
278
|
+
[":method", "GET"],
|
279
|
+
[":scheme", "https"],
|
280
|
+
[":path", "/index.html"],
|
281
|
+
[":authority", "www.example.com"],
|
282
|
+
["custom-key", "custom-value"],
|
283
|
+
],
|
284
|
+
table: [
|
285
|
+
["custom-key", "custom-value"],
|
286
|
+
["cache-control", "no-cache"],
|
287
|
+
[":authority", "www.example.com"],
|
288
|
+
],
|
289
|
+
table_size: 164,
|
290
|
+
}
|
291
|
+
],
|
292
|
+
},
|
293
|
+
{ title: "D.4. Request Examples with Huffman",
|
294
|
+
type: :request,
|
295
|
+
table_size: 4096,
|
296
|
+
huffman: :always,
|
297
|
+
streams: [
|
298
|
+
{ wire: "8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4 ff",
|
299
|
+
emitted: [
|
300
|
+
[":method", "GET"],
|
301
|
+
[":scheme", "http"],
|
302
|
+
[":path", "/"],
|
303
|
+
[":authority", "www.example.com"],
|
304
|
+
],
|
305
|
+
table: [
|
306
|
+
[":authority", "www.example.com"],
|
307
|
+
],
|
308
|
+
table_size: 57,
|
309
|
+
},
|
310
|
+
{ wire: "8286 84be 5886 a8eb 1064 9cbf",
|
311
|
+
emitted: [
|
312
|
+
[":method", "GET"],
|
313
|
+
[":scheme", "http"],
|
314
|
+
[":path", "/"],
|
315
|
+
[":authority", "www.example.com"],
|
316
|
+
["cache-control", "no-cache"],
|
317
|
+
],
|
318
|
+
table: [
|
319
|
+
["cache-control", "no-cache"],
|
320
|
+
[":authority", "www.example.com"],
|
321
|
+
],
|
322
|
+
table_size: 110,
|
323
|
+
},
|
324
|
+
{ wire: "8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925
|
325
|
+
a849 e95b b8e8 b4bf",
|
326
|
+
emitted: [
|
327
|
+
[":method", "GET"],
|
328
|
+
[":scheme", "https"],
|
329
|
+
[":path", "/index.html"],
|
330
|
+
[":authority", "www.example.com"],
|
331
|
+
["custom-key", "custom-value"],
|
332
|
+
],
|
333
|
+
table: [
|
334
|
+
["custom-key", "custom-value"],
|
335
|
+
["cache-control", "no-cache"],
|
336
|
+
[":authority", "www.example.com"],
|
337
|
+
],
|
338
|
+
table_size: 164,
|
339
|
+
},
|
340
|
+
],
|
341
|
+
},
|
342
|
+
{ title: "D.5. Response Examples without Huffman",
|
343
|
+
type: :response,
|
344
|
+
table_size: 256,
|
345
|
+
huffman: :never,
|
346
|
+
streams: [
|
347
|
+
{ wire: "4803 3330 3258 0770 7269 7661 7465 611d
|
348
|
+
4d6f 6e2c 2032 3120 4f63 7420 3230 3133
|
349
|
+
2032 303a 3133 3a32 3120 474d 546e 1768
|
350
|
+
7474 7073 3a2f 2f77 7777 2e65 7861 6d70
|
351
|
+
6c65 2e63 6f6d",
|
352
|
+
emitted: [
|
353
|
+
[":status", "302"],
|
354
|
+
["cache-control", "private"],
|
355
|
+
["date", "Mon, 21 Oct 2013 20:13:21 GMT"],
|
356
|
+
["location", "https://www.example.com"],
|
357
|
+
],
|
358
|
+
table: [
|
359
|
+
["location", "https://www.example.com"],
|
360
|
+
["date", "Mon, 21 Oct 2013 20:13:21 GMT"],
|
361
|
+
["cache-control", "private"],
|
362
|
+
[":status", "302"],
|
363
|
+
],
|
364
|
+
table_size: 222,
|
365
|
+
},
|
366
|
+
{ wire: "4803 3330 37c1 c0bf",
|
367
|
+
emitted: [
|
368
|
+
[":status", "307"],
|
369
|
+
["cache-control", "private"],
|
370
|
+
["date", "Mon, 21 Oct 2013 20:13:21 GMT"],
|
371
|
+
["location", "https://www.example.com"],
|
372
|
+
],
|
373
|
+
table: [
|
374
|
+
[":status", "307"],
|
375
|
+
["location", "https://www.example.com"],
|
376
|
+
["date", "Mon, 21 Oct 2013 20:13:21 GMT"],
|
377
|
+
["cache-control", "private"],
|
378
|
+
],
|
379
|
+
table_size: 222,
|
380
|
+
},
|
381
|
+
{ wire: "88c1 611d 4d6f 6e2c 2032 3120 4f63 7420
|
382
|
+
3230 3133 2032 303a 3133 3a32 3220 474d
|
383
|
+
54c0 5a04 677a 6970 7738 666f 6f3d 4153
|
384
|
+
444a 4b48 514b 425a 584f 5157 454f 5049
|
385
|
+
5541 5851 5745 4f49 553b 206d 6178 2d61
|
386
|
+
6765 3d33 3630 303b 2076 6572 7369 6f6e
|
387
|
+
3d31",
|
388
|
+
emitted: [
|
389
|
+
[":status", "200"],
|
390
|
+
["cache-control", "private"],
|
391
|
+
["date", "Mon, 21 Oct 2013 20:13:22 GMT"],
|
392
|
+
["location", "https://www.example.com"],
|
393
|
+
["content-encoding", "gzip"],
|
394
|
+
["set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"],
|
395
|
+
],
|
396
|
+
table: [
|
397
|
+
["set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"],
|
398
|
+
["content-encoding", "gzip"],
|
399
|
+
["date", "Mon, 21 Oct 2013 20:13:22 GMT"],
|
400
|
+
],
|
401
|
+
table_size: 215,
|
402
|
+
},
|
403
|
+
],
|
404
|
+
},
|
405
|
+
{ title: "D.6. Response Examples with Huffman",
|
406
|
+
type: :response,
|
407
|
+
table_size: 256,
|
408
|
+
huffman: :always,
|
409
|
+
streams: [
|
410
|
+
{ wire: "4882 6402 5885 aec3 771a 4b61 96d0 7abe
|
411
|
+
9410 54d4 44a8 2005 9504 0b81 66e0 82a6
|
412
|
+
2d1b ff6e 919d 29ad 1718 63c7 8f0b 97c8
|
413
|
+
e9ae 82ae 43d3",
|
414
|
+
emitted: [
|
415
|
+
[":status", "302"],
|
416
|
+
["cache-control", "private"],
|
417
|
+
["date", "Mon, 21 Oct 2013 20:13:21 GMT"],
|
418
|
+
["location", "https://www.example.com"],
|
419
|
+
],
|
420
|
+
table: [
|
421
|
+
["location", "https://www.example.com"],
|
422
|
+
["date", "Mon, 21 Oct 2013 20:13:21 GMT"],
|
423
|
+
["cache-control", "private"],
|
424
|
+
[":status", "302"],
|
425
|
+
],
|
426
|
+
table_size: 222,
|
427
|
+
},
|
428
|
+
{ wire: "4883 640e ffc1 c0bf",
|
429
|
+
emitted: [
|
430
|
+
[":status", "307"],
|
431
|
+
["cache-control", "private"],
|
432
|
+
["date", "Mon, 21 Oct 2013 20:13:21 GMT"],
|
433
|
+
["location", "https://www.example.com"],
|
434
|
+
],
|
435
|
+
table: [
|
436
|
+
[":status", "307"],
|
437
|
+
["location", "https://www.example.com"],
|
438
|
+
["date", "Mon, 21 Oct 2013 20:13:21 GMT"],
|
439
|
+
["cache-control", "private"],
|
440
|
+
],
|
441
|
+
table_size: 222,
|
442
|
+
},
|
443
|
+
{ wire: "88c1 6196 d07a be94 1054 d444 a820 0595
|
444
|
+
040b 8166 e084 a62d 1bff c05a 839b d9ab
|
445
|
+
77ad 94e7 821d d7f2 e6c7 b335 dfdf cd5b
|
446
|
+
3960 d5af 2708 7f36 72c1 ab27 0fb5 291f
|
447
|
+
9587 3160 65c0 03ed 4ee5 b106 3d50 07",
|
448
|
+
emitted: [
|
449
|
+
[":status", "200"],
|
450
|
+
["cache-control", "private"],
|
451
|
+
["date", "Mon, 21 Oct 2013 20:13:22 GMT"],
|
452
|
+
["location", "https://www.example.com"],
|
453
|
+
["content-encoding", "gzip"],
|
454
|
+
["set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"],
|
455
|
+
],
|
456
|
+
table: [
|
457
|
+
["set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"],
|
458
|
+
["content-encoding", "gzip"],
|
459
|
+
["date", "Mon, 21 Oct 2013 20:13:22 GMT"],
|
460
|
+
],
|
461
|
+
table_size: 215,
|
462
|
+
},
|
463
|
+
],
|
464
|
+
},
|
465
|
+
]
|
466
|
+
|
467
|
+
context "decode" do
|
468
|
+
spec_examples.each do |ex|
|
469
|
+
context "spec example #{ex[:title]}" do
|
470
|
+
ex[:streams].size.times do |nth|
|
471
|
+
context "request #{nth+1}" do
|
472
|
+
before { @dc = Decompressor.new(table_size: ex[:table_size]) }
|
473
|
+
before do
|
474
|
+
(0...nth).each do |i|
|
475
|
+
bytes = [ex[:streams][i][:wire].delete(" \n")].pack("H*")
|
476
|
+
@dc.decode(HTTP2::Buffer.new(bytes))
|
477
|
+
end
|
478
|
+
end
|
479
|
+
subject do
|
480
|
+
bytes = [ex[:streams][nth][:wire].delete(" \n")].pack("H*")
|
481
|
+
@emitted = @dc.decode(HTTP2::Buffer.new(bytes))
|
482
|
+
end
|
483
|
+
it "should emit expected headers" do
|
484
|
+
subject
|
485
|
+
# order-perserving compare
|
486
|
+
@emitted.should eq ex[:streams][nth][:emitted]
|
487
|
+
end
|
488
|
+
it "should update header table" do
|
489
|
+
subject
|
490
|
+
@dc.instance_eval{@cc.table}.should eq ex[:streams][nth][:table]
|
491
|
+
end
|
492
|
+
it "should compute header table size" do
|
493
|
+
subject
|
494
|
+
@dc.instance_eval{@cc.current_table_size}.should eq ex[:streams][nth][:table_size]
|
495
|
+
end
|
496
|
+
end
|
497
|
+
end
|
498
|
+
end
|
382
499
|
end
|
500
|
+
end
|
383
501
|
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
502
|
+
context "encode" do
|
503
|
+
spec_examples.each do |ex|
|
504
|
+
context "spec example #{ex[:title]}" do
|
505
|
+
ex[:streams].size.times do |nth|
|
506
|
+
context "request #{nth+1}" do
|
507
|
+
before { @cc = Compressor.new(table_size: ex[:table_size],
|
508
|
+
huffman: ex[:huffman]) }
|
509
|
+
before do
|
510
|
+
(0...nth).each do |i|
|
511
|
+
@cc.encode(ex[:streams][i][:emitted])
|
512
|
+
end
|
513
|
+
end
|
514
|
+
subject do
|
515
|
+
@cc.encode(ex[:streams][nth][:emitted])
|
516
|
+
end
|
517
|
+
it "should emit expected bytes on wire" do
|
518
|
+
subject.unpack("H*").first.should eq ex[:streams][nth][:wire].delete(" \n")
|
519
|
+
end
|
520
|
+
it "should update header table" do
|
521
|
+
subject
|
522
|
+
@cc.instance_eval{@cc.table}.should eq ex[:streams][nth][:table]
|
523
|
+
end
|
524
|
+
it "should compute header table size" do
|
525
|
+
subject
|
526
|
+
@cc.instance_eval{@cc.current_table_size}.should eq ex[:streams][nth][:table_size]
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
530
|
+
end
|
391
531
|
end
|
392
532
|
end
|
533
|
+
|
393
534
|
end
|