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.
@@ -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
@@ -0,0 +1,3 @@
1
+ module HTTP2
2
+ VERSION = "0.6.1"
3
+ end
data/lib/http/2.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "http/2/version"
2
+ require "http/2/error"
3
+ require "http/2/emitter"
4
+ require "http/2/buffer"
5
+ require "http/2/flow_buffer"
6
+ require "http/2/compressor"
7
+ require "http/2/framer"
8
+ require "http/2/connection"
9
+ require "http/2/stream"