mieps_http-2 0.8.0

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,132 @@
1
+ module HTTP2
2
+ # HTTP 2.0 server connection class that implements appropriate header
3
+ # compression / decompression algorithms and stream management logic.
4
+ #
5
+ # Your code is responsible for feeding request data to the server object,
6
+ # which in turn performs all of the necessary HTTP 2.0 decoding / encoding,
7
+ # state management, and the rest. A simple example:
8
+ #
9
+ # @example
10
+ # socket = YourTransport.new
11
+ #
12
+ # conn = HTTP2::Server.new
13
+ # conn.on(:stream) do |stream|
14
+ # ...
15
+ # end
16
+ #
17
+ # while bytes = socket.read
18
+ # conn << bytes
19
+ # end
20
+ #
21
+ class Server < Connection
22
+ # Initialize new HTTP 2.0 server object.
23
+ def initialize(**settings)
24
+ @stream_id = 2
25
+ @state = :waiting_magic
26
+
27
+ @local_role = :server
28
+ @remote_role = :client
29
+
30
+ super
31
+ end
32
+
33
+ # GET / HTTP/1.1
34
+ # Host: server.example.com
35
+ # Connection: Upgrade, HTTP2-Settings
36
+ # Upgrade: h2c
37
+ # HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
38
+ #
39
+ # Requests that contain a payload body MUST be sent in their entirety
40
+ # before the client can send HTTP/2 frames. This means that a large
41
+ # request can block the use of the connection until it is completely sent.
42
+ #
43
+ # If concurrency of an initial request with subsequent requests is
44
+ # important, an OPTIONS request can be used to perform the upgrade to
45
+ # HTTP/2, at the cost of an additional round trip.
46
+ #
47
+ # HTTP/1.1 101 Switching Protocols
48
+ # Connection: Upgrade
49
+ # Upgrade: h2c
50
+ #
51
+ # [ HTTP/2 connection ...
52
+ #
53
+ # - The first HTTP/2 frame sent by the server MUST be a server
54
+ # connection preface (Section 3.5) consisting of a SETTINGS frame.
55
+ # - Upon receiving the 101 response, the client MUST send a connection
56
+ # preface (Section 3.5), which includes a SETTINGS frame.
57
+ #
58
+ # The HTTP/1.1 request that is sent prior to upgrade is assigned a stream
59
+ # identifier of 1 (see Section 5.1.1) with default priority values
60
+ # (Section 5.3.5). Stream 1 is implicitly "half-closed" from the client
61
+ # toward the server (see Section 5.1), since the request is completed as
62
+ # an HTTP/1.1 request. After commencing the HTTP/2 connection, stream 1
63
+ # is used for the response.
64
+ #
65
+ def upgrade(settings, headers, body)
66
+ @h2c_upgrade = :start
67
+
68
+ # Pretend that we've received the preface
69
+ # - puts us into :waiting_connection_preface state
70
+ # - emits a SETTINGS frame to the client
71
+ receive(CONNECTION_PREFACE_MAGIC)
72
+
73
+ # Process received HTTP2-Settings payload
74
+ buf = HTTP2::Buffer.new Base64.urlsafe_decode64(settings)
75
+ header = @framer.common_header(
76
+ length: buf.bytesize,
77
+ type: :settings,
78
+ stream: 0,
79
+ flags: [],
80
+ )
81
+ buf.prepend(header)
82
+ receive(buf)
83
+
84
+ # Activate stream (id: 1) with on HTTP/1.1 request parameters
85
+ stream = activate_stream(id: 1)
86
+ emit(:stream, stream)
87
+
88
+ headers_frame = {
89
+ type: :headers,
90
+ stream: 1,
91
+ weight: DEFAULT_WEIGHT,
92
+ dependency: 0,
93
+ exclusive: false,
94
+ payload: headers,
95
+ }
96
+
97
+ if body.empty?
98
+ headers_frame.merge!(flags: [:end_stream])
99
+ stream << headers_frame
100
+ else
101
+ stream << headers_frame
102
+ stream << { type: :data, stream: 1, payload: body, flags: [:end_stream] }
103
+ end
104
+
105
+ # Mark h2c upgrade as finished
106
+ @h2c_upgrade = :finished
107
+
108
+ # Transition back to :waiting_magic and wait for client's preface
109
+ @state = :waiting_magic
110
+ end
111
+
112
+ private
113
+
114
+ # Handle locally initiated server-push event emitted by the stream.
115
+ #
116
+ # @param args [Array]
117
+ # @param callback [Proc]
118
+ def promise(*args, &callback)
119
+ parent, headers, flags = *args
120
+ promise = new_stream(parent: parent)
121
+ promise.send(
122
+ type: :push_promise,
123
+ flags: flags,
124
+ stream: parent.id,
125
+ promise_stream: promise.id,
126
+ payload: headers.to_a,
127
+ )
128
+
129
+ callback.call(promise)
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,576 @@
1
+ module HTTP2
2
+ # A single HTTP 2.0 connection can multiplex multiple streams in parallel:
3
+ # multiple requests and responses can be in flight simultaneously and stream
4
+ # data can be interleaved and prioritized.
5
+ #
6
+ # This class encapsulates all of the state, transition, flow-control, and
7
+ # error management as defined by the HTTP 2.0 specification. All you have
8
+ # to do is subscribe to appropriate events (marked with ":" prefix in
9
+ # diagram below) and provide your application logic to handle request
10
+ # and response processing.
11
+ #
12
+ # +--------+
13
+ # PP | | PP
14
+ # ,--------| idle |--------.
15
+ # / | | \
16
+ # v +--------+ v
17
+ # +----------+ | +----------+
18
+ # | | | H | |
19
+ # ,---|:reserved | | |:reserved |---.
20
+ # | | (local) | v | (remote) | |
21
+ # | +----------+ +--------+ +----------+ |
22
+ # | | :active | | :active | |
23
+ # | | ,-------|:active |-------. | |
24
+ # | | H / ES | | ES \ H | |
25
+ # | v v +--------+ v v |
26
+ # | +-----------+ | +-----------+ |
27
+ # | |:half_close| | |:half_close| |
28
+ # | | (remote) | | | (local) | |
29
+ # | +-----------+ | +-----------+ |
30
+ # | | v | |
31
+ # | | ES/R +--------+ ES/R | |
32
+ # | `----------->| |<-----------' |
33
+ # | R | :close | R |
34
+ # `-------------------->| |<--------------------'
35
+ # +--------+
36
+ class Stream
37
+ include FlowBuffer
38
+ include Emitter
39
+ include Error
40
+
41
+ # Stream ID (odd for client initiated streams, even otherwise).
42
+ attr_reader :id
43
+
44
+ # Stream state as defined by HTTP 2.0.
45
+ attr_reader :state
46
+
47
+ # Request parent stream of push stream.
48
+ attr_reader :parent
49
+
50
+ # Stream priority as set by initiator.
51
+ attr_reader :weight
52
+ attr_reader :dependency
53
+
54
+ # Size of current stream flow control window.
55
+ attr_reader :local_window
56
+ attr_reader :remote_window
57
+ alias_method :window, :local_window
58
+
59
+ # Reason why connection was closed.
60
+ attr_reader :closed
61
+
62
+ # Initializes new stream.
63
+ #
64
+ # Note that you should never have to call this directly. To create a new
65
+ # client initiated stream, use Connection#new_stream. Similarly, Connection
66
+ # will emit new stream objects, when new stream frames are received.
67
+ #
68
+ # @param id [Integer]
69
+ # @param weight [Integer]
70
+ # @param dependency [Integer]
71
+ # @param exclusive [Boolean]
72
+ # @param window [Integer]
73
+ # @param parent [Stream]
74
+ def initialize(connection, id, weight: 16, dependency: 0, exclusive: false, parent: nil)
75
+ @connection = connection
76
+ @id = id
77
+ @weight = weight
78
+ @dependency = dependency
79
+ process_priority(weight: weight, stream_dependency: dependency, exclusive: exclusive)
80
+ @local_window = connection.local_settings[:settings_initial_window_size]
81
+ @remote_window = connection.remote_settings[:settings_initial_window_size]
82
+ @parent = parent
83
+ @state = :idle
84
+ @error = false
85
+ @closed = false
86
+ @send_buffer = []
87
+
88
+ on(:window) { |v| @remote_window = v }
89
+ on(:local_window) { |v| @local_window = v }
90
+ end
91
+
92
+ # Processes incoming HTTP 2.0 frames. The frames must be decoded upstream.
93
+ #
94
+ # @param frame [Hash]
95
+ def receive(frame)
96
+ transition(frame, false)
97
+
98
+ case frame[:type]
99
+ when :data
100
+ @local_window -= frame[:payload].size
101
+ emit(:data, frame[:payload]) unless frame[:ignore]
102
+ when :headers, :push_promise
103
+ emit(:headers, frame[:payload]) unless frame[:ignore]
104
+ when :priority
105
+ process_priority(frame)
106
+ when :window_update
107
+ process_window_update(frame)
108
+ when :altsvc, :blocked
109
+ emit(frame[:type], frame)
110
+ end
111
+
112
+ complete_transition(frame)
113
+ end
114
+ alias_method :<<, :receive
115
+
116
+ # Processes outgoing HTTP 2.0 frames. Data frames may be automatically
117
+ # split and buffered based on maximum frame size and current stream flow
118
+ # control window size.
119
+ #
120
+ # @param frame [Hash]
121
+ def send(frame)
122
+ transition(frame, true)
123
+ frame[:stream] ||= @id
124
+
125
+ process_priority(frame) if frame[:type] == :priority
126
+
127
+ case frame[:type]
128
+ when :data
129
+ # @remote_window is maintained in send_data
130
+ send_data(frame)
131
+ when :window_update
132
+ @local_window += frame[:increment]
133
+ emit(:frame, frame)
134
+ else
135
+ emit(:frame, frame)
136
+ end
137
+
138
+ complete_transition(frame)
139
+ end
140
+
141
+ # Sends a HEADERS frame containing HTTP response headers.
142
+ #
143
+ # @param headers [Array or Hash] Array of key-value pairs or Hash
144
+ # @param end_headers [Boolean] indicates that no more headers will be sent
145
+ # @param end_stream [Boolean] indicates that no payload will be sent
146
+ def headers(headers, end_headers: true, end_stream: false)
147
+ flags = []
148
+ flags << :end_headers if end_headers
149
+ flags << :end_stream if end_stream
150
+
151
+ send(type: :headers, flags: flags, payload: headers.to_a)
152
+ end
153
+
154
+ def promise(headers, end_headers: true, &block)
155
+ fail ArgumentError, 'must provide callback' unless block_given?
156
+
157
+ flags = end_headers ? [:end_headers] : []
158
+ emit(:promise, self, headers, flags, &block)
159
+ end
160
+
161
+ # Sends a PRIORITY frame with new stream priority value (can only be
162
+ # performed by the client).
163
+ #
164
+ # @param weight [Integer] new stream weight value
165
+ # @param dependency [Integer] new stream dependency stream
166
+ def reprioritize(weight: 16, dependency: 0, exclusive: false)
167
+ stream_error if @id.even?
168
+ send(type: :priority, weight: weight, stream_dependency: dependency, exclusive: exclusive)
169
+ end
170
+
171
+ # Sends DATA frame containing response payload.
172
+ #
173
+ # @param payload [String]
174
+ # @param end_stream [Boolean] indicates last response DATA frame
175
+ def data(payload, end_stream: true)
176
+ # Split data according to each frame is smaller enough
177
+ # TODO: consider padding?
178
+ max_size = @connection.remote_settings[:settings_max_frame_size]
179
+ while payload.bytesize > max_size
180
+ chunk = payload.slice!(0, max_size)
181
+ send(type: :data, flags: [], payload: chunk)
182
+ end
183
+
184
+ flags = []
185
+ flags << :end_stream if end_stream
186
+ send(type: :data, flags: flags, payload: payload)
187
+ end
188
+
189
+ # Sends a RST_STREAM frame which closes current stream - this does not
190
+ # close the underlying connection.
191
+ #
192
+ # @param error [:Symbol] optional reason why stream was closed
193
+ def close(error = :stream_closed)
194
+ send(type: :rst_stream, error: error)
195
+ end
196
+
197
+ # Sends a RST_STREAM indicating that the stream is no longer needed.
198
+ def cancel
199
+ send(type: :rst_stream, error: :cancel)
200
+ end
201
+
202
+ # Sends a RST_STREAM indicating that the stream has been refused prior
203
+ # to performing any application processing.
204
+ def refuse
205
+ send(type: :rst_stream, error: :refused_stream)
206
+ end
207
+
208
+ private
209
+
210
+ # HTTP 2.0 Stream States
211
+ # - http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5.1
212
+ #
213
+ # +--------+
214
+ # send PP | | recv PP
215
+ # ,--------| idle |--------.
216
+ # / | | \
217
+ # v +--------+ v
218
+ # +----------+ | +----------+
219
+ # | | | send H/ | |
220
+ # ,-----| reserved | | recv H | reserved |-----.
221
+ # | | (local) | | | (remote) | |
222
+ # | +----------+ v +----------+ |
223
+ # | | +--------+ | |
224
+ # | | recv ES | | send ES | |
225
+ # | send H | ,-------| open |-------. | recv H |
226
+ # | | / | | \ | |
227
+ # | v v +--------+ v v |
228
+ # | +----------+ | +----------+ |
229
+ # | | half | | | half | |
230
+ # | | closed | | send R/ | closed | |
231
+ # | | (remote) | | recv R | (local) | |
232
+ # | +----------+ | +----------+ |
233
+ # | | | | |
234
+ # | | send ES/ | recv ES/ | |
235
+ # | | send R/ v send R/ | |
236
+ # | | recv R +--------+ recv R | |
237
+ # | send R/ `----------->| |<-----------' send R/ |
238
+ # | recv R | closed | recv R |
239
+ # `---------------------->| |<----------------------'
240
+ # +--------+
241
+ #
242
+ def transition(frame, sending)
243
+ case @state
244
+
245
+ # All streams start in the "idle" state. In this state, no frames
246
+ # have been exchanged.
247
+ # The following transitions are valid from this state:
248
+ # * Sending or receiving a HEADERS frame causes the stream to
249
+ # become "open". The stream identifier is selected as described
250
+ # in Section 5.1.1. The same HEADERS frame can also cause a
251
+ # stream to immediately become "half closed".
252
+ # * Sending a PUSH_PROMISE frame reserves an idle stream for later
253
+ # use. The stream state for the reserved stream transitions to
254
+ # "reserved (local)".
255
+ # * Receiving a PUSH_PROMISE frame reserves an idle stream for
256
+ # later use. The stream state for the reserved stream
257
+ # transitions to "reserved (remote)".
258
+ # Receiving any frames other than HEADERS, PUSH_PROMISE or PRIORITY
259
+ # on a stream in this state MUST be treated as a connection error
260
+ # (Section 5.4.1) of type PROTOCOL_ERROR.
261
+
262
+ when :idle
263
+ if sending
264
+ case frame[:type]
265
+ when :push_promise then event(:reserved_local)
266
+ when :headers
267
+ if end_stream?(frame)
268
+ event(:half_closed_local)
269
+ else
270
+ event(:open)
271
+ end
272
+ when :rst_stream then event(:local_rst)
273
+ when :priority then process_priority(frame)
274
+ else stream_error
275
+ end
276
+ else
277
+ case frame[:type]
278
+ when :push_promise then event(:reserved_remote)
279
+ when :headers
280
+ if end_stream?(frame)
281
+ event(:half_closed_remote)
282
+ else
283
+ event(:open)
284
+ end
285
+ when :priority then process_priority(frame)
286
+ else stream_error(:protocol_error)
287
+ end
288
+ end
289
+
290
+ # A stream in the "reserved (local)" state is one that has been
291
+ # promised by sending a PUSH_PROMISE frame. A PUSH_PROMISE frame
292
+ # reserves an idle stream by associating the stream with an open
293
+ # stream that was initiated by the remote peer (see Section 8.2).
294
+ # In this state, only the following transitions are possible:
295
+ # * The endpoint can send a HEADERS frame. This causes the stream
296
+ # to open in a "half closed (remote)" state.
297
+ # * Either endpoint can send a RST_STREAM frame to cause the stream
298
+ # to become "closed". This releases the stream reservation.
299
+ # An endpoint MUST NOT send any type of frame other than HEADERS,
300
+ # RST_STREAM, or PRIORITY in this state.
301
+ # A PRIORITY or WINDOW_UPDATE frame MAY be received in this state.
302
+ # Receiving any type of frame other than RST_STREAM, PRIORITY or
303
+ # WINDOW_UPDATE on a stream in this state MUST be treated as a
304
+ # connection error (Section 5.4.1) of type PROTOCOL_ERROR.
305
+ when :reserved_local
306
+ if sending
307
+ @state = case frame[:type]
308
+ when :headers then event(:half_closed_remote)
309
+ when :rst_stream then event(:local_rst)
310
+ else stream_error
311
+ end
312
+ else
313
+ @state = case frame[:type]
314
+ when :rst_stream then event(:remote_rst)
315
+ when :priority, :window_update then @state
316
+ else stream_error
317
+ end
318
+ end
319
+
320
+ # A stream in the "reserved (remote)" state has been reserved by a
321
+ # remote peer.
322
+ # In this state, only the following transitions are possible:
323
+ # * Receiving a HEADERS frame causes the stream to transition to
324
+ # "half closed (local)".
325
+ # * Either endpoint can send a RST_STREAM frame to cause the stream
326
+ # to become "closed". This releases the stream reservation.
327
+ # An endpoint MAY send a PRIORITY frame in this state to
328
+ # reprioritize the reserved stream. An endpoint MUST NOT send any
329
+ # type of frame other than RST_STREAM, WINDOW_UPDATE, or PRIORITY in
330
+ # this state.
331
+ # Receiving any type of frame other than HEADERS, RST_STREAM or
332
+ # PRIORITY on a stream in this state MUST be treated as a connection
333
+ # error (Section 5.4.1) of type PROTOCOL_ERROR.
334
+ when :reserved_remote
335
+ if sending
336
+ @state = case frame[:type]
337
+ when :rst_stream then event(:local_rst)
338
+ when :priority, :window_update then @state
339
+ else stream_error
340
+ end
341
+ else
342
+ @state = case frame[:type]
343
+ when :headers then event(:half_closed_local)
344
+ when :rst_stream then event(:remote_rst)
345
+ else stream_error
346
+ end
347
+ end
348
+
349
+ # A stream in the "open" state may be used by both peers to send
350
+ # frames of any type. In this state, sending peers observe
351
+ # advertised stream level flow control limits (Section 5.2).
352
+ # From this state either endpoint can send a frame with an
353
+ # END_STREAM flag set, which causes the stream to transition into
354
+ # one of the "half closed" states: an endpoint sending an END_STREAM
355
+ # flag causes the stream state to become "half closed (local)"; an
356
+ # endpoint receiving an END_STREAM flag causes the stream state to
357
+ # become "half closed (remote)".
358
+ # Either endpoint can send a RST_STREAM frame from this state,
359
+ # causing it to transition immediately to "closed".
360
+ when :open
361
+ if sending
362
+ case frame[:type]
363
+ when :data, :headers, :continuation
364
+ event(:half_closed_local) if end_stream?(frame)
365
+ when :rst_stream then event(:local_rst)
366
+ end
367
+ else
368
+ case frame[:type]
369
+ when :data, :headers, :continuation
370
+ event(:half_closed_remote) if end_stream?(frame)
371
+ when :rst_stream then event(:remote_rst)
372
+ end
373
+ end
374
+
375
+ # A stream that is in the "half closed (local)" state cannot be used
376
+ # for sending frames. Only WINDOW_UPDATE, PRIORITY and RST_STREAM
377
+ # frames can be sent in this state.
378
+ # A stream transitions from this state to "closed" when a frame that
379
+ # contains an END_STREAM flag is received, or when either peer sends
380
+ # a RST_STREAM frame.
381
+ # A receiver can ignore WINDOW_UPDATE frames in this state, which
382
+ # might arrive for a short period after a frame bearing the
383
+ # END_STREAM flag is sent.
384
+ # PRIORITY frames received in this state are used to reprioritize
385
+ # streams that depend on the current stream.
386
+ when :half_closed_local
387
+ if sending
388
+ case frame[:type]
389
+ when :rst_stream
390
+ event(:local_rst)
391
+ when :priority
392
+ process_priority(frame)
393
+ when :window_update
394
+ # nop here
395
+ else
396
+ stream_error
397
+ end
398
+ else
399
+ case frame[:type]
400
+ when :data, :headers, :continuation
401
+ event(:remote_closed) if end_stream?(frame)
402
+ when :rst_stream then event(:remote_rst)
403
+ when :priority
404
+ process_priority(frame)
405
+ when :window_update
406
+ # nop here
407
+ end
408
+ end
409
+
410
+ # A stream that is "half closed (remote)" is no longer being used by
411
+ # the peer to send frames. In this state, an endpoint is no longer
412
+ # obligated to maintain a receiver flow control window if it
413
+ # performs flow control.
414
+ # If an endpoint receives additional frames for a stream that is in
415
+ # this state, other than WINDOW_UPDATE, PRIORITY or RST_STREAM, it
416
+ # MUST respond with a stream error (Section 5.4.2) of type
417
+ # STREAM_CLOSED.
418
+ # A stream that is "half closed (remote)" can be used by the
419
+ # endpoint to send frames of any type. In this state, the endpoint
420
+ # continues to observe advertised stream level flow control limits
421
+ # (Section 5.2).
422
+ # A stream can transition from this state to "closed" by sending a
423
+ # frame that contains an END_STREAM flag, or when either peer sends
424
+ # a RST_STREAM frame.
425
+ when :half_closed_remote
426
+ if sending
427
+ case frame[:type]
428
+ when :data, :headers, :continuation
429
+ event(:local_closed) if end_stream?(frame)
430
+ when :rst_stream then event(:local_rst)
431
+ end
432
+ else
433
+ case frame[:type]
434
+ when :rst_stream then event(:remote_rst)
435
+ when :priority
436
+ process_priority(frame)
437
+ when :window_update
438
+ # nop
439
+ else
440
+ stream_error(:stream_closed)
441
+ end
442
+ end
443
+
444
+ # The "closed" state is the terminal state.
445
+ # An endpoint MUST NOT send frames other than PRIORITY on a closed
446
+ # stream. An endpoint that receives any frame other than PRIORITY
447
+ # after receiving a RST_STREAM MUST treat that as a stream error
448
+ # (Section 5.4.2) of type STREAM_CLOSED. Similarly, an endpoint
449
+ # that receives any frames after receiving a frame with the
450
+ # END_STREAM flag set MUST treat that as a connection error
451
+ # (Section 5.4.1) of type STREAM_CLOSED, unless the frame is
452
+ # permitted as described below.
453
+ # WINDOW_UPDATE or RST_STREAM frames can be received in this state
454
+ # for a short period after a DATA or HEADERS frame containing an
455
+ # END_STREAM flag is sent. Until the remote peer receives and
456
+ # processes RST_STREAM or the frame bearing the END_STREAM flag, it
457
+ # might send frames of these types. Endpoints MUST ignore
458
+ # WINDOW_UPDATE or RST_STREAM frames received in this state, though
459
+ # endpoints MAY choose to treat frames that arrive a significant
460
+ # time after sending END_STREAM as a connection error
461
+ # (Section 5.4.1) of type PROTOCOL_ERROR.
462
+ # PRIORITY frames can be sent on closed streams to prioritize
463
+ # streams that are dependent on the closed stream. Endpoints SHOULD
464
+ # process PRIORITY frames, though they can be ignored if the stream
465
+ # has been removed from the dependency tree (see Section 5.3.4).
466
+ # If this state is reached as a result of sending a RST_STREAM
467
+ # frame, the peer that receives the RST_STREAM might have already
468
+ # sent - or enqueued for sending - frames on the stream that cannot
469
+ # be withdrawn. An endpoint MUST ignore frames that it receives on
470
+ # closed streams after it has sent a RST_STREAM frame. An endpoint
471
+ # MAY choose to limit the period over which it ignores frames and
472
+ # treat frames that arrive after this time as being in error.
473
+ # Flow controlled frames (i.e., DATA) received after sending
474
+ # RST_STREAM are counted toward the connection flow control window.
475
+ # Even though these frames might be ignored, because they are sent
476
+ # before the sender receives the RST_STREAM, the sender will
477
+ # consider the frames to count against the flow control window.
478
+ # An endpoint might receive a PUSH_PROMISE frame after it sends
479
+ # RST_STREAM. PUSH_PROMISE causes a stream to become "reserved"
480
+ # even if the associated stream has been reset. Therefore, a
481
+ # RST_STREAM is needed to close an unwanted promised stream.
482
+ when :closed
483
+ if sending
484
+ case frame[:type]
485
+ when :rst_stream then # ignore
486
+ when :priority then
487
+ process_priority(frame)
488
+ else
489
+ stream_error(:stream_closed) unless (frame[:type] == :rst_stream)
490
+ end
491
+ else
492
+ if frame[:type] == :priority
493
+ process_priority(frame)
494
+ else
495
+ case @closed
496
+ when :remote_rst, :remote_closed
497
+ case frame[:type]
498
+ when :rst_stream, :window_update # nop here
499
+ else
500
+ stream_error(:stream_closed)
501
+ end
502
+ when :local_rst, :local_closed
503
+ frame[:ignore] = true if frame[:type] != :window_update
504
+ end
505
+ end
506
+ end
507
+ end
508
+ end
509
+
510
+ def event(newstate)
511
+ case newstate
512
+ when :open
513
+ @state = newstate
514
+ emit(:active)
515
+
516
+ when :reserved_local, :reserved_remote
517
+ @state = newstate
518
+ emit(:reserved)
519
+
520
+ when :half_closed_local, :half_closed_remote
521
+ @closed = newstate
522
+ emit(:active) unless @state == :open
523
+ @state = :half_closing
524
+
525
+ when :local_closed, :remote_closed, :local_rst, :remote_rst
526
+ @closed = newstate
527
+ @state = :closing
528
+ end
529
+
530
+ @state
531
+ end
532
+
533
+ def complete_transition(frame)
534
+ case @state
535
+ when :closing
536
+ @state = :closed
537
+ emit(:close, frame[:error])
538
+ when :half_closing
539
+ @state = @closed
540
+ emit(:half_close)
541
+ end
542
+ end
543
+
544
+ def process_priority(frame)
545
+ @weight = frame[:weight]
546
+ @dependency = frame[:stream_dependency]
547
+ emit(
548
+ :priority,
549
+ weight: frame[:weight],
550
+ dependency: frame[:stream_dependency],
551
+ exclusive: frame[:exclusive],
552
+ )
553
+ # TODO: implement dependency tree housekeeping
554
+ # Latest draft defines a fairly complex priority control.
555
+ # See https://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5.3
556
+ # We currently have no prioritization among streams.
557
+ # We should add code here.
558
+ end
559
+
560
+ def end_stream?(frame)
561
+ case frame[:type]
562
+ when :data, :headers, :continuation
563
+ frame[:flags].include?(:end_stream)
564
+ else false
565
+ end
566
+ end
567
+
568
+ def stream_error(error = :internal_error, msg: nil)
569
+ @error = error
570
+ close(error) if @state != :closed
571
+
572
+ klass = error.to_s.split('_').map(&:capitalize).join
573
+ fail Error.const_get(klass), msg
574
+ end
575
+ end
576
+ end