http-2 0.6.1
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 +7 -0
- data/.autotest +19 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/README.md +280 -0
- data/Rakefile +11 -0
- data/example/client.rb +46 -0
- data/example/helper.rb +14 -0
- data/example/server.rb +50 -0
- data/http-2.gemspec +24 -0
- data/lib/http/2/buffer.rb +21 -0
- data/lib/http/2/compressor.rb +493 -0
- data/lib/http/2/connection.rb +516 -0
- data/lib/http/2/emitter.rb +47 -0
- data/lib/http/2/error.rb +45 -0
- data/lib/http/2/flow_buffer.rb +64 -0
- data/lib/http/2/framer.rb +302 -0
- data/lib/http/2/stream.rb +474 -0
- data/lib/http/2/version.rb +3 -0
- data/lib/http/2.rb +9 -0
- data/spec/compressor_spec.rb +384 -0
- data/spec/connection_spec.rb +448 -0
- data/spec/emitter_spec.rb +46 -0
- data/spec/framer_spec.rb +325 -0
- data/spec/helper.rb +98 -0
- data/spec/stream_spec.rb +683 -0
- metadata +120 -0
@@ -0,0 +1,302 @@
|
|
1
|
+
module HTTP2
|
2
|
+
|
3
|
+
# Performs encoding, decoding, and validation of binary HTTP 2.0 frames.
|
4
|
+
#
|
5
|
+
class Framer
|
6
|
+
include Error
|
7
|
+
|
8
|
+
# Maximum frame size (65535 bytes)
|
9
|
+
MAX_PAYLOAD_SIZE = 2**16-1
|
10
|
+
|
11
|
+
# Maximum stream ID (2^31)
|
12
|
+
MAX_STREAM_ID = 0x7fffffff
|
13
|
+
|
14
|
+
# Maximum window increment value (2^31)
|
15
|
+
MAX_WINDOWINC = 0x7fffffff
|
16
|
+
|
17
|
+
# HTTP 2.0 frame type mapping as defined by the spec
|
18
|
+
FRAME_TYPES = {
|
19
|
+
data: 0x0,
|
20
|
+
headers: 0x1,
|
21
|
+
priority: 0x2,
|
22
|
+
rst_stream: 0x3,
|
23
|
+
settings: 0x4,
|
24
|
+
push_promise: 0x5,
|
25
|
+
ping: 0x6,
|
26
|
+
goaway: 0x7,
|
27
|
+
window_update: 0x9,
|
28
|
+
continuation: 0xa
|
29
|
+
}
|
30
|
+
|
31
|
+
# Per frame flags as defined by the spec
|
32
|
+
FRAME_FLAGS = {
|
33
|
+
data: {
|
34
|
+
end_stream: 0, reserved: 1
|
35
|
+
},
|
36
|
+
headers: {
|
37
|
+
end_stream: 0, reserved: 1,
|
38
|
+
end_headers: 2, priority: 3
|
39
|
+
},
|
40
|
+
priority: {},
|
41
|
+
rst_stream: {},
|
42
|
+
settings: {},
|
43
|
+
push_promise: { end_push_promise: 0 },
|
44
|
+
ping: { pong: 0 },
|
45
|
+
goaway: {},
|
46
|
+
window_update:{},
|
47
|
+
continuation: {
|
48
|
+
end_stream: 0, end_headers: 1
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
# Default settings as defined by the spec
|
53
|
+
DEFINED_SETTINGS = {
|
54
|
+
settings_max_concurrent_streams: 4,
|
55
|
+
settings_initial_window_size: 7,
|
56
|
+
settings_flow_control_options: 10
|
57
|
+
}
|
58
|
+
|
59
|
+
# Default error types as defined by the spec
|
60
|
+
DEFINED_ERRORS = {
|
61
|
+
no_error: 0,
|
62
|
+
protocol_error: 1,
|
63
|
+
internal_error: 2,
|
64
|
+
flow_control_error: 3,
|
65
|
+
stream_closed: 5,
|
66
|
+
frame_too_large: 6,
|
67
|
+
refused_stream: 7,
|
68
|
+
cancel: 8,
|
69
|
+
compression_error: 9
|
70
|
+
}
|
71
|
+
|
72
|
+
RBIT = 0x7fffffff
|
73
|
+
RBYTE = 0x0fffffff
|
74
|
+
HEADERPACK = "SCCL"
|
75
|
+
UINT32 = "L"
|
76
|
+
|
77
|
+
private_constant :RBIT, :RBYTE, :HEADERPACK, :UINT32
|
78
|
+
|
79
|
+
# Generates common 8-byte frame header.
|
80
|
+
# - http://tools.ietf.org/html/draft-ietf-httpbis-http2-04#section-4.1
|
81
|
+
#
|
82
|
+
# @param frame [Hash]
|
83
|
+
# @return [String]
|
84
|
+
def commonHeader(frame)
|
85
|
+
header = []
|
86
|
+
|
87
|
+
if !FRAME_TYPES[frame[:type]]
|
88
|
+
raise CompressionError.new("Invalid frame type (#{frame[:type]})")
|
89
|
+
end
|
90
|
+
|
91
|
+
if frame[:length] > MAX_PAYLOAD_SIZE
|
92
|
+
raise CompressionError.new("Frame size is too large: #{frame[:length]}")
|
93
|
+
end
|
94
|
+
|
95
|
+
if frame[:stream] > MAX_STREAM_ID
|
96
|
+
raise CompressionError.new("Stream ID (#{frame[:stream]}) is too large")
|
97
|
+
end
|
98
|
+
|
99
|
+
if frame[:type] == :window_update && frame[:increment] > MAX_WINDOWINC
|
100
|
+
raise CompressionError.new("Window increment (#{frame[:increment]}) is too large")
|
101
|
+
end
|
102
|
+
|
103
|
+
header << frame[:length]
|
104
|
+
header << FRAME_TYPES[frame[:type]]
|
105
|
+
header << frame[:flags].reduce(0) do |acc, f|
|
106
|
+
position = FRAME_FLAGS[frame[:type]][f]
|
107
|
+
if !position
|
108
|
+
raise CompressionError.new("Invalid frame flag (#{f}) for #{frame[:type]}")
|
109
|
+
end
|
110
|
+
|
111
|
+
acc |= (1 << position)
|
112
|
+
acc
|
113
|
+
end
|
114
|
+
|
115
|
+
header << frame[:stream]
|
116
|
+
header.pack(HEADERPACK) # 16,8,8,32
|
117
|
+
end
|
118
|
+
|
119
|
+
# Decodes common 8-byte header.
|
120
|
+
#
|
121
|
+
# @param buf [String]
|
122
|
+
def readCommonHeader(buf)
|
123
|
+
frame = {}
|
124
|
+
frame[:length], type, flags, stream = buf.slice(0,8).unpack(HEADERPACK)
|
125
|
+
|
126
|
+
frame[:type], _ = FRAME_TYPES.select { |t,pos| type == pos }.first
|
127
|
+
frame[:flags] = FRAME_FLAGS[frame[:type]].reduce([]) do |acc, (name, pos)|
|
128
|
+
acc << name if (flags & (1 << pos)) > 0
|
129
|
+
acc
|
130
|
+
end
|
131
|
+
|
132
|
+
frame[:stream] = stream & RBIT
|
133
|
+
frame
|
134
|
+
end
|
135
|
+
|
136
|
+
# Generates encoded HTTP 2.0 frame.
|
137
|
+
# - http://tools.ietf.org/html/draft-ietf-httpbis-http2
|
138
|
+
#
|
139
|
+
# @param frame [Hash]
|
140
|
+
def generate(frame)
|
141
|
+
bytes = ''
|
142
|
+
length = 0
|
143
|
+
|
144
|
+
frame[:flags] ||= []
|
145
|
+
frame[:stream] ||= 0
|
146
|
+
|
147
|
+
case frame[:type]
|
148
|
+
when :data
|
149
|
+
bytes += frame[:payload]
|
150
|
+
length += frame[:payload].bytesize
|
151
|
+
|
152
|
+
when :headers
|
153
|
+
if frame[:priority]
|
154
|
+
frame[:flags] += [:priority] if !frame[:flags].include? :priority
|
155
|
+
end
|
156
|
+
|
157
|
+
if frame[:flags].include? :priority
|
158
|
+
bytes += [frame[:priority] & RBIT].pack(UINT32)
|
159
|
+
length += 4
|
160
|
+
end
|
161
|
+
|
162
|
+
bytes += frame[:payload]
|
163
|
+
length += frame[:payload].bytesize
|
164
|
+
|
165
|
+
when :priority
|
166
|
+
bytes += [frame[:priority] & RBIT].pack(UINT32)
|
167
|
+
length += 4
|
168
|
+
|
169
|
+
when :rst_stream
|
170
|
+
bytes += pack_error frame[:error]
|
171
|
+
length += 4
|
172
|
+
|
173
|
+
when :settings
|
174
|
+
if frame[:stream] != 0
|
175
|
+
raise CompressionError.new("Invalid stream ID (#{frame[:stream]})")
|
176
|
+
end
|
177
|
+
|
178
|
+
frame[:payload].each do |(k,v)|
|
179
|
+
if !k.is_a? Integer
|
180
|
+
k = DEFINED_SETTINGS[k]
|
181
|
+
|
182
|
+
if k.nil?
|
183
|
+
raise CompressionError.new("Unknown settings ID for #{k}")
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
bytes += [k & RBYTE].pack(UINT32)
|
188
|
+
bytes += [v].pack(UINT32)
|
189
|
+
length += 8
|
190
|
+
end
|
191
|
+
|
192
|
+
when :push_promise
|
193
|
+
bytes += [frame[:promise_stream] & RBIT].pack(UINT32)
|
194
|
+
bytes += frame[:payload]
|
195
|
+
length += 4 + frame[:payload].bytesize
|
196
|
+
|
197
|
+
when :ping
|
198
|
+
if frame[:payload].bytesize != 8
|
199
|
+
raise CompressionError.new("Invalid payload size \
|
200
|
+
(#{frame[:payload].size} != 8 bytes)")
|
201
|
+
end
|
202
|
+
bytes += frame[:payload]
|
203
|
+
length += 8
|
204
|
+
|
205
|
+
when :goaway
|
206
|
+
bytes += [frame[:last_stream] & RBIT].pack(UINT32)
|
207
|
+
bytes += pack_error frame[:error]
|
208
|
+
length += 8
|
209
|
+
|
210
|
+
if frame[:payload]
|
211
|
+
bytes += frame[:payload]
|
212
|
+
length += frame[:payload].bytesize
|
213
|
+
end
|
214
|
+
|
215
|
+
when :window_update
|
216
|
+
bytes += [frame[:increment] & RBIT].pack(UINT32)
|
217
|
+
length += 4
|
218
|
+
|
219
|
+
when :continuation
|
220
|
+
bytes += frame[:payload]
|
221
|
+
length += frame[:payload].bytesize
|
222
|
+
end
|
223
|
+
|
224
|
+
frame[:length] = length
|
225
|
+
commonHeader(frame) + bytes
|
226
|
+
end
|
227
|
+
|
228
|
+
# Decodes complete HTTP 2.0 frame from provided buffer. If the buffer
|
229
|
+
# does not contain enough data, no further work is performed.
|
230
|
+
#
|
231
|
+
# @param buf [String]
|
232
|
+
def parse(buf)
|
233
|
+
return nil if buf.size < 8
|
234
|
+
frame = readCommonHeader(buf)
|
235
|
+
return nil if buf.size < 8 + frame[:length]
|
236
|
+
|
237
|
+
buf.read(8)
|
238
|
+
payload = buf.read(frame[:length])
|
239
|
+
|
240
|
+
case frame[:type]
|
241
|
+
when :data
|
242
|
+
frame[:payload] = payload.read(frame[:length])
|
243
|
+
when :headers
|
244
|
+
if frame[:flags].include? :priority
|
245
|
+
frame[:priority] = payload.read(4).unpack(UINT32).first & RBIT
|
246
|
+
end
|
247
|
+
frame[:payload] = payload.read(frame[:length])
|
248
|
+
when :priority
|
249
|
+
frame[:priority] = payload.read(4).unpack(UINT32).first & RBIT
|
250
|
+
when :rst_stream
|
251
|
+
frame[:error] = unpack_error payload.read(4).unpack(UINT32).first
|
252
|
+
|
253
|
+
when :settings
|
254
|
+
frame[:payload] = {}
|
255
|
+
(frame[:length] / 8).times do
|
256
|
+
id = payload.read(4).unpack(UINT32).first & RBYTE
|
257
|
+
val = payload.read(4).unpack(UINT32).first
|
258
|
+
|
259
|
+
name, _ = DEFINED_SETTINGS.select { |name, v| v == id }.first
|
260
|
+
frame[:payload][name || id] = val
|
261
|
+
end
|
262
|
+
when :push_promise
|
263
|
+
frame[:promise_stream] = payload.read(4).unpack(UINT32).first & RBIT
|
264
|
+
frame[:payload] = payload.read(frame[:length])
|
265
|
+
when :ping
|
266
|
+
frame[:payload] = payload.read(frame[:length])
|
267
|
+
when :goaway
|
268
|
+
frame[:last_stream] = payload.read(4).unpack(UINT32).first & RBIT
|
269
|
+
frame[:error] = unpack_error payload.read(4).unpack(UINT32).first
|
270
|
+
|
271
|
+
size = frame[:length] - 8
|
272
|
+
frame[:payload] = payload.read(size) if size > 0
|
273
|
+
when :window_update
|
274
|
+
frame[:increment] = payload.read(4).unpack(UINT32).first & RBIT
|
275
|
+
when :continuation
|
276
|
+
frame[:payload] = payload.read(frame[:length])
|
277
|
+
end
|
278
|
+
|
279
|
+
frame
|
280
|
+
end
|
281
|
+
|
282
|
+
private
|
283
|
+
|
284
|
+
def pack_error(e)
|
285
|
+
if !e.is_a? Integer
|
286
|
+
e = DEFINED_ERRORS[e]
|
287
|
+
|
288
|
+
if e.nil?
|
289
|
+
raise CompressionError.new("Unknown error ID for #{e}")
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
[e].pack(UINT32)
|
294
|
+
end
|
295
|
+
|
296
|
+
def unpack_error(e)
|
297
|
+
name, _ = DEFINED_ERRORS.select { |name, v| v == e }.first
|
298
|
+
name || error
|
299
|
+
end
|
300
|
+
|
301
|
+
end
|
302
|
+
end
|