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
data/lib/http/2/connection.rb
CHANGED
@@ -11,36 +11,15 @@ module HTTP2
|
|
11
11
|
|
12
12
|
# Connection encapsulates all of the connection, stream, flow-control,
|
13
13
|
# error management, and other processing logic required for a well-behaved
|
14
|
-
# HTTP 2.0
|
15
|
-
#
|
16
|
-
# When the connection object is instantiated you must specify its role
|
17
|
-
# (:client or :server) to initialize appropriate header compression
|
18
|
-
# and decompression algorithms and stream management logic.
|
19
|
-
#
|
20
|
-
# Your code is responsible for feeding data to connection object, which
|
21
|
-
# performs all of the necessary HTTP 2.0 decoding, state management and
|
22
|
-
# the rest, and vice versa, the parser will emit bytes (encoded HTTP 2.0
|
23
|
-
# frames) that you can then route to the destination. Roughly, this works
|
24
|
-
# as follows:
|
25
|
-
#
|
26
|
-
# @example
|
27
|
-
# socket = YourTransport.new
|
28
|
-
#
|
29
|
-
# conn = HTTP2::Connection.new(:client)
|
30
|
-
# conn.on(:frame) {|bytes| socket << bytes }
|
31
|
-
#
|
32
|
-
# while bytes = socket.read
|
33
|
-
# conn << bytes
|
34
|
-
# end
|
14
|
+
# HTTP 2.0 endpoint.
|
35
15
|
#
|
16
|
+
# Note that this class should not be used directly. Instead, you want to
|
17
|
+
# use either Client or Server class to drive the HTTP 2.0 exchange.
|
36
18
|
class Connection
|
37
19
|
include FlowBuffer
|
38
20
|
include Emitter
|
39
21
|
include Error
|
40
22
|
|
41
|
-
# Type of connection (:server, :client).
|
42
|
-
attr_reader :type
|
43
|
-
|
44
23
|
# Connection state (:new, :closed).
|
45
24
|
attr_reader :state
|
46
25
|
|
@@ -59,34 +38,20 @@ module HTTP2
|
|
59
38
|
# are not counted towards the stream limit).
|
60
39
|
attr_reader :active_stream_count
|
61
40
|
|
62
|
-
# Initializes new
|
41
|
+
# Initializes new connection object.
|
63
42
|
#
|
64
|
-
|
65
|
-
|
66
|
-
@type = type
|
67
|
-
|
68
|
-
if @type == :server
|
69
|
-
@stream_id = 2
|
70
|
-
@compressor = Header::Compressor.new(:response)
|
71
|
-
@decompressor = Header::Decompressor.new(:request)
|
72
|
-
else
|
73
|
-
@stream_id = 1
|
74
|
-
@compressor = Header::Compressor.new(:request)
|
75
|
-
@decompressor = Header::Decompressor.new(:response)
|
76
|
-
end
|
77
|
-
|
78
|
-
@stream_limit = Float::INFINITY
|
43
|
+
def initialize(streams: 100, window: DEFAULT_FLOW_WINDOW)
|
44
|
+
@stream_limit = streams
|
79
45
|
@active_stream_count = 0
|
80
46
|
@streams = {}
|
81
47
|
|
82
48
|
@framer = Framer.new
|
83
|
-
@window =
|
84
|
-
@window_limit =
|
49
|
+
@window = window
|
50
|
+
@window_limit = window
|
85
51
|
|
86
52
|
@recv_buffer = Buffer.new
|
87
53
|
@send_buffer = []
|
88
54
|
@continuation = []
|
89
|
-
@state = :new
|
90
55
|
@error = nil
|
91
56
|
end
|
92
57
|
|
@@ -95,11 +60,11 @@ module HTTP2
|
|
95
60
|
# @param priority [Integer]
|
96
61
|
# @param window [Integer]
|
97
62
|
# @param parent [Stream]
|
98
|
-
def new_stream(priority: DEFAULT_PRIORITY,
|
63
|
+
def new_stream(priority: DEFAULT_PRIORITY, parent: nil)
|
99
64
|
raise ConnectionClosed.new if @state == :closed
|
100
65
|
raise StreamLimitExceeded.new if @active_stream_count == @stream_limit
|
101
66
|
|
102
|
-
stream = activate_stream(@stream_id, priority,
|
67
|
+
stream = activate_stream(@stream_id, priority, parent)
|
103
68
|
@stream_id += 2
|
104
69
|
|
105
70
|
stream
|
@@ -132,13 +97,19 @@ module HTTP2
|
|
132
97
|
@state = :closed
|
133
98
|
end
|
134
99
|
|
135
|
-
# Sends a connection SETTINGS frame to the peer.
|
100
|
+
# Sends a connection SETTINGS frame to the peer. Setting window size
|
101
|
+
# to Float::INFINITY disables flow control.
|
136
102
|
#
|
137
|
-
# @param
|
138
|
-
# @
|
139
|
-
|
140
|
-
|
141
|
-
|
103
|
+
# @param stream_limit [Integer] maximum number of concurrent streams
|
104
|
+
# @param window_limit [Float] maximum flow window size
|
105
|
+
def settings(stream_limit: @stream_limit, window_limit: @window_limit)
|
106
|
+
payload = { settings_max_concurrent_streams: stream_limit }
|
107
|
+
if window_limit.to_f.infinite?
|
108
|
+
payload[:settings_flow_control_options] = 1
|
109
|
+
else
|
110
|
+
payload[:settings_initial_window_size] = window_limit
|
111
|
+
end
|
112
|
+
|
142
113
|
send({type: :settings, stream: 0, payload: payload})
|
143
114
|
end
|
144
115
|
|
@@ -150,6 +121,29 @@ module HTTP2
|
|
150
121
|
def receive(data)
|
151
122
|
@recv_buffer << data
|
152
123
|
|
124
|
+
# Upon establishment of a TCP connection and determination that
|
125
|
+
# HTTP/2.0 will be used by both peers, each endpoint MUST send a
|
126
|
+
# connection header as a final confirmation and to establish the
|
127
|
+
# initial settings for the HTTP/2.0 connection.
|
128
|
+
#
|
129
|
+
# Client connection header is 24 byte connection header followed by
|
130
|
+
# SETTINGS frame. Server connection header is SETTINGS frame only.
|
131
|
+
if @state == :new
|
132
|
+
if @recv_buffer.size < 24
|
133
|
+
if !CONNECTION_HEADER.start_with? @recv_buffer
|
134
|
+
raise HandshakeError.new
|
135
|
+
else
|
136
|
+
return
|
137
|
+
end
|
138
|
+
|
139
|
+
elsif @recv_buffer.read(24) != CONNECTION_HEADER
|
140
|
+
raise HandshakeError.new
|
141
|
+
else
|
142
|
+
@state = :connection_header
|
143
|
+
settings(stream_limit: @stream_limit, window_limit: @window_limit)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
153
147
|
while frame = @framer.parse(@recv_buffer) do
|
154
148
|
# Header blocks MUST be transmitted as a contiguous sequence of frames
|
155
149
|
# with no interleaved frames of any other type, or from any other stream.
|
@@ -207,8 +201,7 @@ module HTTP2
|
|
207
201
|
stream = @streams[frame[:stream]]
|
208
202
|
if stream.nil?
|
209
203
|
stream = activate_stream(frame[:stream],
|
210
|
-
frame[:priority] || DEFAULT_PRIORITY
|
211
|
-
@window_limit)
|
204
|
+
frame[:priority] || DEFAULT_PRIORITY)
|
212
205
|
emit(:stream, stream)
|
213
206
|
end
|
214
207
|
|
@@ -255,7 +248,7 @@ module HTTP2
|
|
255
248
|
end
|
256
249
|
end
|
257
250
|
|
258
|
-
stream = activate_stream(pid, DEFAULT_PRIORITY,
|
251
|
+
stream = activate_stream(pid, DEFAULT_PRIORITY, parent)
|
259
252
|
emit(:promise, stream)
|
260
253
|
stream << frame
|
261
254
|
else
|
@@ -269,6 +262,9 @@ module HTTP2
|
|
269
262
|
end
|
270
263
|
end
|
271
264
|
end
|
265
|
+
|
266
|
+
rescue
|
267
|
+
connection_error
|
272
268
|
end
|
273
269
|
alias :<< :receive
|
274
270
|
|
@@ -300,7 +296,7 @@ module HTTP2
|
|
300
296
|
# Applies HTTP 2.0 binary encoding to the frame.
|
301
297
|
#
|
302
298
|
# @param frame [Hash]
|
303
|
-
# @return [
|
299
|
+
# @return [Buffer] encoded frame
|
304
300
|
def encode(frame)
|
305
301
|
if frame[:type] == :headers ||
|
306
302
|
frame[:type] == :push_promise
|
@@ -331,7 +327,7 @@ module HTTP2
|
|
331
327
|
# @param frame [Hash]
|
332
328
|
def connection_management(frame)
|
333
329
|
case @state
|
334
|
-
when :
|
330
|
+
when :connection_header
|
335
331
|
# SETTINGS frames MUST be sent at the start of a connection.
|
336
332
|
connection_settings(frame)
|
337
333
|
@state = :connected
|
@@ -419,7 +415,7 @@ module HTTP2
|
|
419
415
|
# @param frame [Hash]
|
420
416
|
def decode_headers(frame)
|
421
417
|
if frame[:payload].is_a? String
|
422
|
-
frame[:payload] = @decompressor.decode(
|
418
|
+
frame[:payload] = @decompressor.decode(frame[:payload])
|
423
419
|
end
|
424
420
|
|
425
421
|
rescue Exception => e
|
@@ -453,46 +449,24 @@ module HTTP2
|
|
453
449
|
# @param priority [Integer]
|
454
450
|
# @param window [Integer]
|
455
451
|
# @param parent [Stream]
|
456
|
-
def activate_stream(id, priority,
|
452
|
+
def activate_stream(id, priority, parent = nil)
|
457
453
|
if @streams.key?(id)
|
458
454
|
connection_error(msg: 'Stream ID already exists')
|
459
455
|
end
|
460
456
|
|
461
|
-
stream = Stream.new(id, priority,
|
457
|
+
stream = Stream.new(id, priority, @window_limit, parent)
|
462
458
|
|
463
459
|
# Streams that are in the "open" state, or either of the "half closed"
|
464
460
|
# states count toward the maximum number of streams that an endpoint is
|
465
461
|
# permitted to open.
|
466
462
|
stream.once(:active) { @active_stream_count += 1 }
|
467
463
|
stream.once(:close) { @active_stream_count -= 1 }
|
468
|
-
stream.on(:promise, &method(:promise))
|
464
|
+
stream.on(:promise, &method(:promise)) if self.is_a? Server
|
469
465
|
stream.on(:frame, &method(:send))
|
470
466
|
|
471
467
|
@streams[id] = stream
|
472
468
|
end
|
473
469
|
|
474
|
-
# Handle locally initiated server-push event emitted by the stream.
|
475
|
-
#
|
476
|
-
# @param args [Array]
|
477
|
-
# @param callback [Proc]
|
478
|
-
def promise(*args, &callback)
|
479
|
-
if @type == :client
|
480
|
-
raise ProtocolError.new("client cannot initiate promise")
|
481
|
-
end
|
482
|
-
|
483
|
-
parent, headers, flags = *args
|
484
|
-
promise = new_stream(parent: parent)
|
485
|
-
promise.send({
|
486
|
-
type: :push_promise,
|
487
|
-
flags: flags,
|
488
|
-
stream: parent.id,
|
489
|
-
promise_stream: promise.id,
|
490
|
-
payload: headers.to_a
|
491
|
-
})
|
492
|
-
|
493
|
-
callback.call(promise)
|
494
|
-
end
|
495
|
-
|
496
470
|
# Emit GOAWAY error indicating to peer that the connection is being
|
497
471
|
# aborted, and once sent, raise a local exception.
|
498
472
|
#
|
@@ -509,7 +483,7 @@ module HTTP2
|
|
509
483
|
|
510
484
|
@state, @error = :closed, error
|
511
485
|
klass = error.to_s.split('_').map(&:capitalize).join
|
512
|
-
raise
|
486
|
+
raise Error.const_get(klass).new(msg)
|
513
487
|
end
|
514
488
|
|
515
489
|
end
|
data/lib/http/2/emitter.rb
CHANGED
@@ -20,8 +20,8 @@ module HTTP2
|
|
20
20
|
# @param event [Symbol]
|
21
21
|
# @param block [Proc] callback function
|
22
22
|
def once(event, &block)
|
23
|
-
add_listener(event) do |*args|
|
24
|
-
block.call(*args)
|
23
|
+
add_listener(event) do |*args, &callback|
|
24
|
+
block.call(*args, &callback)
|
25
25
|
:delete
|
26
26
|
end
|
27
27
|
end
|
data/lib/http/2/error.rb
CHANGED
@@ -3,6 +3,11 @@ module HTTP2
|
|
3
3
|
# Stream, connection, and compressor exceptions.
|
4
4
|
module Error
|
5
5
|
|
6
|
+
# Raised if connection header is missing or invalid indicating that
|
7
|
+
# this is an invalid HTTP 2.0 request - no frames are emitted and the
|
8
|
+
# connection must be aborted.
|
9
|
+
class HandshakeError < Exception; end
|
10
|
+
|
6
11
|
# Raised by stream or connection handlers, results in GOAWAY frame
|
7
12
|
# which signals termination of the current connection. You *cannot*
|
8
13
|
# recover from this exception, or any exceptions subclassed from it.
|
data/lib/http/2/framer.rb
CHANGED
@@ -71,8 +71,8 @@ module HTTP2
|
|
71
71
|
|
72
72
|
RBIT = 0x7fffffff
|
73
73
|
RBYTE = 0x0fffffff
|
74
|
-
HEADERPACK = "
|
75
|
-
UINT32 = "
|
74
|
+
HEADERPACK = "nCCN"
|
75
|
+
UINT32 = "N"
|
76
76
|
|
77
77
|
private_constant :RBIT, :RBYTE, :HEADERPACK, :UINT32
|
78
78
|
|
@@ -118,7 +118,7 @@ module HTTP2
|
|
118
118
|
|
119
119
|
# Decodes common 8-byte header.
|
120
120
|
#
|
121
|
-
# @param buf [
|
121
|
+
# @param buf [Buffer]
|
122
122
|
def readCommonHeader(buf)
|
123
123
|
frame = {}
|
124
124
|
frame[:length], type, flags, stream = buf.slice(0,8).unpack(HEADERPACK)
|
@@ -138,7 +138,7 @@ module HTTP2
|
|
138
138
|
#
|
139
139
|
# @param frame [Hash]
|
140
140
|
def generate(frame)
|
141
|
-
bytes =
|
141
|
+
bytes = Buffer.new
|
142
142
|
length = 0
|
143
143
|
|
144
144
|
frame[:flags] ||= []
|
@@ -146,7 +146,7 @@ module HTTP2
|
|
146
146
|
|
147
147
|
case frame[:type]
|
148
148
|
when :data
|
149
|
-
bytes
|
149
|
+
bytes << frame[:payload]
|
150
150
|
length += frame[:payload].bytesize
|
151
151
|
|
152
152
|
when :headers
|
@@ -155,19 +155,19 @@ module HTTP2
|
|
155
155
|
end
|
156
156
|
|
157
157
|
if frame[:flags].include? :priority
|
158
|
-
bytes
|
158
|
+
bytes << [frame[:priority] & RBIT].pack(UINT32)
|
159
159
|
length += 4
|
160
160
|
end
|
161
161
|
|
162
|
-
bytes
|
162
|
+
bytes << frame[:payload]
|
163
163
|
length += frame[:payload].bytesize
|
164
164
|
|
165
165
|
when :priority
|
166
|
-
bytes
|
166
|
+
bytes << [frame[:priority] & RBIT].pack(UINT32)
|
167
167
|
length += 4
|
168
168
|
|
169
169
|
when :rst_stream
|
170
|
-
bytes
|
170
|
+
bytes << pack_error(frame[:error])
|
171
171
|
length += 4
|
172
172
|
|
173
173
|
when :settings
|
@@ -184,14 +184,14 @@ module HTTP2
|
|
184
184
|
end
|
185
185
|
end
|
186
186
|
|
187
|
-
bytes
|
188
|
-
bytes
|
187
|
+
bytes << [k & RBYTE].pack(UINT32)
|
188
|
+
bytes << [v].pack(UINT32)
|
189
189
|
length += 8
|
190
190
|
end
|
191
191
|
|
192
192
|
when :push_promise
|
193
|
-
bytes
|
194
|
-
bytes
|
193
|
+
bytes << [frame[:promise_stream] & RBIT].pack(UINT32)
|
194
|
+
bytes << frame[:payload]
|
195
195
|
length += 4 + frame[:payload].bytesize
|
196
196
|
|
197
197
|
when :ping
|
@@ -199,36 +199,36 @@ module HTTP2
|
|
199
199
|
raise CompressionError.new("Invalid payload size \
|
200
200
|
(#{frame[:payload].size} != 8 bytes)")
|
201
201
|
end
|
202
|
-
bytes
|
202
|
+
bytes << frame[:payload]
|
203
203
|
length += 8
|
204
204
|
|
205
205
|
when :goaway
|
206
|
-
bytes
|
207
|
-
bytes
|
206
|
+
bytes << [frame[:last_stream] & RBIT].pack(UINT32)
|
207
|
+
bytes << pack_error(frame[:error])
|
208
208
|
length += 8
|
209
209
|
|
210
210
|
if frame[:payload]
|
211
|
-
bytes
|
211
|
+
bytes << frame[:payload]
|
212
212
|
length += frame[:payload].bytesize
|
213
213
|
end
|
214
214
|
|
215
215
|
when :window_update
|
216
|
-
bytes
|
216
|
+
bytes << [frame[:increment] & RBIT].pack(UINT32)
|
217
217
|
length += 4
|
218
218
|
|
219
219
|
when :continuation
|
220
|
-
bytes
|
220
|
+
bytes << frame[:payload]
|
221
221
|
length += frame[:payload].bytesize
|
222
222
|
end
|
223
223
|
|
224
224
|
frame[:length] = length
|
225
|
-
commonHeader(frame)
|
225
|
+
bytes.prepend(commonHeader(frame))
|
226
226
|
end
|
227
227
|
|
228
228
|
# Decodes complete HTTP 2.0 frame from provided buffer. If the buffer
|
229
229
|
# does not contain enough data, no further work is performed.
|
230
230
|
#
|
231
|
-
# @param buf [
|
231
|
+
# @param buf [Buffer]
|
232
232
|
def parse(buf)
|
233
233
|
return nil if buf.size < 8
|
234
234
|
frame = readCommonHeader(buf)
|
@@ -242,36 +242,37 @@ module HTTP2
|
|
242
242
|
frame[:payload] = payload.read(frame[:length])
|
243
243
|
when :headers
|
244
244
|
if frame[:flags].include? :priority
|
245
|
-
frame[:priority] = payload.
|
245
|
+
frame[:priority] = payload.read_uint32 & RBIT
|
246
246
|
end
|
247
247
|
frame[:payload] = payload.read(frame[:length])
|
248
248
|
when :priority
|
249
|
-
frame[:priority] = payload.
|
249
|
+
frame[:priority] = payload.read_uint32 & RBIT
|
250
250
|
when :rst_stream
|
251
|
-
frame[:error] = unpack_error payload.
|
251
|
+
frame[:error] = unpack_error payload.read_uint32
|
252
252
|
|
253
253
|
when :settings
|
254
254
|
frame[:payload] = {}
|
255
255
|
(frame[:length] / 8).times do
|
256
|
-
id = payload.
|
257
|
-
val = payload.
|
256
|
+
id = payload.read_uint32 & RBYTE
|
257
|
+
val = payload.read_uint32
|
258
258
|
|
259
|
+
# Unsupported or unrecognized settings MUST be ignored.
|
259
260
|
name, _ = DEFINED_SETTINGS.select { |name, v| v == id }.first
|
260
|
-
frame[:payload][name
|
261
|
+
frame[:payload][name] = val if name
|
261
262
|
end
|
262
263
|
when :push_promise
|
263
|
-
frame[:promise_stream] = payload.
|
264
|
+
frame[:promise_stream] = payload.read_uint32 & RBIT
|
264
265
|
frame[:payload] = payload.read(frame[:length])
|
265
266
|
when :ping
|
266
267
|
frame[:payload] = payload.read(frame[:length])
|
267
268
|
when :goaway
|
268
|
-
frame[:last_stream] = payload.
|
269
|
-
frame[:error] = unpack_error payload.
|
269
|
+
frame[:last_stream] = payload.read_uint32 & RBIT
|
270
|
+
frame[:error] = unpack_error payload.read_uint32
|
270
271
|
|
271
272
|
size = frame[:length] - 8
|
272
273
|
frame[:payload] = payload.read(size) if size > 0
|
273
274
|
when :window_update
|
274
|
-
frame[:increment] = payload.
|
275
|
+
frame[:increment] = payload.read_uint32 & RBIT
|
275
276
|
when :continuation
|
276
277
|
frame[:payload] = payload.read(frame[:length])
|
277
278
|
end
|