http-2-next 0.1.0

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