mieps_http-2 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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