http-2 0.6.1 → 0.6.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.autotest +2 -1
- data/Gemfile +8 -1
- data/README.md +27 -21
- data/example/README.md +53 -0
- data/example/client.rb +88 -30
- data/example/helper.rb +5 -0
- data/example/keys/mycert.pem +24 -0
- data/example/keys/mykey.pem +27 -0
- data/example/server.rb +47 -8
- data/http-2.gemspec +0 -2
- data/lib/http/2.rb +2 -0
- data/lib/http/2/buffer.rb +21 -4
- data/lib/http/2/client.rb +50 -0
- data/lib/http/2/compressor.rb +197 -181
- data/lib/http/2/connection.rb +57 -83
- data/lib/http/2/emitter.rb +2 -2
- data/lib/http/2/error.rb +5 -0
- data/lib/http/2/framer.rb +32 -31
- data/lib/http/2/server.rb +55 -0
- data/lib/http/2/stream.rb +1 -1
- data/lib/http/2/version.rb +1 -1
- data/spec/buffer_spec.rb +23 -0
- data/spec/client_spec.rb +93 -0
- data/spec/compressor_spec.rb +89 -80
- data/spec/connection_spec.rb +24 -75
- data/spec/emitter_spec.rb +8 -0
- data/spec/framer_spec.rb +36 -40
- data/spec/helper.rb +6 -2
- data/spec/server_spec.rb +50 -0
- data/spec/stream_spec.rb +23 -30
- metadata +13 -30
@@ -0,0 +1,55 @@
|
|
1
|
+
module HTTP2
|
2
|
+
|
3
|
+
# HTTP 2.0 server connection class that implements appropriate header
|
4
|
+
# compression / decompression algorithms and stream management logic.
|
5
|
+
#
|
6
|
+
# Your code is responsible for feeding request data to the server object,
|
7
|
+
# which in turn performs all of the necessary HTTP 2.0 decoding / encoding,
|
8
|
+
# state management, and the rest. A simple example:
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# socket = YourTransport.new
|
12
|
+
#
|
13
|
+
# conn = HTTP2::Server.new
|
14
|
+
# conn.on(:stream) do |stream|
|
15
|
+
# ...
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# while bytes = socket.read
|
19
|
+
# conn << bytes
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
class Server < Connection
|
23
|
+
|
24
|
+
# Initialize new HTTP 2.0 server object.
|
25
|
+
def initialize(*args)
|
26
|
+
@stream_id = 2
|
27
|
+
@state = :new
|
28
|
+
@compressor = Header::Compressor.new(:response)
|
29
|
+
@decompressor = Header::Decompressor.new(:request)
|
30
|
+
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Handle locally initiated server-push event emitted by the stream.
|
37
|
+
#
|
38
|
+
# @param args [Array]
|
39
|
+
# @param callback [Proc]
|
40
|
+
def promise(*args, &callback)
|
41
|
+
parent, headers, flags = *args
|
42
|
+
promise = new_stream(parent: parent)
|
43
|
+
promise.send({
|
44
|
+
type: :push_promise,
|
45
|
+
flags: flags,
|
46
|
+
stream: parent.id,
|
47
|
+
promise_stream: promise.id,
|
48
|
+
payload: headers.to_a
|
49
|
+
})
|
50
|
+
|
51
|
+
callback.call(promise)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
data/lib/http/2/stream.rb
CHANGED
data/lib/http/2/version.rb
CHANGED
data/spec/buffer_spec.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
describe HTTP2::Buffer do
|
4
|
+
|
5
|
+
let(:b) { Buffer.new("émalgré") }
|
6
|
+
|
7
|
+
it "should force 8-bit encoding" do
|
8
|
+
b.encoding.to_s.should eq "ASCII-8BIT"
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should return bytesize of the buffer" do
|
12
|
+
b.size.should eq 9
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should read single byte at a time" do
|
16
|
+
9.times { b.read(1).should_not be_nil }
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should unpack an unsigned 32-bit int" do
|
20
|
+
Buffer.new([256].pack("N")).read_uint32.should eq 256
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
require "helper"
|
2
|
+
|
3
|
+
describe HTTP2::Client do
|
4
|
+
before(:each) do
|
5
|
+
@client = Client.new
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:f) { Framer.new }
|
9
|
+
|
10
|
+
context "initialization and settings" do
|
11
|
+
it "should return odd stream IDs" do
|
12
|
+
@client.new_stream.id.should_not be_even
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should emit connection header and SETTINGS on new client connection" do
|
16
|
+
frames = []
|
17
|
+
@client.on(:frame) { |bytes| frames << bytes }
|
18
|
+
@client.ping("12345678")
|
19
|
+
|
20
|
+
frames[0].should eq CONNECTION_HEADER
|
21
|
+
f.parse(frames[1])[:type].should eq :settings
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should initialize client with custom connection settings" do
|
25
|
+
frames = []
|
26
|
+
|
27
|
+
@client = Client.new(streams: 200)
|
28
|
+
@client.on(:frame) { |bytes| frames << bytes }
|
29
|
+
@client.ping("12345678")
|
30
|
+
|
31
|
+
frame = f.parse(frames[1])
|
32
|
+
frame[:type].should eq :settings
|
33
|
+
frame[:payload][:settings_max_concurrent_streams].should eq 200
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "push" do
|
38
|
+
it "should disallow client initiated push" do
|
39
|
+
expect do
|
40
|
+
@client.promise({}) {}
|
41
|
+
end.to raise_error(Exception)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should raise error on PUSH_PROMISE against stream 0" do
|
45
|
+
expect {
|
46
|
+
@client << set_stream_id(f.generate(PUSH_PROMISE), 0)
|
47
|
+
}.to raise_error(ProtocolError)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should raise error on PUSH_PROMISE against bogus stream" do
|
51
|
+
expect {
|
52
|
+
@client << set_stream_id(f.generate(PUSH_PROMISE), 31415)
|
53
|
+
}.to raise_error(ProtocolError)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should raise error on PUSH_PROMISE against non-idle stream" do
|
57
|
+
expect {
|
58
|
+
s = @client.new_stream
|
59
|
+
s.send HEADERS
|
60
|
+
|
61
|
+
@client << set_stream_id(f.generate(PUSH_PROMISE), s.id)
|
62
|
+
@client << set_stream_id(f.generate(PUSH_PROMISE), s.id)
|
63
|
+
}.to raise_error(ProtocolError)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should emit stream object for received PUSH_PROMISE" do
|
67
|
+
s = @client.new_stream
|
68
|
+
s.send HEADERS
|
69
|
+
|
70
|
+
promise = nil
|
71
|
+
@client.on(:promise) {|s| promise = s }
|
72
|
+
@client << set_stream_id(f.generate(PUSH_PROMISE), s.id)
|
73
|
+
|
74
|
+
promise.id.should eq 2
|
75
|
+
promise.state.should eq :reserved_remote
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should auto RST_STREAM promises against locally-RST stream" do
|
79
|
+
s = @client.new_stream
|
80
|
+
s.send HEADERS
|
81
|
+
s.close
|
82
|
+
|
83
|
+
@client.stub(:send)
|
84
|
+
@client.should_receive(:send) do |frame|
|
85
|
+
frame[:type].should eq :rst_stream
|
86
|
+
frame[:stream].should eq 2
|
87
|
+
end
|
88
|
+
|
89
|
+
@client << set_stream_id(f.generate(PUSH_PROMISE), s.id)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
data/spec/compressor_spec.rb
CHANGED
@@ -10,25 +10,25 @@ describe HTTP2::Header do
|
|
10
10
|
it "should encode 10 using a 5-bit prefix" do
|
11
11
|
buf = c.integer(10, 5)
|
12
12
|
buf.should eq [10].pack('C')
|
13
|
-
d.integer(
|
13
|
+
d.integer(Buffer.new(buf), 5).should eq 10
|
14
14
|
end
|
15
15
|
|
16
16
|
it "should encode 10 using a 0-bit prefix" do
|
17
17
|
buf = c.integer(10, 0)
|
18
18
|
buf.should eq [10].pack('C')
|
19
|
-
d.integer(
|
19
|
+
d.integer(Buffer.new(buf), 0).should eq 10
|
20
20
|
end
|
21
21
|
|
22
22
|
it "should encode 1337 using a 5-bit prefix" do
|
23
23
|
buf = c.integer(1337, 5)
|
24
24
|
buf.should eq [31,128+26,10].pack('C*')
|
25
|
-
d.integer(
|
25
|
+
d.integer(Buffer.new(buf), 5).should eq 1337
|
26
26
|
end
|
27
27
|
|
28
28
|
it "should encode 1337 using a 0-bit prefix" do
|
29
29
|
buf = c.integer(1337,0)
|
30
30
|
buf.should eq [128+57,10].pack('C*')
|
31
|
-
d.integer(
|
31
|
+
d.integer(Buffer.new(buf), 0).should eq 1337
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
@@ -37,7 +37,7 @@ describe HTTP2::Header do
|
|
37
37
|
ascii = "abcdefghij"
|
38
38
|
str = c.string(ascii)
|
39
39
|
|
40
|
-
buf =
|
40
|
+
buf = Buffer.new(str+"trailer")
|
41
41
|
d.string(buf).should eq ascii
|
42
42
|
end
|
43
43
|
|
@@ -45,7 +45,7 @@ describe HTTP2::Header do
|
|
45
45
|
utf8 = "éáűőúöüó€"
|
46
46
|
str = c.string(utf8)
|
47
47
|
|
48
|
-
buf =
|
48
|
+
buf = Buffer.new(str+"trailer")
|
49
49
|
d.string(buf).should eq utf8
|
50
50
|
end
|
51
51
|
|
@@ -53,7 +53,7 @@ describe HTTP2::Header do
|
|
53
53
|
utf8 = "éáűőúöüó€"*100
|
54
54
|
str = c.string(utf8)
|
55
55
|
|
56
|
-
buf =
|
56
|
+
buf = Buffer.new(str+"trailer")
|
57
57
|
d.string(buf).should eq utf8
|
58
58
|
end
|
59
59
|
end
|
@@ -62,120 +62,105 @@ describe HTTP2::Header do
|
|
62
62
|
context "header representation" do
|
63
63
|
it "should handle indexed representation" do
|
64
64
|
h = {name: 10, type: :indexed}
|
65
|
-
|
66
|
-
indexed = StringIO.new(c.header(h))
|
67
|
-
d.header(indexed).should eq h
|
65
|
+
d.header(c.header(h)).should eq h
|
68
66
|
end
|
69
67
|
|
70
68
|
context "literal w/o indexing representation" do
|
71
69
|
it "should handle indexed header" do
|
72
70
|
h = {name: 10, value: "my-value", type: :noindex}
|
73
|
-
|
74
|
-
literal = StringIO.new(c.header(h))
|
75
|
-
d.header(literal).should eq h
|
71
|
+
d.header(c.header(h)).should eq h
|
76
72
|
end
|
77
73
|
|
78
74
|
it "should handle literal header" do
|
79
75
|
h = {name: "x-custom", value: "my-value", type: :noindex}
|
80
|
-
|
81
|
-
literal = StringIO.new(c.header(h))
|
82
|
-
d.header(literal).should eq h
|
76
|
+
d.header(c.header(h)).should eq h
|
83
77
|
end
|
84
78
|
end
|
85
79
|
|
86
80
|
context "literal w/ incremental indexing" do
|
87
81
|
it "should handle indexed header" do
|
88
82
|
h = {name: 10, value: "my-value", type: :incremental}
|
89
|
-
|
90
|
-
literal = StringIO.new(c.header(h))
|
91
|
-
d.header(literal).should eq h
|
83
|
+
d.header(c.header(h)).should eq h
|
92
84
|
end
|
93
85
|
|
94
86
|
it "should handle literal header" do
|
95
87
|
h = {name: "x-custom", value: "my-value", type: :incremental}
|
96
|
-
|
97
|
-
literal = StringIO.new(c.header(h))
|
98
|
-
d.header(literal).should eq h
|
88
|
+
d.header(c.header(h)).should eq h
|
99
89
|
end
|
100
90
|
end
|
101
91
|
|
102
92
|
context "literal w/ substitution indexing" do
|
103
93
|
it "should handle indexed header" do
|
104
94
|
h = {name: 1, value: "my-value", index: 10, type: :substitution}
|
105
|
-
|
106
|
-
literal = StringIO.new(c.header(h))
|
107
|
-
d.header(literal).should eq h
|
95
|
+
d.header(c.header(h)).should eq h
|
108
96
|
end
|
109
97
|
|
110
98
|
it "should handle literal header" do
|
111
99
|
h = {name: "x-new", value: "my-value", index: 10, type: :substitution}
|
112
|
-
|
113
|
-
literal = StringIO.new(c.header(h))
|
114
|
-
d.header(literal).should eq h
|
100
|
+
d.header(c.header(h)).should eq h
|
115
101
|
end
|
116
102
|
end
|
117
103
|
end
|
118
104
|
|
119
105
|
context "differential coding" do
|
120
106
|
context "shared compression context" do
|
121
|
-
before(:each) { @cc =
|
107
|
+
before(:each) { @cc = EncodingContext.new(:request) }
|
122
108
|
|
123
109
|
it "should be initialized with pre-defined headers" do
|
124
|
-
cc =
|
125
|
-
cc.table.size.should eq
|
110
|
+
cc = EncodingContext.new(:request)
|
111
|
+
cc.table.size.should eq 30
|
126
112
|
|
127
|
-
cc =
|
128
|
-
cc.table.size.should eq
|
113
|
+
cc = EncodingContext.new(:response)
|
114
|
+
cc.table.size.should eq 30
|
129
115
|
end
|
130
116
|
|
131
117
|
it "should be initialized with empty working set" do
|
132
|
-
@cc.
|
118
|
+
@cc.refset.should be_empty
|
133
119
|
end
|
134
120
|
|
135
121
|
it "should update working set based on prior state" do
|
136
|
-
@cc.
|
137
|
-
@cc.workset.should be_empty
|
122
|
+
@cc.refset.should be_empty
|
138
123
|
|
139
124
|
@cc.process({name: 0, type: :indexed})
|
140
|
-
@cc.
|
141
|
-
@cc.workset.should eq [[0, [":scheme", "http"]]]
|
125
|
+
@cc.refset.should eq [[0, [":scheme", "http"]]]
|
142
126
|
|
143
127
|
@cc.process({name: 0, type: :indexed})
|
144
|
-
@cc.
|
145
|
-
@cc.workset.should be_empty
|
128
|
+
@cc.refset.should be_empty
|
146
129
|
end
|
147
130
|
|
148
131
|
context "processing" do
|
149
132
|
it "should toggle index representation headers in working set" do
|
150
133
|
@cc.process({name: 0, type: :indexed})
|
151
|
-
@cc.
|
134
|
+
@cc.refset.first.should eq [0, [":scheme", "http"]]
|
152
135
|
|
153
136
|
@cc.process({name: 0, type: :indexed})
|
154
|
-
@cc.
|
137
|
+
@cc.refset.should be_empty
|
155
138
|
end
|
156
139
|
|
157
140
|
context "no indexing" do
|
158
141
|
it "should process indexed header with literal value" do
|
159
142
|
original_table = @cc.table
|
160
143
|
|
161
|
-
@cc.process({name: 3, value: "/path", type: :noindex})
|
162
|
-
|
144
|
+
emit = @cc.process({name: 3, value: "/path", type: :noindex})
|
145
|
+
emit.should eq [":path", "/path"]
|
146
|
+
@cc.refset.should be_empty
|
163
147
|
@cc.table.should eq original_table
|
164
148
|
end
|
165
149
|
|
166
150
|
it "should process indexed header with default value" do
|
167
151
|
original_table = @cc.table
|
168
152
|
|
169
|
-
@cc.process({name: 3, type: :noindex})
|
170
|
-
|
153
|
+
emit = @cc.process({name: 3, type: :noindex})
|
154
|
+
emit.should eq [":path", "/"]
|
171
155
|
@cc.table.should eq original_table
|
172
156
|
end
|
173
157
|
|
174
158
|
it "should process literal header with literal value" do
|
175
159
|
original_table = @cc.table
|
176
160
|
|
177
|
-
@cc.process({name: "x-custom", value: "random", type: :noindex})
|
178
|
-
|
161
|
+
emit = @cc.process({name: "x-custom", value: "random", type: :noindex})
|
162
|
+
emit.should eq ["x-custom", "random"]
|
163
|
+
@cc.refset.should be_empty
|
179
164
|
@cc.table.should eq original_table
|
180
165
|
end
|
181
166
|
end
|
@@ -185,7 +170,7 @@ describe HTTP2::Header do
|
|
185
170
|
original_table = @cc.table.dup
|
186
171
|
|
187
172
|
@cc.process({name: "x-custom", value: "random", type: :incremental})
|
188
|
-
@cc.
|
173
|
+
@cc.refset.first.should eq [original_table.size, ["x-custom", "random"]]
|
189
174
|
(@cc.table - original_table).should eq [["x-custom", "random"]]
|
190
175
|
end
|
191
176
|
end
|
@@ -200,9 +185,9 @@ describe HTTP2::Header do
|
|
200
185
|
index: idx, type: :substitution
|
201
186
|
})
|
202
187
|
|
203
|
-
@cc.
|
188
|
+
@cc.refset.first.should eq [idx, ["x-custom", "random"]]
|
204
189
|
(@cc.table - original_table).should eq [["x-custom", "random"]]
|
205
|
-
(original_table - @cc.table).should eq [["
|
190
|
+
(original_table - @cc.table).should eq [["via", ""]]
|
206
191
|
end
|
207
192
|
|
208
193
|
it "should raise error on invalid substitution index" do
|
@@ -217,7 +202,7 @@ describe HTTP2::Header do
|
|
217
202
|
|
218
203
|
context "size bounds" do
|
219
204
|
it "should drop headers from beginning of table" do
|
220
|
-
cc =
|
205
|
+
cc = EncodingContext.new(:request, 2048)
|
221
206
|
original_table = cc.table.dup
|
222
207
|
original_size = original_table.join.bytesize +
|
223
208
|
original_table.size * 32
|
@@ -233,7 +218,7 @@ describe HTTP2::Header do
|
|
233
218
|
end
|
234
219
|
|
235
220
|
it "should prepend on dropped substitution index" do
|
236
|
-
cc =
|
221
|
+
cc = EncodingContext.new(:request, 2048)
|
237
222
|
original_table = cc.table.dup
|
238
223
|
original_size = original_table.join.bytesize +
|
239
224
|
original_table.size * 32
|
@@ -248,39 +233,54 @@ describe HTTP2::Header do
|
|
248
233
|
cc.table[1][0].should eq ":scheme"
|
249
234
|
end
|
250
235
|
end
|
236
|
+
|
237
|
+
it "should clear table if entry exceeds table size" do
|
238
|
+
cc = EncodingContext.new(:request, 2048)
|
239
|
+
|
240
|
+
h = { name: "x-custom", value: "a", index: 0, type: :incremental }
|
241
|
+
e = { name: "large", value: "a" * 2048, index: 0}
|
242
|
+
|
243
|
+
cc.process(h)
|
244
|
+
cc.process(e.merge({type: :substitution}))
|
245
|
+
cc.table.should be_empty
|
246
|
+
|
247
|
+
cc.process(h)
|
248
|
+
cc.process(e.merge({type: :incremental}))
|
249
|
+
cc.table.should be_empty
|
250
|
+
end
|
251
251
|
end
|
252
252
|
end
|
253
253
|
|
254
254
|
context "integration" do
|
255
|
-
before (:all) { @cc =
|
255
|
+
before (:all) { @cc = EncodingContext.new(:request) }
|
256
256
|
|
257
257
|
it "should match first header set in spec appendix" do
|
258
258
|
req_headers = [
|
259
259
|
{name: 3, value: "/my-example/index.html"},
|
260
|
-
{name:
|
261
|
-
{name: "
|
260
|
+
{name: 11, value: "my-user-agent"},
|
261
|
+
{name: "mynewheader", value: "first"}
|
262
262
|
]
|
263
263
|
|
264
264
|
req_headers.each {|h| @cc.process(h.merge({type: :incremental})) }
|
265
265
|
|
266
|
-
@cc.table[
|
267
|
-
@cc.table[
|
268
|
-
@cc.table[
|
266
|
+
@cc.table[30].should eq [":path", "/my-example/index.html"]
|
267
|
+
@cc.table[31].should eq ["user-agent", "my-user-agent"]
|
268
|
+
@cc.table[32].should eq req_headers[2].values
|
269
269
|
end
|
270
270
|
|
271
271
|
it "should match second header set in spec appendix" do
|
272
|
-
@cc.process({name:
|
273
|
-
@cc.process({name:
|
272
|
+
@cc.process({name: 30, type: :indexed})
|
273
|
+
@cc.process({name: 31, type: :indexed})
|
274
274
|
@cc.process({
|
275
275
|
name: 3, value: "/my-example/resources/script.js",
|
276
|
-
index:
|
276
|
+
index: 30, type: :substitution
|
277
277
|
})
|
278
|
-
@cc.process({name:
|
278
|
+
@cc.process({name: 32, value: "second", type: :incremental})
|
279
279
|
|
280
|
-
@cc.table[
|
281
|
-
@cc.table[
|
282
|
-
@cc.table[
|
283
|
-
@cc.table[
|
280
|
+
@cc.table[30].should eq [":path", "/my-example/resources/script.js"]
|
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"]
|
284
284
|
end
|
285
285
|
end
|
286
286
|
end
|
@@ -297,12 +297,12 @@ describe HTTP2::Header do
|
|
297
297
|
0x44, # (literal header with incremental indexing, name index = 3)
|
298
298
|
0x16, # (header value string length = 22)
|
299
299
|
"/my-example/index.html".bytes,
|
300
|
-
|
300
|
+
0x4C, # (literal header with incremental indexing, name index = 11)
|
301
301
|
0x0D, # (header value string length = 13)
|
302
302
|
"my-user-agent".bytes,
|
303
303
|
0x40, # (literal header with incremental indexing, new name)
|
304
304
|
0x0B, # (header name string length = 11)
|
305
|
-
"
|
305
|
+
"mynewheader".bytes,
|
306
306
|
0x05, # (header value string length = 5)
|
307
307
|
"first".bytes
|
308
308
|
].flatten
|
@@ -310,7 +310,7 @@ describe HTTP2::Header do
|
|
310
310
|
E1_HEADERS = [
|
311
311
|
[":path", "/my-example/index.html"],
|
312
312
|
["user-agent", "my-user-agent"],
|
313
|
-
["
|
313
|
+
["mynewheader", "first"]
|
314
314
|
]
|
315
315
|
|
316
316
|
it "should match first header set in spec appendix" do
|
@@ -318,18 +318,18 @@ describe HTTP2::Header do
|
|
318
318
|
end
|
319
319
|
|
320
320
|
it "should decode first header set in spec appendix" do
|
321
|
-
@dc.decode(
|
321
|
+
@dc.decode(Buffer.new(E1_BYTES.pack("C*"))).should eq E1_HEADERS
|
322
322
|
end
|
323
323
|
|
324
324
|
E2_BYTES = [
|
325
|
-
|
326
|
-
|
325
|
+
0x9e, # (indexed header, index = 30: removal from reference set)
|
326
|
+
0xa0, # (indexed header, index = 32: removal from reference set)
|
327
327
|
0x04, # (literal header, substitution indexing, name index = 3)
|
328
|
-
|
328
|
+
0x1e, # (replaced entry index = 30)
|
329
329
|
0x1f, # (header value string length = 31)
|
330
330
|
"/my-example/resources/script.js".bytes,
|
331
331
|
0x5f,
|
332
|
-
|
332
|
+
0x02, # (literal header, incremental indexing, name index = 32)
|
333
333
|
0x06, # (header value string length = 6)
|
334
334
|
"second".bytes
|
335
335
|
].flatten
|
@@ -337,7 +337,7 @@ describe HTTP2::Header do
|
|
337
337
|
E2_HEADERS = [
|
338
338
|
[":path", "/my-example/resources/script.js"],
|
339
339
|
["user-agent", "my-user-agent"],
|
340
|
-
["
|
340
|
+
["mynewheader", "second"]
|
341
341
|
]
|
342
342
|
|
343
343
|
it "should match second header set in spec appendix" do
|
@@ -356,15 +356,15 @@ describe HTTP2::Header do
|
|
356
356
|
end
|
357
357
|
|
358
358
|
it "should decode second header set in spec appendix" do
|
359
|
-
@dc.decode(
|
359
|
+
@dc.decode(Buffer.new(E2_BYTES.pack("C*"))).should match_array E2_HEADERS
|
360
360
|
end
|
361
361
|
|
362
362
|
it "encode-decode should be invariant" do
|
363
363
|
cc = Compressor.new(:request)
|
364
364
|
dc = Decompressor.new(:request)
|
365
365
|
|
366
|
-
E1_HEADERS.should match_array dc.decode(
|
367
|
-
E2_HEADERS.should match_array dc.decode(
|
366
|
+
E1_HEADERS.should match_array dc.decode(cc.encode(E1_HEADERS))
|
367
|
+
E2_HEADERS.should match_array dc.decode(cc.encode(E2_HEADERS))
|
368
368
|
end
|
369
369
|
|
370
370
|
it "should encode-decode request set of headers" do
|
@@ -378,7 +378,16 @@ describe HTTP2::Header do
|
|
378
378
|
["accept", "*/*"]
|
379
379
|
]
|
380
380
|
|
381
|
-
dc.decode(
|
381
|
+
dc.decode(cc.encode(req)).should eq req
|
382
|
+
end
|
383
|
+
|
384
|
+
it "should downcase all request header names" do
|
385
|
+
cc = Compressor.new(:request)
|
386
|
+
dc = Decompressor.new(:request)
|
387
|
+
|
388
|
+
req = [["Accept", "IMAGE/PNG"]]
|
389
|
+
recv = dc.decode(cc.encode(req))
|
390
|
+
recv.should eq [["accept", "IMAGE/PNG"]]
|
382
391
|
end
|
383
392
|
end
|
384
393
|
end
|