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,474 @@
|
|
1
|
+
module HTTP2
|
2
|
+
|
3
|
+
# A single HTTP 2.0 connection can multiplex multiple streams in parallel:
|
4
|
+
# multiple requests and responses can be in flight simultaneously and stream
|
5
|
+
# data can be interleaved and prioritized.
|
6
|
+
#
|
7
|
+
# This class encapsulates all of the state, transition, flow-control, and
|
8
|
+
# error management as defined by the HTTP 2.0 specification. All you have
|
9
|
+
# to do is subscribe to appropriate events (marked with ":" prefix in
|
10
|
+
# diagram below) and provide your application logic to handle request
|
11
|
+
# and response processing.
|
12
|
+
#
|
13
|
+
# +--------+
|
14
|
+
# PP | | PP
|
15
|
+
# ,--------| idle |--------.
|
16
|
+
# / | | \
|
17
|
+
# v +--------+ v
|
18
|
+
# +----------+ | +----------+
|
19
|
+
# | | | H | |
|
20
|
+
# ,---|:reserved | | |:reserved |---.
|
21
|
+
# | | (local) | v | (remote) | |
|
22
|
+
# | +----------+ +--------+ +----------+ |
|
23
|
+
# | | :active | | :active | |
|
24
|
+
# | | ,-------|:active |-------. | |
|
25
|
+
# | | H / ES | | ES \ H | |
|
26
|
+
# | v v +--------+ v v |
|
27
|
+
# | +-----------+ | +-_---------+ |
|
28
|
+
# | |:half_close| | |:half_close| |
|
29
|
+
# | | (remote) | | | (local) | |
|
30
|
+
# | +-----------+ | +-----------+ |
|
31
|
+
# | | v | |
|
32
|
+
# | | ES/R +--------+ ES/R | |
|
33
|
+
# | `----------->| |<-----------' |
|
34
|
+
# | R | :close | R |
|
35
|
+
# `-------------------->| |<--------------------'
|
36
|
+
# +--------+
|
37
|
+
class Stream
|
38
|
+
include FlowBuffer
|
39
|
+
include Emitter
|
40
|
+
include Error
|
41
|
+
|
42
|
+
# Stream ID (odd for client initiated streams, even otherwise).
|
43
|
+
attr_reader :id
|
44
|
+
|
45
|
+
# Stream state as defined by HTTP 2.0.
|
46
|
+
attr_reader :state
|
47
|
+
|
48
|
+
# Request parent stream of push stream.
|
49
|
+
attr_reader :parent
|
50
|
+
|
51
|
+
# Stream priority as set by initiator.
|
52
|
+
attr_reader :priority
|
53
|
+
|
54
|
+
# Size of current stream flow control window.
|
55
|
+
attr_reader :window
|
56
|
+
|
57
|
+
# Reason why connection was closed.
|
58
|
+
attr_reader :closed
|
59
|
+
|
60
|
+
# Initializes new stream.
|
61
|
+
#
|
62
|
+
# Note that you should never have to call this directly. To create a new
|
63
|
+
# client initiated stream, use Connection#new_stream. Similarly, Connection
|
64
|
+
# will emit new stream objects, when new stream frames are received.
|
65
|
+
#
|
66
|
+
# @param id [Integer]
|
67
|
+
# @param priority [Integer]
|
68
|
+
# @param window [Integer]
|
69
|
+
# @param parent [Stream]
|
70
|
+
def initialize(id, priority, window, parent = nil)
|
71
|
+
@id = id
|
72
|
+
@priority = priority
|
73
|
+
@window = window
|
74
|
+
@parent = parent
|
75
|
+
@state = :idle
|
76
|
+
@error = false
|
77
|
+
@closed = false
|
78
|
+
@send_buffer = []
|
79
|
+
|
80
|
+
on(:window) { |v| @window = v }
|
81
|
+
end
|
82
|
+
|
83
|
+
# Processes incoming HTTP 2.0 frames. The frames must be decoded upstream.
|
84
|
+
#
|
85
|
+
# @param frame [Hash]
|
86
|
+
def receive(frame)
|
87
|
+
transition(frame, false)
|
88
|
+
|
89
|
+
case frame[:type]
|
90
|
+
when :data
|
91
|
+
emit(:data, frame[:payload]) if !frame[:ignore]
|
92
|
+
when :headers, :push_promise
|
93
|
+
if frame[:payload].is_a? Array
|
94
|
+
emit(:headers, Hash[*frame[:payload].flatten]) if !frame[:ignore]
|
95
|
+
else
|
96
|
+
emit(:headers, frame[:payload]) if !frame[:ignore]
|
97
|
+
end
|
98
|
+
when :priority
|
99
|
+
@priority = frame[:priority]
|
100
|
+
emit(:priority, @priority)
|
101
|
+
when :window_update
|
102
|
+
@window += frame[:increment]
|
103
|
+
send_data
|
104
|
+
end
|
105
|
+
|
106
|
+
complete_transition(frame)
|
107
|
+
end
|
108
|
+
alias :<< :receive
|
109
|
+
|
110
|
+
# Processes outgoing HTTP 2.0 frames. Data frames may be automatically
|
111
|
+
# split and buffered based on maximum frame size and current stream flow
|
112
|
+
# control window size.
|
113
|
+
#
|
114
|
+
# @param frame [Hash]
|
115
|
+
def send(frame)
|
116
|
+
transition(frame, true)
|
117
|
+
frame[:stream] ||= @id
|
118
|
+
|
119
|
+
@priority = frame[:priority] if frame[:type] == :priority
|
120
|
+
|
121
|
+
if frame[:type] == :data
|
122
|
+
send_data(frame)
|
123
|
+
else
|
124
|
+
emit(:frame, frame)
|
125
|
+
end
|
126
|
+
|
127
|
+
complete_transition(frame)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Sends a HEADERS frame containing HTTP response headers.
|
131
|
+
#
|
132
|
+
# @param headers [Hash]
|
133
|
+
# @param end_headers [Boolean] indicates that no more headers will be sent
|
134
|
+
# @param end_stream [Boolean] indicates that no payload will be sent
|
135
|
+
def headers(headers, end_headers: true, end_stream: false)
|
136
|
+
flags = []
|
137
|
+
flags << :end_headers if end_headers
|
138
|
+
flags << :end_stream if end_stream
|
139
|
+
|
140
|
+
send({type: :headers, flags: flags, payload: headers.to_a})
|
141
|
+
end
|
142
|
+
|
143
|
+
def promise(headers, end_push_promise: true, &block)
|
144
|
+
raise Exception.new("must provide callback") if !block_given?
|
145
|
+
|
146
|
+
flags = end_push_promise ? [:end_push_promise] : []
|
147
|
+
emit(:promise, self, headers, flags, &block)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Sends a PRIORITY frame with new stream priority value (can only be
|
151
|
+
# performed by the client).
|
152
|
+
#
|
153
|
+
# @param p [Integer] new stream priority value
|
154
|
+
def reprioritize(p)
|
155
|
+
stream_error if @id.even?
|
156
|
+
send({type: :priority, priority: p})
|
157
|
+
end
|
158
|
+
|
159
|
+
# Sends DATA frame containing response payload.
|
160
|
+
#
|
161
|
+
# @param payload [String]
|
162
|
+
# @param end_stream [Boolean] indicates last response DATA frame
|
163
|
+
def data(payload, end_stream: true)
|
164
|
+
flags = []
|
165
|
+
flags << :end_stream if end_stream
|
166
|
+
|
167
|
+
while payload.bytesize > MAX_FRAME_SIZE do
|
168
|
+
chunk = payload.slice!(0, MAX_FRAME_SIZE)
|
169
|
+
send({type: :data, payload: chunk})
|
170
|
+
end
|
171
|
+
|
172
|
+
send({type: :data, flags: flags, payload: payload})
|
173
|
+
end
|
174
|
+
|
175
|
+
# Sends a RST_STREAM frame which closes current stream - this does not
|
176
|
+
# close the underlying connection.
|
177
|
+
#
|
178
|
+
# @param error [:Symbol] optional reason why stream was closed
|
179
|
+
def close(error = :stream_closed)
|
180
|
+
send({type: :rst_stream, error: error})
|
181
|
+
end
|
182
|
+
|
183
|
+
# Sends a RST_STREAM indicating that the stream is no longer needed.
|
184
|
+
def cancel
|
185
|
+
send({type: :rst_stream, error: :cancel})
|
186
|
+
end
|
187
|
+
|
188
|
+
# Sends a RST_STREAM indicating that the stream has been refused prior
|
189
|
+
# to performing any application processing.
|
190
|
+
def refuse
|
191
|
+
send({type: :rst_stream, error: :refused_stream})
|
192
|
+
end
|
193
|
+
|
194
|
+
private
|
195
|
+
|
196
|
+
# HTTP 2.0 Stream States
|
197
|
+
# - http://tools.ietf.org/html/draft-ietf-httpbis-http2-05#section-5
|
198
|
+
#
|
199
|
+
# +--------+
|
200
|
+
# PP | | PP
|
201
|
+
# ,--------| idle |--------.
|
202
|
+
# / | | \
|
203
|
+
# v +--------+ v
|
204
|
+
# +----------+ | +----------+
|
205
|
+
# | | | H | |
|
206
|
+
# ,---| reserved | | | reserved |---.
|
207
|
+
# | | (local) | v | (remote) | |
|
208
|
+
# | +----------+ +--------+ +----------+ |
|
209
|
+
# | | ES | | ES | |
|
210
|
+
# | | H ,-------| open |-------. | H |
|
211
|
+
# | | / | | \ | |
|
212
|
+
# | v v +--------+ v v |
|
213
|
+
# | +----------+ | +----------+ |
|
214
|
+
# | | half | | | half | |
|
215
|
+
# | | closed | | R | closed | |
|
216
|
+
# | | (remote) | | | (local) | |
|
217
|
+
# | +----------+ | +----------+ |
|
218
|
+
# | | v | |
|
219
|
+
# | | ES / R +--------+ ES / R | |
|
220
|
+
# | `----------->| |<-----------' |
|
221
|
+
# | R | closed | R |
|
222
|
+
# `-------------------->| |<--------------------'
|
223
|
+
# +--------+
|
224
|
+
#
|
225
|
+
def transition(frame, sending)
|
226
|
+
case @state
|
227
|
+
|
228
|
+
# All streams start in the "idle" state. In this state, no frames
|
229
|
+
# have been exchanged.
|
230
|
+
# * Sending or receiving a HEADERS frame causes the stream to
|
231
|
+
# become "open". The stream identifier is selected as described
|
232
|
+
# in Section 5.1.1.
|
233
|
+
# * Sending a PUSH_PROMISE frame marks the associated stream for
|
234
|
+
# later use. The stream state for the reserved stream
|
235
|
+
# transitions to "reserved (local)".
|
236
|
+
# * Receiving a PUSH_PROMISE frame marks the associated stream as
|
237
|
+
# reserved by the remote peer. The state of the stream becomes
|
238
|
+
# "reserved (remote)".
|
239
|
+
when :idle
|
240
|
+
if sending
|
241
|
+
case frame[:type]
|
242
|
+
when :push_promise then event(:reserved_local)
|
243
|
+
when :headers
|
244
|
+
if end_stream?(frame)
|
245
|
+
event(:half_closed_local)
|
246
|
+
else
|
247
|
+
event(:open)
|
248
|
+
end
|
249
|
+
when :rst_stream then event(:local_rst)
|
250
|
+
else stream_error; end
|
251
|
+
else
|
252
|
+
case frame[:type]
|
253
|
+
when :push_promise then event(:reserved_remote)
|
254
|
+
when :headers
|
255
|
+
if end_stream?(frame)
|
256
|
+
event(:half_closed_remote)
|
257
|
+
else
|
258
|
+
event(:open)
|
259
|
+
end
|
260
|
+
else stream_error(:protocol_error); end
|
261
|
+
end
|
262
|
+
|
263
|
+
# A stream in the "reserved (local)" state is one that has been
|
264
|
+
# promised by sending a PUSH_PROMISE frame. A PUSH_PROMISE frame
|
265
|
+
# reserves an idle stream by associating the stream with an open
|
266
|
+
# stream that was initiated by the remote peer (see Section 8.2).
|
267
|
+
# * The endpoint can send a HEADERS frame. This causes the stream
|
268
|
+
# to open in a "half closed (remote)" state.
|
269
|
+
# * Either endpoint can send a RST_STREAM frame to cause the stream
|
270
|
+
# to become "closed". This also releases the stream reservation.
|
271
|
+
# An endpoint MUST NOT send any other type of frame in this state.
|
272
|
+
# Receiving any frame other than RST_STREAM or PRIORITY MUST be
|
273
|
+
# treated as a connection error (Section 5.4.1) of type
|
274
|
+
# PROTOCOL_ERROR.
|
275
|
+
when :reserved_local
|
276
|
+
if sending
|
277
|
+
@state = case frame[:type]
|
278
|
+
when :headers then event(:half_closed_remote)
|
279
|
+
when :rst_stream then event(:local_rst)
|
280
|
+
else stream_error; end
|
281
|
+
else
|
282
|
+
@state = case frame[:type]
|
283
|
+
when :rst_stream then event(:remote_rst)
|
284
|
+
when :priority then @state
|
285
|
+
else stream_error; end
|
286
|
+
end
|
287
|
+
|
288
|
+
# A stream in the "reserved (remote)" state has been reserved by a
|
289
|
+
# remote peer.
|
290
|
+
# * Receiving a HEADERS frame causes the stream to transition to
|
291
|
+
# "half closed (local)".
|
292
|
+
# * Either endpoint can send a RST_STREAM frame to cause the stream
|
293
|
+
# to become "closed". This also releases the stream reservation.
|
294
|
+
# Receiving any other type of frame MUST be treated as a stream
|
295
|
+
# error (Section 5.4.2) of type PROTOCOL_ERROR. An endpoint MAY
|
296
|
+
# send RST_STREAM or PRIORITY frames in this state to cancel or
|
297
|
+
# reprioritize the reserved stream.
|
298
|
+
when :reserved_remote
|
299
|
+
if sending
|
300
|
+
@state = case frame[:type]
|
301
|
+
when :rst_stream then event(:local_rst)
|
302
|
+
when :priority then @state
|
303
|
+
else stream_error; end
|
304
|
+
else
|
305
|
+
@state = case frame[:type]
|
306
|
+
when :headers then event(:half_closed_local)
|
307
|
+
when :rst_stream then event(:remote_rst)
|
308
|
+
else stream_error; end
|
309
|
+
end
|
310
|
+
|
311
|
+
# The "open" state is where both peers can send frames of any type.
|
312
|
+
# In this state, sending peers observe advertised stream level flow
|
313
|
+
# control limits (Section 5.2).
|
314
|
+
# * From this state either endpoint can send a frame with a END_STREAM
|
315
|
+
# flag set, which causes the stream to transition into one of the
|
316
|
+
# "half closed" states: an endpoint sending a END_STREAM flag causes
|
317
|
+
# the stream state to become "half closed (local)"; an endpoint
|
318
|
+
# receiving a END_STREAM flag causes the stream state to become
|
319
|
+
# "half closed (remote)".
|
320
|
+
# * Either endpoint can send a RST_STREAM frame from this state,
|
321
|
+
# causing it to transition immediately to "closed".
|
322
|
+
when :open
|
323
|
+
if sending
|
324
|
+
case frame[:type]
|
325
|
+
when :data, :headers, :continuation
|
326
|
+
event(:half_closed_local) if end_stream?(frame)
|
327
|
+
when :rst_stream then event(:local_rst)
|
328
|
+
end
|
329
|
+
else
|
330
|
+
case frame[:type]
|
331
|
+
when :data, :headers, :continuation
|
332
|
+
event(:half_closed_remote) if end_stream?(frame)
|
333
|
+
when :rst_stream then event(:remote_rst)
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
# A stream that is "half closed (local)" cannot be used for sending
|
338
|
+
# frames.
|
339
|
+
# A stream transitions from this state to "closed" when a frame that
|
340
|
+
# contains a END_STREAM flag is received, or when either peer sends
|
341
|
+
# a RST_STREAM frame.
|
342
|
+
# A receiver can ignore WINDOW_UPDATE or PRIORITY frames in this
|
343
|
+
# state. These frame types might arrive for a short period after a
|
344
|
+
# frame bearing the END_STREAM flag is sent.
|
345
|
+
when :half_closed_local
|
346
|
+
if sending
|
347
|
+
if frame[:type] == :rst_stream
|
348
|
+
event(:local_rst)
|
349
|
+
else
|
350
|
+
stream_error
|
351
|
+
end
|
352
|
+
else
|
353
|
+
case frame[:type]
|
354
|
+
when :data, :headers, :continuation
|
355
|
+
event(:remote_closed) if end_stream?(frame)
|
356
|
+
when :rst_stream then event(:remote_rst)
|
357
|
+
when :window_update, :priority
|
358
|
+
frame[:igore] = true
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
# A stream that is "half closed (remote)" is no longer being used by
|
363
|
+
# the peer to send frames. In this state, an endpoint is no longer
|
364
|
+
# obligated to maintain a receiver flow control window if it
|
365
|
+
# performs flow control.
|
366
|
+
# If an endpoint receives additional frames for a stream that is in
|
367
|
+
# this state it MUST respond with a stream error (Section 5.4.2) of
|
368
|
+
# type STREAM_CLOSED.
|
369
|
+
# A stream can transition from this state to "closed" by sending a
|
370
|
+
# frame that contains a END_STREAM flag, or when either peer sends a
|
371
|
+
# RST_STREAM frame.
|
372
|
+
when :half_closed_remote
|
373
|
+
if sending
|
374
|
+
case frame[:type]
|
375
|
+
when :data, :headers, :continuation
|
376
|
+
event(:local_closed) if end_stream?(frame)
|
377
|
+
when :rst_stream then event(:local_rst)
|
378
|
+
end
|
379
|
+
else
|
380
|
+
case frame[:type]
|
381
|
+
when :rst_stream then event(:remote_rst)
|
382
|
+
when :window_update then frame[:ignore] = true
|
383
|
+
else stream_error(:stream_closed); end
|
384
|
+
end
|
385
|
+
|
386
|
+
# An endpoint MUST NOT send frames on a closed stream. An endpoint
|
387
|
+
# that receives a frame after receiving a RST_STREAM or a frame
|
388
|
+
# containing a END_STREAM flag on that stream MUST treat that as a
|
389
|
+
# stream error (Section 5.4.2) of type STREAM_CLOSED.
|
390
|
+
#
|
391
|
+
# WINDOW_UPDATE or PRIORITY frames can be received in this state for
|
392
|
+
# a short period after a a frame containing an END_STREAM flag is
|
393
|
+
# sent. Until the remote peer receives and processes the frame
|
394
|
+
# bearing the END_STREAM flag, it might send either frame type.
|
395
|
+
#
|
396
|
+
# If this state is reached as a result of sending a RST_STREAM
|
397
|
+
# frame, the peer that receives the RST_STREAM might have already
|
398
|
+
# sent - or enqueued for sending - frames on the stream that cannot
|
399
|
+
# be withdrawn. An endpoint MUST ignore frames that it receives on
|
400
|
+
# closed streams after it has sent a RST_STREAM frame.
|
401
|
+
#
|
402
|
+
# An endpoint might receive a PUSH_PROMISE or a CONTINUATION frame
|
403
|
+
# after it sends RST_STREAM. PUSH_PROMISE causes a stream to become
|
404
|
+
# "reserved". If promised streams are not desired, a RST_STREAM can
|
405
|
+
# be used to close any of those streams.
|
406
|
+
when :closed
|
407
|
+
if sending
|
408
|
+
case frame[:type]
|
409
|
+
when :rst_stream then # ignore
|
410
|
+
else
|
411
|
+
stream_error(:stream_closed) if !(frame[:type] == :rst_stream)
|
412
|
+
end
|
413
|
+
else
|
414
|
+
case @closed
|
415
|
+
when :remote_rst, :remote_closed
|
416
|
+
stream_error(:stream_closed) if !(frame[:type] == :rst_stream)
|
417
|
+
when :local_rst, :local_closed
|
418
|
+
frame[:ignore] = true
|
419
|
+
end
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
def event(newstate)
|
425
|
+
case newstate
|
426
|
+
when :open
|
427
|
+
@state = newstate
|
428
|
+
emit(:active)
|
429
|
+
|
430
|
+
when :reserved_local, :reserved_remote
|
431
|
+
@state = newstate
|
432
|
+
emit(:reserved)
|
433
|
+
|
434
|
+
when :half_closed_local, :half_closed_remote
|
435
|
+
@closed = newstate
|
436
|
+
emit(:active) unless @state == :open
|
437
|
+
@state = :half_closing
|
438
|
+
|
439
|
+
when :local_closed, :remote_closed, :local_rst, :remote_rst
|
440
|
+
@closed = newstate
|
441
|
+
@state = :closing
|
442
|
+
end
|
443
|
+
|
444
|
+
@state
|
445
|
+
end
|
446
|
+
|
447
|
+
def complete_transition(frame)
|
448
|
+
case @state
|
449
|
+
when :closing
|
450
|
+
@state = :closed
|
451
|
+
emit(:close, frame[:error])
|
452
|
+
when :half_closing
|
453
|
+
@state = @closed
|
454
|
+
emit(:half_close)
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
def end_stream?(frame)
|
459
|
+
case frame[:type]
|
460
|
+
when :data, :headers, :continuation
|
461
|
+
frame[:flags].include?(:end_stream)
|
462
|
+
else false; end
|
463
|
+
end
|
464
|
+
|
465
|
+
def stream_error(error = :stream_error, msg: nil)
|
466
|
+
@error = error
|
467
|
+
close(error) if @state != :closed
|
468
|
+
|
469
|
+
klass = error.to_s.split('_').map(&:capitalize).join
|
470
|
+
raise Kernel.const_get(klass).new(msg)
|
471
|
+
end
|
472
|
+
|
473
|
+
end
|
474
|
+
end
|
data/lib/http/2.rb
ADDED