hpack 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.rspec +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +40 -0
- data/Rakefile +7 -0
- data/hpack.gemspec +25 -0
- data/lib/hpack.rb +10 -0
- data/lib/hpack/decoder.rb +110 -0
- data/lib/hpack/encoder.rb +4 -0
- data/lib/hpack/field.rb +11 -0
- data/lib/hpack/huffman.rb +326 -0
- data/lib/hpack/integer_reader.rb +19 -0
- data/lib/hpack/lookup_table.rb +172 -0
- data/lib/hpack/version.rb +3 -0
- data/spec/decoder_spec.rb +312 -0
- data/spec/integer_reader_spec.rb +30 -0
- data/spec/spec_helper.rb +87 -0
- metadata +140 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module Hpack
|
2
|
+
class IntegerReader
|
3
|
+
def read length: 7, input: nil, start_with: 0
|
4
|
+
prefix_mask = 0xFF >> (8 - length)
|
5
|
+
result = start_with & prefix_mask
|
6
|
+
|
7
|
+
return result if result != prefix_mask
|
8
|
+
|
9
|
+
index = 0
|
10
|
+
begin
|
11
|
+
next_byte = input.readbyte
|
12
|
+
result += (next_byte & 0b0111_1111) << (7 * index)
|
13
|
+
index += 1
|
14
|
+
end until next_byte & 0b1000_0000 == 0
|
15
|
+
|
16
|
+
result
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
module Hpack
|
2
|
+
class LookupTable
|
3
|
+
class IndexOutOfBounds < StandardError; end
|
4
|
+
|
5
|
+
class Entry
|
6
|
+
attr_accessor :name
|
7
|
+
attr_accessor :value
|
8
|
+
|
9
|
+
def initialize name, value = ""
|
10
|
+
@name = name
|
11
|
+
@value = value
|
12
|
+
end
|
13
|
+
|
14
|
+
def size
|
15
|
+
32 + name.length + value.length
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_a
|
19
|
+
[name, value]
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
"(s = %4d) %s: %s" % [size, name, value]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
SETTINGS_HEADER_TABLE_SIZE = 4096
|
28
|
+
STATIC_TABLE_SIZE = 61
|
29
|
+
STATIC_ENTRIES = [
|
30
|
+
[":authority"],
|
31
|
+
[":method", "GET"],
|
32
|
+
[":method", "POST"],
|
33
|
+
[":path", "/"],
|
34
|
+
[":path", "/index.html"],
|
35
|
+
[":scheme", "http"],
|
36
|
+
[":scheme", "https"],
|
37
|
+
[":status", "200"],
|
38
|
+
[":status", "204"],
|
39
|
+
[":status", "206"],
|
40
|
+
[":status", "304"],
|
41
|
+
[":status", "400"],
|
42
|
+
[":status", "404"],
|
43
|
+
[":status", "500"],
|
44
|
+
["accept-charset"],
|
45
|
+
["accept-encoding", "gzip, deflate"],
|
46
|
+
["accept-language"],
|
47
|
+
["accept-ranges"],
|
48
|
+
["accept"],
|
49
|
+
["access-control-allow-origin"],
|
50
|
+
["age"],
|
51
|
+
["allow"],
|
52
|
+
["authorization"],
|
53
|
+
["cache-control"],
|
54
|
+
["content-disposition"],
|
55
|
+
["content-encoding"],
|
56
|
+
["content-language"],
|
57
|
+
["content-length"],
|
58
|
+
["content-location"],
|
59
|
+
["content-range"],
|
60
|
+
["content-type"],
|
61
|
+
["cookie"],
|
62
|
+
["date"],
|
63
|
+
["etag"],
|
64
|
+
["expect"],
|
65
|
+
["expires"],
|
66
|
+
["from"],
|
67
|
+
["host"],
|
68
|
+
["if-match"],
|
69
|
+
["if-modified-since"],
|
70
|
+
["if-none-match"],
|
71
|
+
["if-range"],
|
72
|
+
["if-unmodified-since"],
|
73
|
+
["last-modified"],
|
74
|
+
["link"],
|
75
|
+
["location"],
|
76
|
+
["max-forwards"],
|
77
|
+
["proxy-authenticate"],
|
78
|
+
["proxy-authorization"],
|
79
|
+
["range"],
|
80
|
+
["referer"],
|
81
|
+
["refresh"],
|
82
|
+
["retry-after"],
|
83
|
+
["server"],
|
84
|
+
["set-cookie"],
|
85
|
+
["strict-transport-security"],
|
86
|
+
["transfer-encoding"],
|
87
|
+
["user-agent"],
|
88
|
+
["vary"],
|
89
|
+
["via"],
|
90
|
+
["www-authenticate"],
|
91
|
+
]
|
92
|
+
|
93
|
+
def initialize max_size: SETTINGS_HEADER_TABLE_SIZE
|
94
|
+
@max_size = max_size
|
95
|
+
@dynamic_entries = []
|
96
|
+
end
|
97
|
+
|
98
|
+
def [] index
|
99
|
+
if index <= STATIC_TABLE_SIZE
|
100
|
+
Entry.new(*STATIC_ENTRIES[index - 1])
|
101
|
+
else
|
102
|
+
dynamic_index = index - STATIC_TABLE_SIZE - 1
|
103
|
+
size = @dynamic_entries.length
|
104
|
+
|
105
|
+
if dynamic_index >= size
|
106
|
+
raise IndexOutOfBounds, "#{dynamic_index} is greater than dynamic table size #{size}"
|
107
|
+
end
|
108
|
+
|
109
|
+
@dynamic_entries[dynamic_index]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# 4.1 Calculating Table Size
|
114
|
+
#
|
115
|
+
# The size of the dynamic table is the sum of the size of its entries.
|
116
|
+
#
|
117
|
+
# The size of an entry is the sum of its name's length in octets
|
118
|
+
# (as defined in Section 5.2), its value's length in octets (see
|
119
|
+
# Section 5.2), plus 32.
|
120
|
+
#
|
121
|
+
# The size of an entry is calculated using the length of the name
|
122
|
+
# and value without any Huffman encoding applied.
|
123
|
+
#
|
124
|
+
# NOTE: The additional 32 octets account for the overhead
|
125
|
+
# associated with an entry. For example, an entry structure using
|
126
|
+
# two 64-bit pointers to reference the name and the value of the
|
127
|
+
# entry, and two 64-bit integers for counting the number of
|
128
|
+
# references to the name and value would have 32 octets of
|
129
|
+
# overhead.
|
130
|
+
#
|
131
|
+
def size
|
132
|
+
@dynamic_entries
|
133
|
+
.map(&:size)
|
134
|
+
.reduce(0, :+)
|
135
|
+
end
|
136
|
+
|
137
|
+
def max_size
|
138
|
+
@max_size
|
139
|
+
end
|
140
|
+
|
141
|
+
def max_size= value
|
142
|
+
@max_size = value
|
143
|
+
evict
|
144
|
+
end
|
145
|
+
|
146
|
+
def << entry
|
147
|
+
@dynamic_entries.unshift entry
|
148
|
+
evict
|
149
|
+
end
|
150
|
+
|
151
|
+
def to_s
|
152
|
+
table = @dynamic_entries
|
153
|
+
.each_with_index
|
154
|
+
.map { |e, i| "[%4s] %s" % [i + 1, e.to_s] }
|
155
|
+
.join "\n"
|
156
|
+
|
157
|
+
summary = " Table size: #{size}"
|
158
|
+
|
159
|
+
table + "\n" + summary
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
def evict
|
165
|
+
overflow = size - max_size
|
166
|
+
while overflow > 0
|
167
|
+
evicted_entry = @dynamic_entries.pop
|
168
|
+
overflow -= evicted_entry.size
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,312 @@
|
|
1
|
+
RSpec.describe Hpack::Decoder do
|
2
|
+
RSpec::Matchers.define :have_dynamic_entry_at_position do |position, header, value|
|
3
|
+
match do |actual|
|
4
|
+
entry = actual[Hpack::LookupTable::STATIC_TABLE_SIZE + position]
|
5
|
+
expect(entry.name).to eq header
|
6
|
+
expect(entry.value).to eq value
|
7
|
+
end
|
8
|
+
|
9
|
+
failure_message do |actual|
|
10
|
+
"""Expected that
|
11
|
+
#{actual}
|
12
|
+
would include '#{header}: #{value}' at position #{position}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def input_fixture data
|
17
|
+
io = StringIO.new
|
18
|
+
io.write [data.gsub(/\s/, "")].pack "H*"
|
19
|
+
io.rewind
|
20
|
+
io
|
21
|
+
end
|
22
|
+
|
23
|
+
it "properly decodes an example of a literal header field with indexing" do
|
24
|
+
io = input_fixture "400a 6375 7374 6f6d 2d6b 6579 0d63 7573 746f 6d2d 6865 6164 6572"
|
25
|
+
|
26
|
+
expect { |b| subject.decode io, &b }
|
27
|
+
.to yield_with_args("custom-key", "custom-header", anything)
|
28
|
+
expect(subject.lookup_table.size).to eq 55
|
29
|
+
expect(subject.lookup_table)
|
30
|
+
.to have_dynamic_entry_at_position(1, "custom-key", "custom-header")
|
31
|
+
end
|
32
|
+
|
33
|
+
it "properly decodes an example of a literal header field without indexing" do
|
34
|
+
io = input_fixture "040c 2f73 616d 706c 652f 7061 7468"
|
35
|
+
|
36
|
+
expect { |b| subject.decode io, &b }
|
37
|
+
.to yield_with_args(":path", "/sample/path", anything)
|
38
|
+
expect(subject.lookup_table.size).to eq 0
|
39
|
+
end
|
40
|
+
|
41
|
+
it "properly decodes an example of a never indexed literal header field" do
|
42
|
+
io = input_fixture "1008 7061 7373 776f 7264 0673 6563 7265 74"
|
43
|
+
|
44
|
+
expect { |b| subject.decode io, &b }
|
45
|
+
.to yield_with_args("password", "secret", anything)
|
46
|
+
expect(subject.lookup_table.size).to eq 0
|
47
|
+
end
|
48
|
+
|
49
|
+
it "properly decodes an example of an indexed header field" do
|
50
|
+
io = input_fixture "82"
|
51
|
+
|
52
|
+
expect { |b| subject.decode io, &b }
|
53
|
+
.to yield_with_args(":method", "GET", anything)
|
54
|
+
expect(subject.lookup_table.size).to eq 0
|
55
|
+
end
|
56
|
+
|
57
|
+
it "properly decodes a sequence of requests without huffman coding" do
|
58
|
+
io = input_fixture """ 8286 8441 0f77 7777 2e65 7861 6d70 6c65
|
59
|
+
2e63 6f6d """
|
60
|
+
|
61
|
+
expect { |b| subject.decode io, &b }
|
62
|
+
.to yield_successive_args(
|
63
|
+
[":method", "GET", anything],
|
64
|
+
[":scheme", "http", anything],
|
65
|
+
[":path", "/", anything],
|
66
|
+
[":authority", "www.example.com", anything]
|
67
|
+
)
|
68
|
+
expect(subject.lookup_table.size).to eq 57
|
69
|
+
expect(subject.lookup_table)
|
70
|
+
.to have_dynamic_entry_at_position(1, ":authority", "www.example.com")
|
71
|
+
|
72
|
+
io = input_fixture """ 8286 84be 5808 6e6f 2d63 6163 6865 """
|
73
|
+
|
74
|
+
expect { |b| subject.decode io, &b }
|
75
|
+
.to yield_successive_args(
|
76
|
+
[":method", "GET", anything],
|
77
|
+
[":scheme", "http", anything],
|
78
|
+
[":path", "/", anything],
|
79
|
+
[":authority", "www.example.com", anything],
|
80
|
+
["cache-control", "no-cache", anything]
|
81
|
+
)
|
82
|
+
expect(subject.lookup_table.size).to eq 110
|
83
|
+
|
84
|
+
expect(subject.lookup_table)
|
85
|
+
.to have_dynamic_entry_at_position(1, "cache-control", "no-cache")
|
86
|
+
expect(subject.lookup_table)
|
87
|
+
.to have_dynamic_entry_at_position(2, ":authority", "www.example.com")
|
88
|
+
|
89
|
+
io = input_fixture """ 8287 85bf 400a 6375 7374 6f6d 2d6b 6579
|
90
|
+
0c63 7573 746f 6d2d 7661 6c75 65 """
|
91
|
+
|
92
|
+
expect { |b| subject.decode io, &b }
|
93
|
+
.to yield_successive_args(
|
94
|
+
[":method", "GET", anything],
|
95
|
+
[":scheme", "https", anything],
|
96
|
+
[":path", "/index.html", anything],
|
97
|
+
[":authority", "www.example.com", anything],
|
98
|
+
["custom-key", "custom-value", anything]
|
99
|
+
)
|
100
|
+
expect(subject.lookup_table.size).to eq 164
|
101
|
+
|
102
|
+
expect(subject.lookup_table)
|
103
|
+
.to have_dynamic_entry_at_position(1, "custom-key", "custom-value")
|
104
|
+
expect(subject.lookup_table)
|
105
|
+
.to have_dynamic_entry_at_position(2, "cache-control", "no-cache")
|
106
|
+
expect(subject.lookup_table)
|
107
|
+
.to have_dynamic_entry_at_position(3, ":authority", "www.example.com")
|
108
|
+
end
|
109
|
+
|
110
|
+
it "properly decodes a sequence of requests with huffman coding" do
|
111
|
+
io = input_fixture """ 8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4
|
112
|
+
ff """
|
113
|
+
|
114
|
+
expect { |b| subject.decode io, &b }
|
115
|
+
.to yield_successive_args(
|
116
|
+
[":method", "GET", anything],
|
117
|
+
[":scheme", "http", anything],
|
118
|
+
[":path", "/", anything],
|
119
|
+
[":authority", "www.example.com", anything]
|
120
|
+
)
|
121
|
+
expect(subject.lookup_table.size).to eq 57
|
122
|
+
|
123
|
+
expect(subject.lookup_table)
|
124
|
+
.to have_dynamic_entry_at_position(1, ":authority", "www.example.com")
|
125
|
+
|
126
|
+
io = input_fixture """ 8286 84be 5886 a8eb 1064 9cbf """
|
127
|
+
|
128
|
+
expect { |b| subject.decode io, &b }
|
129
|
+
.to yield_successive_args(
|
130
|
+
[":method", "GET", anything],
|
131
|
+
[":scheme", "http", anything],
|
132
|
+
[":path", "/", anything],
|
133
|
+
[":authority", "www.example.com", anything],
|
134
|
+
["cache-control", "no-cache", anything]
|
135
|
+
)
|
136
|
+
expect(subject.lookup_table.size).to eq 110
|
137
|
+
|
138
|
+
expect(subject.lookup_table)
|
139
|
+
.to have_dynamic_entry_at_position(1, "cache-control", "no-cache")
|
140
|
+
expect(subject.lookup_table)
|
141
|
+
.to have_dynamic_entry_at_position(2, ":authority", "www.example.com")
|
142
|
+
|
143
|
+
entry = subject.lookup_table[Hpack::LookupTable::STATIC_TABLE_SIZE + 1]
|
144
|
+
expect(entry.name).to eq "cache-control"
|
145
|
+
expect(entry.value).to eq "no-cache"
|
146
|
+
|
147
|
+
io = input_fixture """ 8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925
|
148
|
+
a849 e95b b8e8 b4bf """
|
149
|
+
|
150
|
+
expect { |b| subject.decode io, &b }
|
151
|
+
.to yield_successive_args(
|
152
|
+
[":method", "GET", anything],
|
153
|
+
[":scheme", "https", anything],
|
154
|
+
[":path", "/index.html", anything],
|
155
|
+
[":authority", "www.example.com", anything],
|
156
|
+
["custom-key", "custom-value", anything]
|
157
|
+
)
|
158
|
+
expect(subject.lookup_table.size).to eq 164
|
159
|
+
|
160
|
+
expect(subject.lookup_table)
|
161
|
+
.to have_dynamic_entry_at_position(1, "custom-key", "custom-value")
|
162
|
+
expect(subject.lookup_table)
|
163
|
+
.to have_dynamic_entry_at_position(2, "cache-control", "no-cache")
|
164
|
+
expect(subject.lookup_table)
|
165
|
+
.to have_dynamic_entry_at_position(3, ":authority", "www.example.com")
|
166
|
+
end
|
167
|
+
|
168
|
+
it "properly decodes a sequence of responses without huffman coding" do
|
169
|
+
subject.lookup_table.max_size = 256
|
170
|
+
|
171
|
+
io = input_fixture """ 4803 3330 3258 0770 7269 7661 7465 611d
|
172
|
+
4d6f 6e2c 2032 3120 4f63 7420 3230 3133
|
173
|
+
2032 303a 3133 3a32 3120 474d 546e 1768
|
174
|
+
7474 7073 3a2f 2f77 7777 2e65 7861 6d70
|
175
|
+
6c65 2e63 6f6d """
|
176
|
+
|
177
|
+
expect { |b| subject.decode io, &b }
|
178
|
+
.to yield_successive_args(
|
179
|
+
[":status", "302", anything],
|
180
|
+
["cache-control", "private", anything],
|
181
|
+
["date", "Mon, 21 Oct 2013 20:13:21 GMT", anything],
|
182
|
+
["location", "https://www.example.com", anything]
|
183
|
+
)
|
184
|
+
expect(subject.lookup_table.size).to eq 222
|
185
|
+
|
186
|
+
expect(subject.lookup_table)
|
187
|
+
.to have_dynamic_entry_at_position(1, "location", "https://www.example.com")
|
188
|
+
expect(subject.lookup_table)
|
189
|
+
.to have_dynamic_entry_at_position(2, "date", "Mon, 21 Oct 2013 20:13:21 GMT")
|
190
|
+
expect(subject.lookup_table)
|
191
|
+
.to have_dynamic_entry_at_position(3, "cache-control", "private")
|
192
|
+
expect(subject.lookup_table)
|
193
|
+
.to have_dynamic_entry_at_position(4, ":status", "302")
|
194
|
+
|
195
|
+
io = input_fixture """ 4803 3330 37c1 c0bf """
|
196
|
+
|
197
|
+
expect { |b| subject.decode io, &b }
|
198
|
+
.to yield_successive_args(
|
199
|
+
[":status", "307", anything],
|
200
|
+
["cache-control", "private", anything],
|
201
|
+
["date", "Mon, 21 Oct 2013 20:13:21 GMT", anything],
|
202
|
+
["location", "https://www.example.com", anything]
|
203
|
+
)
|
204
|
+
expect(subject.lookup_table.size).to eq 222
|
205
|
+
|
206
|
+
expect(subject.lookup_table)
|
207
|
+
.to have_dynamic_entry_at_position(1, ":status", "307")
|
208
|
+
expect(subject.lookup_table)
|
209
|
+
.to have_dynamic_entry_at_position(2, "location", "https://www.example.com")
|
210
|
+
expect(subject.lookup_table)
|
211
|
+
.to have_dynamic_entry_at_position(3, "date", "Mon, 21 Oct 2013 20:13:21 GMT")
|
212
|
+
expect(subject.lookup_table)
|
213
|
+
.to have_dynamic_entry_at_position(4, "cache-control", "private")
|
214
|
+
|
215
|
+
io = input_fixture """ 88c1 611d 4d6f 6e2c 2032 3120 4f63 7420
|
216
|
+
3230 3133 2032 303a 3133 3a32 3220 474d
|
217
|
+
54c0 5a04 677a 6970 7738 666f 6f3d 4153
|
218
|
+
444a 4b48 514b 425a 584f 5157 454f 5049
|
219
|
+
5541 5851 5745 4f49 553b 206d 6178 2d61
|
220
|
+
6765 3d33 3630 303b 2076 6572 7369 6f6e
|
221
|
+
3d31 """
|
222
|
+
|
223
|
+
expect { |b| subject.decode io, &b }
|
224
|
+
.to yield_successive_args(
|
225
|
+
[":status", "200", anything],
|
226
|
+
["cache-control", "private", anything],
|
227
|
+
["date", "Mon, 21 Oct 2013 20:13:22 GMT", anything],
|
228
|
+
["location", "https://www.example.com", anything],
|
229
|
+
["content-encoding", "gzip", anything],
|
230
|
+
["set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", anything]
|
231
|
+
)
|
232
|
+
expect(subject.lookup_table.size).to eq 215
|
233
|
+
|
234
|
+
expect(subject.lookup_table)
|
235
|
+
.to have_dynamic_entry_at_position(1, "set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1")
|
236
|
+
expect(subject.lookup_table)
|
237
|
+
.to have_dynamic_entry_at_position(2, "content-encoding", "gzip")
|
238
|
+
expect(subject.lookup_table)
|
239
|
+
.to have_dynamic_entry_at_position(3, "date", "Mon, 21 Oct 2013 20:13:22 GMT")
|
240
|
+
end
|
241
|
+
|
242
|
+
it "properly decodes a sequence of responses with huffman coding" do
|
243
|
+
subject.lookup_table.max_size = 256
|
244
|
+
|
245
|
+
io = input_fixture """ 4882 6402 5885 aec3 771a 4b61 96d0 7abe
|
246
|
+
9410 54d4 44a8 2005 9504 0b81 66e0 82a6
|
247
|
+
2d1b ff6e 919d 29ad 1718 63c7 8f0b 97c8
|
248
|
+
e9ae 82ae 43d3 """
|
249
|
+
|
250
|
+
expect { |b| subject.decode io, &b }
|
251
|
+
.to yield_successive_args(
|
252
|
+
[":status", "302", anything],
|
253
|
+
["cache-control", "private", anything],
|
254
|
+
["date", "Mon, 21 Oct 2013 20:13:21 GMT", anything],
|
255
|
+
["location", "https://www.example.com", anything]
|
256
|
+
)
|
257
|
+
expect(subject.lookup_table.size).to eq 222
|
258
|
+
|
259
|
+
expect(subject.lookup_table)
|
260
|
+
.to have_dynamic_entry_at_position(1, "location", "https://www.example.com")
|
261
|
+
expect(subject.lookup_table)
|
262
|
+
.to have_dynamic_entry_at_position(2, "date", "Mon, 21 Oct 2013 20:13:21 GMT")
|
263
|
+
expect(subject.lookup_table)
|
264
|
+
.to have_dynamic_entry_at_position(3, "cache-control", "private")
|
265
|
+
expect(subject.lookup_table)
|
266
|
+
.to have_dynamic_entry_at_position(4, ":status", "302")
|
267
|
+
|
268
|
+
io = input_fixture """ 4883 640e ffc1 c0bf """
|
269
|
+
|
270
|
+
expect { |b| subject.decode io, &b }
|
271
|
+
.to yield_successive_args(
|
272
|
+
[":status", "307", anything],
|
273
|
+
["cache-control", "private", anything],
|
274
|
+
["date", "Mon, 21 Oct 2013 20:13:21 GMT", anything],
|
275
|
+
["location", "https://www.example.com", anything]
|
276
|
+
)
|
277
|
+
expect(subject.lookup_table.size).to eq 222
|
278
|
+
|
279
|
+
expect(subject.lookup_table)
|
280
|
+
.to have_dynamic_entry_at_position(1, ":status", "307")
|
281
|
+
expect(subject.lookup_table)
|
282
|
+
.to have_dynamic_entry_at_position(2, "location", "https://www.example.com")
|
283
|
+
expect(subject.lookup_table)
|
284
|
+
.to have_dynamic_entry_at_position(3, "date", "Mon, 21 Oct 2013 20:13:21 GMT")
|
285
|
+
expect(subject.lookup_table)
|
286
|
+
.to have_dynamic_entry_at_position(4, "cache-control", "private")
|
287
|
+
|
288
|
+
io = input_fixture """ 88c1 6196 d07a be94 1054 d444 a820 0595
|
289
|
+
040b 8166 e084 a62d 1bff c05a 839b d9ab
|
290
|
+
77ad 94e7 821d d7f2 e6c7 b335 dfdf cd5b
|
291
|
+
3960 d5af 2708 7f36 72c1 ab27 0fb5 291f
|
292
|
+
9587 3160 65c0 03ed 4ee5 b106 3d50 07 """
|
293
|
+
|
294
|
+
expect { |b| subject.decode io, &b }
|
295
|
+
.to yield_successive_args(
|
296
|
+
[":status", "200", anything],
|
297
|
+
["cache-control", "private", anything],
|
298
|
+
["date", "Mon, 21 Oct 2013 20:13:22 GMT", anything],
|
299
|
+
["location", "https://www.example.com", anything],
|
300
|
+
["content-encoding", "gzip", anything],
|
301
|
+
["set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", anything]
|
302
|
+
)
|
303
|
+
expect(subject.lookup_table.size).to eq 215
|
304
|
+
|
305
|
+
expect(subject.lookup_table)
|
306
|
+
.to have_dynamic_entry_at_position(1, "set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1")
|
307
|
+
expect(subject.lookup_table)
|
308
|
+
.to have_dynamic_entry_at_position(2, "content-encoding", "gzip")
|
309
|
+
expect(subject.lookup_table)
|
310
|
+
.to have_dynamic_entry_at_position(3, "date", "Mon, 21 Oct 2013 20:13:22 GMT")
|
311
|
+
end
|
312
|
+
end
|