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
data/http-2.gemspec
CHANGED
data/lib/http/2.rb
CHANGED
data/lib/http/2/buffer.rb
CHANGED
@@ -4,17 +4,34 @@ module HTTP2
|
|
4
4
|
#
|
5
5
|
class Buffer < String
|
6
6
|
|
7
|
+
UINT32 = "N"
|
8
|
+
BINARY = "binary"
|
9
|
+
private_constant :UINT32, :BINARY
|
10
|
+
|
7
11
|
# Forces binary encoding on the string
|
8
|
-
def initialize(
|
9
|
-
force_encoding(
|
10
|
-
super(*args)
|
12
|
+
def initialize(data = '')
|
13
|
+
super(data.force_encoding(BINARY))
|
11
14
|
end
|
12
15
|
|
13
16
|
# Emulate StringIO#read: slice first n bytes from the buffer.
|
14
17
|
#
|
15
18
|
# @param n [Integer] number of bytes to slice from the buffer
|
16
19
|
def read(n)
|
17
|
-
slice!(0,n)
|
20
|
+
Buffer.new(slice!(0,n))
|
21
|
+
end
|
22
|
+
|
23
|
+
# Alias getbyte to readbyte
|
24
|
+
alias :readbyte :getbyte
|
25
|
+
|
26
|
+
# Emulate StringIO#getbyte: slice first byte from buffer.
|
27
|
+
def getbyte
|
28
|
+
read(1).ord
|
29
|
+
end
|
30
|
+
|
31
|
+
# Slice unsigned 32-bit integer from buffer.
|
32
|
+
# @return [Integer]
|
33
|
+
def read_uint32
|
34
|
+
read(4).unpack(UINT32).first
|
18
35
|
end
|
19
36
|
end
|
20
37
|
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module HTTP2
|
2
|
+
|
3
|
+
# HTTP 2.0 client connection class that implements appropriate header
|
4
|
+
# compression / decompression algorithms and stream management logic.
|
5
|
+
#
|
6
|
+
# Your code is responsible for driving the client object, which in turn
|
7
|
+
# performs all of the necessary HTTP 2.0 encoding / decoding, state
|
8
|
+
# management, and the rest. A simple example:
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# socket = YourTransport.new
|
12
|
+
#
|
13
|
+
# conn = HTTP2::Client.new
|
14
|
+
# conn.on(:frame) {|bytes| socket << bytes }
|
15
|
+
#
|
16
|
+
# while bytes = socket.read
|
17
|
+
# conn << bytes
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
class Client < Connection
|
21
|
+
|
22
|
+
# Initialize new HTTP 2.0 client object.
|
23
|
+
def initialize(*args)
|
24
|
+
@stream_id = 1
|
25
|
+
@state = :connection_header
|
26
|
+
@compressor = Header::Compressor.new(:request)
|
27
|
+
@decompressor = Header::Decompressor.new(:response)
|
28
|
+
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
# Send an outgoing frame. Connection and stream flow control is managed
|
33
|
+
# by Connection class.
|
34
|
+
#
|
35
|
+
# @see Connection
|
36
|
+
# @note Client will emit the connection header as the first 24 bytes
|
37
|
+
# @param frame [Hash]
|
38
|
+
def send(frame)
|
39
|
+
if @state == :connection_header
|
40
|
+
emit(:frame, CONNECTION_HEADER)
|
41
|
+
@state = :connected
|
42
|
+
|
43
|
+
settings(stream_limit: @stream_limit, window_limit: @window_limit)
|
44
|
+
end
|
45
|
+
|
46
|
+
super(frame)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
data/lib/http/2/compressor.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require "stringio"
|
2
|
-
|
3
1
|
module HTTP2
|
4
2
|
|
5
3
|
# Implementation of header compression for HTTP 2.0 (HPACK) format adapted
|
@@ -12,97 +10,84 @@ module HTTP2
|
|
12
10
|
# encoding context: an encoding context contains a header table and a
|
13
11
|
# reference set - there is one encoding context for each direction.
|
14
12
|
#
|
15
|
-
class
|
13
|
+
class EncodingContext
|
16
14
|
include Error
|
17
15
|
|
18
16
|
# TODO: replace StringIO with Buffer...
|
19
17
|
|
20
18
|
# Default request working set as defined by the spec.
|
21
19
|
REQ_DEFAULTS = [
|
22
|
-
[':scheme' ,'http' ],
|
23
|
-
[':scheme' ,'https'],
|
24
|
-
[':host' ,'' ],
|
25
|
-
[':path' ,'/' ],
|
26
|
-
[':method' ,'get' ],
|
27
|
-
['accept' ,'' ],
|
28
|
-
['accept-charset' ,'' ],
|
29
|
-
['accept-encoding' ,'' ],
|
30
|
-
['accept-language' ,'' ],
|
31
|
-
['cookie' ,'' ],
|
32
|
-
['if-modified-since' ,'' ],
|
33
|
-
['
|
34
|
-
['
|
35
|
-
['
|
36
|
-
['
|
37
|
-
['
|
38
|
-
['
|
39
|
-
['
|
40
|
-
['
|
41
|
-
['
|
42
|
-
['
|
43
|
-
['
|
44
|
-
['
|
45
|
-
['
|
46
|
-
['
|
47
|
-
['
|
48
|
-
['
|
49
|
-
['
|
50
|
-
['
|
51
|
-
['
|
52
|
-
|
53
|
-
['pragma' ,'' ],
|
54
|
-
['proxy-authorization','' ],
|
55
|
-
['range' ,'' ],
|
56
|
-
['te' ,'' ],
|
57
|
-
['upgrade' ,'' ],
|
58
|
-
['via' ,'' ],
|
59
|
-
['warning' ,'' ]
|
60
|
-
];
|
20
|
+
[':scheme' , 'http' ],
|
21
|
+
[':scheme' , 'https'],
|
22
|
+
[':host' , '' ],
|
23
|
+
[':path' , '/' ],
|
24
|
+
[':method' , 'get' ],
|
25
|
+
['accept' , '' ],
|
26
|
+
['accept-charset' , '' ],
|
27
|
+
['accept-encoding' , '' ],
|
28
|
+
['accept-language' , '' ],
|
29
|
+
['cookie' , '' ],
|
30
|
+
['if-modified-since' , '' ],
|
31
|
+
['user-agent' , '' ],
|
32
|
+
['referer' , '' ],
|
33
|
+
['authorization' , '' ],
|
34
|
+
['allow' , '' ],
|
35
|
+
['cache-control' , '' ],
|
36
|
+
['connection' , '' ],
|
37
|
+
['content-length' , '' ],
|
38
|
+
['content-type' , '' ],
|
39
|
+
['date' , '' ],
|
40
|
+
['expect' , '' ],
|
41
|
+
['from' , '' ],
|
42
|
+
['if-match' , '' ],
|
43
|
+
['if-none-match' , '' ],
|
44
|
+
['if-range' , '' ],
|
45
|
+
['if-unmodified-since', '' ],
|
46
|
+
['max-forwards' , '' ],
|
47
|
+
['proxy-authorization', '' ],
|
48
|
+
['range' , '' ],
|
49
|
+
['via' , '' ]
|
50
|
+
]
|
61
51
|
|
62
52
|
# Default response working set as defined by the spec.
|
63
53
|
RESP_DEFAULTS = [
|
64
|
-
[':status'
|
65
|
-
['age'
|
66
|
-
['cache-control'
|
67
|
-
['content-length'
|
68
|
-
['content-type'
|
69
|
-
['date'
|
70
|
-
['etag'
|
71
|
-
['expires'
|
72
|
-
['last-modified'
|
73
|
-
['server'
|
74
|
-
['set-cookie'
|
75
|
-
['vary'
|
76
|
-
['via'
|
77
|
-
['access-control-allow-origin','' ],
|
78
|
-
['accept-ranges'
|
79
|
-
['allow'
|
80
|
-
['connection'
|
81
|
-
['content-disposition'
|
82
|
-
['content-encoding'
|
83
|
-
['content-language'
|
84
|
-
['content-location'
|
85
|
-
['content-
|
86
|
-
['
|
87
|
-
['
|
88
|
-
['
|
89
|
-
['
|
90
|
-
['
|
91
|
-
['
|
92
|
-
['
|
93
|
-
['
|
94
|
-
|
95
|
-
['trailer' ,'' ],
|
96
|
-
['transfer-encoding' ,'' ],
|
97
|
-
['warning' ,'' ],
|
98
|
-
['www-authenticate' ,'' ]
|
99
|
-
];
|
54
|
+
[':status' , '200'],
|
55
|
+
['age' , '' ],
|
56
|
+
['cache-control' , '' ],
|
57
|
+
['content-length' , '' ],
|
58
|
+
['content-type' , '' ],
|
59
|
+
['date' , '' ],
|
60
|
+
['etag' , '' ],
|
61
|
+
['expires' , '' ],
|
62
|
+
['last-modified' , '' ],
|
63
|
+
['server' , '' ],
|
64
|
+
['set-cookie' , '' ],
|
65
|
+
['vary' , '' ],
|
66
|
+
['via' , '' ],
|
67
|
+
['access-control-allow-origin' , '' ],
|
68
|
+
['accept-ranges' , '' ],
|
69
|
+
['allow' , '' ],
|
70
|
+
['connection' , '' ],
|
71
|
+
['content-disposition' , '' ],
|
72
|
+
['content-encoding' , '' ],
|
73
|
+
['content-language' , '' ],
|
74
|
+
['content-location' , '' ],
|
75
|
+
['content-range' , '' ],
|
76
|
+
['link' , '' ],
|
77
|
+
['location' , '' ],
|
78
|
+
['proxy-authenticate' , '' ],
|
79
|
+
['refresh' , '' ],
|
80
|
+
['retry-after' , '' ],
|
81
|
+
['strict-transport-security' , '' ],
|
82
|
+
['transfer-encoding' , '' ],
|
83
|
+
['www-authenticate' , '' ]
|
84
|
+
]
|
100
85
|
|
101
86
|
# Current table of header key-value pairs.
|
102
87
|
attr_reader :table
|
103
88
|
|
104
|
-
# Current
|
105
|
-
attr_reader :
|
89
|
+
# Current reference set of header key-value pairs.
|
90
|
+
attr_reader :refset
|
106
91
|
|
107
92
|
# Initializes compression context with appropriate client/server
|
108
93
|
# defaults and maximum size of the header table.
|
@@ -113,37 +98,50 @@ module HTTP2
|
|
113
98
|
@type = type
|
114
99
|
@table = (type == :request) ? REQ_DEFAULTS.dup : RESP_DEFAULTS.dup
|
115
100
|
@limit = limit
|
116
|
-
@
|
101
|
+
@refset = []
|
117
102
|
end
|
118
103
|
|
119
104
|
# Performs differential coding based on provided command type.
|
120
|
-
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-
|
105
|
+
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-03#section-3.2
|
121
106
|
#
|
122
107
|
# @param cmd [Hash]
|
108
|
+
# @return [Hash] emitted header
|
123
109
|
def process(cmd)
|
110
|
+
emit = nil
|
111
|
+
|
124
112
|
# indexed representation
|
125
113
|
if cmd[:type] == :indexed
|
126
|
-
#
|
127
|
-
#
|
128
|
-
#
|
129
|
-
#
|
130
|
-
#
|
131
|
-
#
|
132
|
-
#
|
133
|
-
|
114
|
+
# An indexed representation corresponding to an entry not present
|
115
|
+
# in the reference set entails the following actions:
|
116
|
+
# - The header corresponding to the entry is emitted.
|
117
|
+
# - The entry is added to the reference set.
|
118
|
+
#
|
119
|
+
# An indexed representation corresponding to an entry present in
|
120
|
+
# the reference set entails the following actions:
|
121
|
+
# - The entry is removed from the reference set.
|
122
|
+
#
|
123
|
+
idx = cmd[:name]
|
124
|
+
cur = @refset.find_index {|(i,v)| i == idx}
|
134
125
|
|
135
126
|
if cur
|
136
|
-
@
|
127
|
+
@refset.delete_at(cur)
|
137
128
|
else
|
138
|
-
|
129
|
+
emit = @table[idx]
|
130
|
+
@refset.push [idx, @table[idx]]
|
139
131
|
end
|
140
132
|
|
141
133
|
else
|
142
|
-
#
|
143
|
-
#
|
144
|
-
#
|
145
|
-
#
|
146
|
-
#
|
134
|
+
# A literal representation that is not added to the header table
|
135
|
+
# entails the following action:
|
136
|
+
# - The header is emitted.
|
137
|
+
#
|
138
|
+
# A literal representation that is added to the header table entails
|
139
|
+
# the following actions:
|
140
|
+
# - The header is emitted.
|
141
|
+
# - The header is added to the header table, at the location
|
142
|
+
# defined by the representation.
|
143
|
+
# - The new entry is added to the reference set.
|
144
|
+
#
|
147
145
|
if cmd[:name].is_a? Integer
|
148
146
|
k,v = @table[cmd[:name]]
|
149
147
|
|
@@ -152,42 +150,29 @@ module HTTP2
|
|
152
150
|
cmd[:name] = k
|
153
151
|
end
|
154
152
|
|
155
|
-
|
153
|
+
emit = [cmd[:name], cmd[:value]]
|
156
154
|
|
157
155
|
if cmd[:type] != :noindex
|
158
|
-
size_check
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
156
|
+
if size_check(cmd)
|
157
|
+
|
158
|
+
case cmd[:type]
|
159
|
+
when :incremental
|
160
|
+
cmd[:index] = @table.size
|
161
|
+
when :substitution
|
162
|
+
if @table[cmd[:index]].nil?
|
163
|
+
raise HeaderException.new("invalid index")
|
164
|
+
end
|
165
|
+
when :prepend
|
166
|
+
@table = [emit] + @table
|
166
167
|
end
|
167
|
-
when :prepend
|
168
|
-
@table = [newval] + @table
|
169
|
-
end
|
170
168
|
|
171
|
-
|
169
|
+
@table[cmd[:index]] = emit
|
170
|
+
@refset.push [cmd[:index], emit]
|
171
|
+
end
|
172
172
|
end
|
173
|
-
|
174
|
-
@workset.push [cmd[:index], newval]
|
175
173
|
end
|
176
|
-
end
|
177
174
|
|
178
|
-
|
179
|
-
# reference set of headers is interpreted into the working set of
|
180
|
-
# headers: for each header in the reference set, an entry is added to
|
181
|
-
# the working set, containing the header name, its value, and its
|
182
|
-
# current index in the header table.
|
183
|
-
#
|
184
|
-
# @return [Array] current working set
|
185
|
-
def update_sets
|
186
|
-
# new refset is the the workset sans headers not in header table
|
187
|
-
refset = @workset.reject {|(i,h)| !@table.include? h}
|
188
|
-
|
189
|
-
# new workset is the refset with index of each header in header table
|
190
|
-
@workset = refset.collect {|(i,h)| [@table.find_index(h), h]}
|
175
|
+
emit
|
191
176
|
end
|
192
177
|
|
193
178
|
# Emits best available command to encode provided header.
|
@@ -230,40 +215,55 @@ module HTTP2
|
|
230
215
|
|
231
216
|
private
|
232
217
|
|
233
|
-
# Before
|
234
|
-
#
|
235
|
-
#
|
236
|
-
#
|
237
|
-
#
|
238
|
-
#
|
239
|
-
#
|
240
|
-
#
|
218
|
+
# Before doing such a modification, it has to be ensured that the header
|
219
|
+
# table size will stay lower than or equal to the
|
220
|
+
# SETTINGS_HEADER_TABLE_SIZE limit. To achieve this, repeatedly, the
|
221
|
+
# first entry of the header table is removed, until enough space is
|
222
|
+
# available for the modification.
|
223
|
+
#
|
224
|
+
# A consequence of removing one or more entries at the beginning of the
|
225
|
+
# header table is that the remaining entries are renumbered. The first
|
226
|
+
# entry of the header table is always associated to the index 0.
|
241
227
|
#
|
242
228
|
# @param cmd [Hash]
|
229
|
+
# @return [Boolean]
|
243
230
|
def size_check(cmd)
|
244
231
|
cursize = @table.join.bytesize + @table.size * 32
|
245
232
|
cmdsize = cmd[:name].bytesize + cmd[:value].bytesize + 32
|
246
233
|
|
234
|
+
# The addition of a new entry with a size greater than the
|
235
|
+
# SETTINGS_HEADER_TABLE_SIZE limit causes all the entries from the
|
236
|
+
# header table to be dropped and the new entry not to be added to the
|
237
|
+
# header table. The replacement of an existing entry with a new entry
|
238
|
+
# with a size greater than the SETTINGS_HEADER_TABLE_SIZE has the same
|
239
|
+
# consequences.
|
240
|
+
if cmdsize > @limit
|
241
|
+
@table.clear
|
242
|
+
return false
|
243
|
+
end
|
244
|
+
|
247
245
|
cur = 0
|
248
246
|
while (cursize + cmdsize) > @limit do
|
249
247
|
e = @table.shift
|
250
248
|
|
251
|
-
# When
|
252
|
-
#
|
253
|
-
#
|
254
|
-
#
|
255
|
-
#
|
256
|
-
#
|
249
|
+
# When the modification of the header table is the replacement of an
|
250
|
+
# existing entry, the replaced entry is the one indicated in the
|
251
|
+
# literal representation before any entry is removed from the header
|
252
|
+
# table. If the entry to be replaced is removed from the header table
|
253
|
+
# when performing the size adjustment, the replacement entry is
|
254
|
+
# inserted at the beginning of the header table.
|
257
255
|
if cmd[:type] == :substitution && cur == cmd[:index]
|
258
256
|
cmd[:type] = :prepend
|
259
257
|
end
|
260
258
|
|
261
259
|
cursize -= (e.join.bytesize + 32)
|
262
260
|
end
|
261
|
+
|
262
|
+
return true
|
263
263
|
end
|
264
264
|
|
265
265
|
def active?(idx)
|
266
|
-
!@
|
266
|
+
!@refset.find {|i,_| i == idx }.nil?
|
267
267
|
end
|
268
268
|
|
269
269
|
def default?(idx)
|
@@ -289,20 +289,20 @@ module HTTP2
|
|
289
289
|
# server_role = Compressor.new(:response)
|
290
290
|
class Compressor
|
291
291
|
def initialize(type)
|
292
|
-
@cc =
|
292
|
+
@cc = EncodingContext.new(type)
|
293
293
|
end
|
294
294
|
|
295
295
|
# Encodes provided value via integer representation.
|
296
|
-
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-
|
296
|
+
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-03#section-4.1.1
|
297
297
|
#
|
298
|
-
#
|
299
|
-
#
|
300
|
-
#
|
301
|
-
#
|
302
|
-
#
|
303
|
-
#
|
304
|
-
#
|
305
|
-
# I
|
298
|
+
# If I < 2^N - 1, encode I on N bits
|
299
|
+
# Else
|
300
|
+
# encode 2^N - 1 on N bits
|
301
|
+
# I = I - (2^N - 1)
|
302
|
+
# While I >= 128
|
303
|
+
# Encode (I % 128 + 128) on 8 bits
|
304
|
+
# I = I / 128
|
305
|
+
# encode (I) on 8 bits
|
306
306
|
#
|
307
307
|
# @param i [Integer] value to encode
|
308
308
|
# @param n [Integer] number of available bits
|
@@ -315,21 +315,17 @@ module HTTP2
|
|
315
315
|
bytes.push limit if !n.zero?
|
316
316
|
|
317
317
|
i -= limit
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
q, r = i.divmod(128)
|
322
|
-
r += 128 if (q > 0)
|
323
|
-
i = q
|
324
|
-
|
325
|
-
bytes.push(r)
|
318
|
+
while (i >= 128) do
|
319
|
+
bytes.push((i % 128) + 128)
|
320
|
+
i = i / 128
|
326
321
|
end
|
327
322
|
|
323
|
+
bytes.push i
|
328
324
|
bytes.pack('C*')
|
329
325
|
end
|
330
326
|
|
331
327
|
# Encodes provided value via string literal representation.
|
332
|
-
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-
|
328
|
+
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-03#section-4.1.3
|
333
329
|
#
|
334
330
|
# * The string length, defined as the number of bytes needed to store
|
335
331
|
# its UTF-8 representation, is represented as an integer with a zero
|
@@ -340,15 +336,15 @@ module HTTP2
|
|
340
336
|
# @param str [String]
|
341
337
|
# @return [String] binary string
|
342
338
|
def string(str)
|
343
|
-
integer(str.bytesize, 0)
|
339
|
+
integer(str.bytesize, 0) << str.dup.force_encoding('binary')
|
344
340
|
end
|
345
341
|
|
346
342
|
# Encodes header command with appropriate header representation.
|
347
|
-
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-
|
343
|
+
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-03#section-4
|
348
344
|
#
|
349
345
|
# @param h [Hash] header command
|
350
346
|
# @param buffer [String]
|
351
|
-
def header(h, buffer =
|
347
|
+
def header(h, buffer = Buffer.new)
|
352
348
|
rep = HEADREP[h[:type]]
|
353
349
|
|
354
350
|
if h[:type] == :indexed
|
@@ -383,29 +379,35 @@ module HTTP2
|
|
383
379
|
# Encodes provided list of HTTP headers.
|
384
380
|
#
|
385
381
|
# @param headers [Hash]
|
386
|
-
# @return [
|
382
|
+
# @return [Buffer]
|
387
383
|
def encode(headers)
|
384
|
+
buffer = Buffer.new
|
388
385
|
commands = []
|
389
|
-
@cc.update_sets
|
390
386
|
|
391
|
-
#
|
392
|
-
|
387
|
+
# Literal header names MUST be translated to lowercase before
|
388
|
+
# encoding and transmission.
|
389
|
+
headers.map! {|(hk,hv)| [hk.downcase, hv] }
|
390
|
+
|
391
|
+
# Generate remove commands for missing headers
|
392
|
+
@cc.refset.each do |idx, (wk,wv)|
|
393
393
|
if headers.find {|(hk,hv)| hk == wk && hv == wv }.nil?
|
394
394
|
commands.push @cc.removecmd idx
|
395
395
|
end
|
396
396
|
end
|
397
397
|
|
398
|
-
#
|
398
|
+
# Generate add commands for new headers
|
399
399
|
headers.each do |(hk,hv)|
|
400
|
-
if @cc.
|
400
|
+
if @cc.refset.find {|i,(wk,wv)| hk == wk && hv == wv}.nil?
|
401
401
|
commands.push @cc.addcmd [hk, hv]
|
402
402
|
end
|
403
403
|
end
|
404
404
|
|
405
|
-
commands.
|
405
|
+
commands.each do |cmd|
|
406
406
|
@cc.process cmd.dup
|
407
|
-
header
|
408
|
-
end
|
407
|
+
buffer << header(cmd)
|
408
|
+
end
|
409
|
+
|
410
|
+
buffer
|
409
411
|
end
|
410
412
|
end
|
411
413
|
|
@@ -418,7 +420,7 @@ module HTTP2
|
|
418
420
|
# client_role = Decompressor.new(:response)
|
419
421
|
class Decompressor
|
420
422
|
def initialize(type)
|
421
|
-
@cc =
|
423
|
+
@cc = EncodingContext.new(type)
|
422
424
|
end
|
423
425
|
|
424
426
|
# Decodes integer value from provided buffer.
|
@@ -430,7 +432,7 @@ module HTTP2
|
|
430
432
|
i = !n.zero? ? (buf.getbyte & limit) : 0
|
431
433
|
|
432
434
|
m = 0
|
433
|
-
buf.
|
435
|
+
while byte = buf.getbyte do
|
434
436
|
i += ((byte & 127) << m)
|
435
437
|
m += 7
|
436
438
|
|
@@ -450,10 +452,9 @@ module HTTP2
|
|
450
452
|
|
451
453
|
# Decodes header command from provided buffer.
|
452
454
|
#
|
453
|
-
# @param buf [
|
455
|
+
# @param buf [Buffer]
|
454
456
|
def header(buf)
|
455
|
-
peek = buf.
|
456
|
-
buf.seek(-1, IO::SEEK_CUR)
|
457
|
+
peek = buf.readbyte(0)
|
457
458
|
|
458
459
|
header = {}
|
459
460
|
header[:type], type = HEADREP.select do |t, desc|
|
@@ -481,11 +482,26 @@ module HTTP2
|
|
481
482
|
|
482
483
|
# Decodes and processes header commands within provided buffer.
|
483
484
|
#
|
484
|
-
#
|
485
|
+
# Once all the representations contained in a header block have been
|
486
|
+
# processed, the headers that are in common with the previous header
|
487
|
+
# set are emitted, during the reference set emission.
|
488
|
+
#
|
489
|
+
# For the reference set emission, each header contained in the
|
490
|
+
# reference set that has not been emitted during the processing of the
|
491
|
+
# header block is emitted.
|
492
|
+
#
|
493
|
+
# - http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-03#section-3.2.2
|
494
|
+
#
|
495
|
+
# @param buf [Buffer]
|
496
|
+
# @return [Array] set of HTTP headers
|
485
497
|
def decode(buf)
|
486
|
-
|
487
|
-
@cc.process(header(buf)) while !buf.
|
488
|
-
@cc.
|
498
|
+
set = []
|
499
|
+
set << @cc.process(header(buf)) while !buf.empty?
|
500
|
+
@cc.refset.each do |i,header|
|
501
|
+
set << header if !set.include? header
|
502
|
+
end
|
503
|
+
|
504
|
+
set.compact
|
489
505
|
end
|
490
506
|
end
|
491
507
|
|