http-2 0.6.1 → 0.6.3
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.
- 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
|