raptor 0.20.2 → 0.21.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +28 -17
- data/docs/raptor-vs-puma.md +15 -9
- data/ext/raptor_http2/raptor_http2.c +12 -0
- data/lib/raptor/binder.rb +39 -13
- data/lib/raptor/cli.rb +10 -0
- data/lib/raptor/cluster.rb +5 -3
- data/lib/raptor/http.rb +18 -0
- data/lib/raptor/http1.rb +2 -20
- data/lib/raptor/http2.rb +406 -79
- data/lib/raptor/reactor.rb +142 -23
- data/lib/raptor/server.rb +12 -0
- data/lib/raptor/version.rb +1 -1
- data/sig/generated/raptor/binder.rbs +26 -13
- data/sig/generated/raptor/http.rbs +12 -0
- data/sig/generated/raptor/http1.rbs +0 -12
- data/sig/generated/raptor/http2.rbs +202 -15
- data/sig/generated/raptor/reactor.rbs +70 -8
- metadata +2 -2
data/lib/raptor/http2.rb
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
require "stringio"
|
|
5
5
|
|
|
6
6
|
require "atomic-ruby/atom"
|
|
7
|
+
require "atomic-ruby/atomic_boolean"
|
|
8
|
+
require "atomic-ruby/atomic_condition_variable"
|
|
7
9
|
require "rack"
|
|
8
10
|
|
|
9
11
|
require_relative "http"
|
|
@@ -14,6 +16,8 @@ module Raptor
|
|
|
14
16
|
# Handles HTTP/2 request processing and Rack application integration.
|
|
15
17
|
#
|
|
16
18
|
class Http2
|
|
19
|
+
RESPONSE_TRAILERS = "raptor.response_trailers"
|
|
20
|
+
|
|
17
21
|
# Serialises concurrent frame writes on a single HTTP/2 connection so
|
|
18
22
|
# exactly one thread is writing at any moment.
|
|
19
23
|
#
|
|
@@ -72,15 +76,20 @@ module Raptor
|
|
|
72
76
|
end
|
|
73
77
|
end
|
|
74
78
|
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
class StreamClosedError < StandardError
|
|
80
|
+
# @rbs () -> String
|
|
81
|
+
def message = "HTTP/2 stream closed"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Coordinates outbound flow control and stream cancellation without
|
|
85
|
+
# polling or mutexes.
|
|
77
86
|
#
|
|
78
87
|
class FlowControl
|
|
79
|
-
ACQUIRE_POLL_INTERVAL = 0.001
|
|
80
|
-
|
|
81
88
|
# @rbs @connection_window: Atom
|
|
82
89
|
# @rbs @stream_windows: Atom
|
|
83
90
|
# @rbs @initial_stream_window: Atom
|
|
91
|
+
# @rbs @cancelled_streams: Atom
|
|
92
|
+
# @rbs @available: AtomicConditionVariable
|
|
84
93
|
|
|
85
94
|
# Creates a new FlowControl with the spec-default windows.
|
|
86
95
|
#
|
|
@@ -91,32 +100,25 @@ module Raptor
|
|
|
91
100
|
@connection_window = Atom.new(DEFAULT_WINDOW_SIZE)
|
|
92
101
|
@stream_windows = Atom.new({})
|
|
93
102
|
@initial_stream_window = Atom.new(DEFAULT_WINDOW_SIZE)
|
|
103
|
+
@cancelled_streams = Atom.new({})
|
|
104
|
+
@available = AtomicConditionVariable.new
|
|
94
105
|
end
|
|
95
106
|
|
|
96
|
-
# Reserves outbound capacity on the given stream,
|
|
107
|
+
# Reserves outbound capacity on the given stream, waiting until at
|
|
97
108
|
# least one byte is available on both the connection and stream
|
|
98
|
-
# windows.
|
|
109
|
+
# windows. Raises when the stream or connection closes.
|
|
99
110
|
#
|
|
100
111
|
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
101
112
|
# @param max_bytes [Integer] the largest size the caller would like to send
|
|
102
|
-
# @param end_stream [Boolean] true when this is the final frame on the stream
|
|
103
113
|
# @return [Integer] the number of bytes the caller may now send
|
|
104
114
|
#
|
|
105
|
-
# @rbs (Integer stream_id, Integer max_bytes
|
|
106
|
-
def acquire(stream_id, max_bytes
|
|
115
|
+
# @rbs (Integer stream_id, Integer max_bytes) -> Integer
|
|
116
|
+
def acquire(stream_id, max_bytes)
|
|
107
117
|
initial = @initial_stream_window.value
|
|
108
118
|
capped = max_bytes < MAX_FRAME_SIZE ? max_bytes : MAX_FRAME_SIZE
|
|
109
119
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
granted = reserve_connection(capped)
|
|
113
|
-
return granted if granted.positive?
|
|
114
|
-
|
|
115
|
-
sleep ACQUIRE_POLL_INTERVAL
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
loop do
|
|
120
|
+
@available.wait do
|
|
121
|
+
check(stream_id)
|
|
120
122
|
stream_window = @stream_windows.value[stream_id] || initial
|
|
121
123
|
capped_full = capped < stream_window ? capped : stream_window
|
|
122
124
|
granted = capped_full.positive? ? reserve_connection(capped_full) : 0
|
|
@@ -126,10 +128,8 @@ module Raptor
|
|
|
126
128
|
current = windows[stream_id] || initial
|
|
127
129
|
windows.merge(stream_id => current - granted)
|
|
128
130
|
end
|
|
129
|
-
|
|
131
|
+
granted
|
|
130
132
|
end
|
|
131
|
-
|
|
132
|
-
sleep ACQUIRE_POLL_INTERVAL
|
|
133
133
|
end
|
|
134
134
|
end
|
|
135
135
|
|
|
@@ -141,6 +141,7 @@ module Raptor
|
|
|
141
141
|
# @rbs (Integer increment) -> void
|
|
142
142
|
def add_connection_window(increment)
|
|
143
143
|
@connection_window.swap { |window| window + increment }
|
|
144
|
+
@available.broadcast
|
|
144
145
|
end
|
|
145
146
|
|
|
146
147
|
# Increments the send window for the given stream by `increment` bytes.
|
|
@@ -156,6 +157,7 @@ module Raptor
|
|
|
156
157
|
current = windows[stream_id] || initial
|
|
157
158
|
windows.merge(stream_id => current + increment)
|
|
158
159
|
end
|
|
160
|
+
@available.broadcast
|
|
159
161
|
end
|
|
160
162
|
|
|
161
163
|
# Updates the peer's `SETTINGS_INITIAL_WINDOW_SIZE`. Shifts every
|
|
@@ -174,6 +176,40 @@ module Raptor
|
|
|
174
176
|
@stream_windows.swap do |windows|
|
|
175
177
|
windows.transform_values { |size| size + delta }
|
|
176
178
|
end
|
|
179
|
+
@available.broadcast
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Raises when the given stream can no longer write a response.
|
|
183
|
+
#
|
|
184
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
185
|
+
# @return [void]
|
|
186
|
+
# @raise [StreamClosedError] if the stream or connection has closed
|
|
187
|
+
#
|
|
188
|
+
# @rbs (Integer stream_id) -> void
|
|
189
|
+
def check(stream_id)
|
|
190
|
+
cancelled = @cancelled_streams.value
|
|
191
|
+
raise StreamClosedError if cancelled.key?(0) || cancelled.key?(stream_id)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Cancels pending and future writes for the given stream.
|
|
195
|
+
#
|
|
196
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
197
|
+
# @return [void]
|
|
198
|
+
#
|
|
199
|
+
# @rbs (Integer stream_id) -> void
|
|
200
|
+
def cancel_stream(stream_id)
|
|
201
|
+
@cancelled_streams.swap { |streams| streams.merge(stream_id => true) }
|
|
202
|
+
@available.broadcast
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Cancels pending and future writes for the connection.
|
|
206
|
+
#
|
|
207
|
+
# @return [void]
|
|
208
|
+
#
|
|
209
|
+
# @rbs () -> void
|
|
210
|
+
def close
|
|
211
|
+
@cancelled_streams.swap { |streams| streams.merge(0 => true) }
|
|
212
|
+
@available.broadcast
|
|
177
213
|
end
|
|
178
214
|
|
|
179
215
|
# Discards any per-stream tracking for the given stream.
|
|
@@ -183,8 +219,6 @@ module Raptor
|
|
|
183
219
|
#
|
|
184
220
|
# @rbs (Integer stream_id) -> void
|
|
185
221
|
def discard_stream(stream_id)
|
|
186
|
-
return unless @stream_windows.value.key?(stream_id)
|
|
187
|
-
|
|
188
222
|
@stream_windows.swap do |windows|
|
|
189
223
|
next windows unless windows.key?(stream_id)
|
|
190
224
|
|
|
@@ -192,6 +226,13 @@ module Raptor
|
|
|
192
226
|
pruned.delete(stream_id)
|
|
193
227
|
pruned
|
|
194
228
|
end
|
|
229
|
+
@cancelled_streams.swap do |streams|
|
|
230
|
+
next streams unless streams.key?(stream_id)
|
|
231
|
+
|
|
232
|
+
pruned = streams.dup
|
|
233
|
+
pruned.delete(stream_id)
|
|
234
|
+
pruned
|
|
235
|
+
end
|
|
195
236
|
end
|
|
196
237
|
|
|
197
238
|
private
|
|
@@ -214,6 +255,142 @@ module Raptor
|
|
|
214
255
|
end
|
|
215
256
|
end
|
|
216
257
|
|
|
258
|
+
# Adapts one HTTP/2 response stream to Rack's streaming body interface.
|
|
259
|
+
#
|
|
260
|
+
class ResponseStream
|
|
261
|
+
# @rbs @socket: OpenSSL::SSL::SSLSocket
|
|
262
|
+
# @rbs @writer: Writer
|
|
263
|
+
# @rbs @flow_control: FlowControl
|
|
264
|
+
# @rbs @stream_id: Integer
|
|
265
|
+
# @rbs @trailers: Hash[String, String | Array[String]]
|
|
266
|
+
# @rbs @parser: Http2Parser
|
|
267
|
+
# @rbs @bytes_written: Integer
|
|
268
|
+
# @rbs @closed: bool
|
|
269
|
+
|
|
270
|
+
# Returns the number of response body bytes written to the stream.
|
|
271
|
+
#
|
|
272
|
+
# @return [Integer]
|
|
273
|
+
attr_reader :bytes_written #: Integer
|
|
274
|
+
|
|
275
|
+
# Creates a response stream for a Rack streaming body.
|
|
276
|
+
#
|
|
277
|
+
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
278
|
+
# @param writer [Writer] frame writer for the connection
|
|
279
|
+
# @param flow_control [FlowControl] outbound flow controller
|
|
280
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
281
|
+
# @param trailers [Hash] trailing response headers populated by the Rack application
|
|
282
|
+
# @return [void]
|
|
283
|
+
#
|
|
284
|
+
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Hash[String, String | Array[String]] trailers) -> void
|
|
285
|
+
def initialize(socket, writer, flow_control, stream_id, trailers)
|
|
286
|
+
@socket = socket
|
|
287
|
+
@writer = writer
|
|
288
|
+
@flow_control = flow_control
|
|
289
|
+
@stream_id = stream_id
|
|
290
|
+
@trailers = trailers
|
|
291
|
+
@parser = Http2Parser.new
|
|
292
|
+
@bytes_written = 0
|
|
293
|
+
@closed = false
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# Streaming responses are write-only.
|
|
297
|
+
#
|
|
298
|
+
# @raise [IOError]
|
|
299
|
+
#
|
|
300
|
+
# @rbs (*untyped) -> bot
|
|
301
|
+
def read(*)
|
|
302
|
+
raise IOError, "not opened for reading"
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Writes response body bytes to the stream.
|
|
306
|
+
#
|
|
307
|
+
# @param chunk [String] response body bytes
|
|
308
|
+
# @return [Integer] number of bytes written
|
|
309
|
+
#
|
|
310
|
+
# @rbs (String chunk) -> Integer
|
|
311
|
+
def write(chunk)
|
|
312
|
+
raise IOError, "closed stream" if @closed
|
|
313
|
+
raise TypeError, "body must yield String values" unless chunk.is_a?(String)
|
|
314
|
+
|
|
315
|
+
offset = 0
|
|
316
|
+
while offset < chunk.bytesize
|
|
317
|
+
granted = @flow_control.acquire(@stream_id, chunk.bytesize - offset)
|
|
318
|
+
slice = offset.zero? && granted == chunk.bytesize ? chunk : chunk.byteslice(offset, granted)
|
|
319
|
+
offset += granted
|
|
320
|
+
@writer.write_frames(@socket, [@parser.build_frame(:data, 0, @stream_id, slice)])
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
@bytes_written += chunk.bytesize
|
|
324
|
+
chunk.bytesize
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# Writes response body bytes and returns the stream.
|
|
328
|
+
#
|
|
329
|
+
# @param chunk [String] response body bytes
|
|
330
|
+
# @return [ResponseStream]
|
|
331
|
+
#
|
|
332
|
+
# @rbs (String chunk) -> ResponseStream
|
|
333
|
+
def <<(chunk)
|
|
334
|
+
write(chunk)
|
|
335
|
+
self
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# Flushes the stream. Frame writes are already immediate.
|
|
339
|
+
#
|
|
340
|
+
# @return [ResponseStream]
|
|
341
|
+
#
|
|
342
|
+
# @rbs () -> ResponseStream
|
|
343
|
+
def flush
|
|
344
|
+
self
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# Finishes the stream.
|
|
348
|
+
#
|
|
349
|
+
# @return [void]
|
|
350
|
+
#
|
|
351
|
+
# @rbs () -> void
|
|
352
|
+
def close
|
|
353
|
+
return if @closed
|
|
354
|
+
|
|
355
|
+
@flow_control.check(@stream_id)
|
|
356
|
+
@closed = true
|
|
357
|
+
if @trailers.empty?
|
|
358
|
+
frame = @parser.build_frame(:data, FLAG_END_STREAM, @stream_id, nil)
|
|
359
|
+
else
|
|
360
|
+
encoded = @parser.encode_response_trailers(@trailers)
|
|
361
|
+
frame = @parser.build_frame(:headers, FLAG_END_STREAM | FLAG_END_HEADERS, @stream_id, encoded)
|
|
362
|
+
end
|
|
363
|
+
@writer.write_frames(@socket, [frame])
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
# Closes the unsupported read side of the stream.
|
|
367
|
+
#
|
|
368
|
+
# @raise [IOError]
|
|
369
|
+
#
|
|
370
|
+
# @rbs () -> bot
|
|
371
|
+
def close_read
|
|
372
|
+
raise IOError, "closing non-duplex IO for reading"
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# Finishes the writable side of the stream.
|
|
376
|
+
#
|
|
377
|
+
# @return [void]
|
|
378
|
+
#
|
|
379
|
+
# @rbs () -> void
|
|
380
|
+
def close_write
|
|
381
|
+
close
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# Returns whether the stream has finished.
|
|
385
|
+
#
|
|
386
|
+
# @return [Boolean]
|
|
387
|
+
#
|
|
388
|
+
# @rbs () -> bool
|
|
389
|
+
def closed?
|
|
390
|
+
@closed
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
|
|
217
394
|
EAGER_READ_TIMEOUT = 0.001
|
|
218
395
|
EAGER_READ_BUFFER_SIZE = 64 * 1024
|
|
219
396
|
EAGER_MAX_ROUNDS = 8
|
|
@@ -225,6 +402,8 @@ module Raptor
|
|
|
225
402
|
|
|
226
403
|
ERROR_NO_ERROR = 0x0
|
|
227
404
|
ERROR_PROTOCOL_ERROR = 0x1
|
|
405
|
+
ERROR_INTERNAL_ERROR = 0x2
|
|
406
|
+
ERROR_REFUSED_STREAM = 0x7
|
|
228
407
|
|
|
229
408
|
DEFAULT_WINDOW_SIZE = 65_535
|
|
230
409
|
MAX_FRAME_SIZE = 16_384
|
|
@@ -272,6 +451,7 @@ module Raptor
|
|
|
272
451
|
# @rbs @clean_fiber_locals: bool
|
|
273
452
|
# @rbs @on_error: ^(Hash[String, untyped]?, Exception) -> void | nil
|
|
274
453
|
# @rbs @initial_settings_frame: String
|
|
454
|
+
# @rbs @running: AtomicBoolean
|
|
275
455
|
|
|
276
456
|
# Returns the initial server SETTINGS frame to send on every new
|
|
277
457
|
# HTTP/2 connection.
|
|
@@ -302,6 +482,7 @@ module Raptor
|
|
|
302
482
|
@clean_thread_locals = clean_thread_locals
|
|
303
483
|
@clean_fiber_locals = clean_fiber_locals
|
|
304
484
|
@on_error = on_error
|
|
485
|
+
@running = AtomicBoolean.new(true)
|
|
305
486
|
|
|
306
487
|
parser = Http2Parser.new
|
|
307
488
|
settings_payload = parser.build_settings(
|
|
@@ -320,6 +501,22 @@ module Raptor
|
|
|
320
501
|
Writer.new(write_timeout: @write_timeout)
|
|
321
502
|
end
|
|
322
503
|
|
|
504
|
+
# Stops new stream dispatch and asks clients to finish streams already
|
|
505
|
+
# handed to the application.
|
|
506
|
+
#
|
|
507
|
+
# @param reactor [Reactor] the reactor managing HTTP/2 connections
|
|
508
|
+
# @return [void]
|
|
509
|
+
#
|
|
510
|
+
# @rbs (Reactor reactor) -> void
|
|
511
|
+
def shutdown(reactor)
|
|
512
|
+
@running.make_false
|
|
513
|
+
parser = Http2Parser.new
|
|
514
|
+
reactor.drain_http2 do |stream_id|
|
|
515
|
+
payload = [stream_id, ERROR_NO_ERROR].pack("NN")
|
|
516
|
+
parser.build_frame(:goaway, 0, 0, payload)
|
|
517
|
+
end
|
|
518
|
+
end
|
|
519
|
+
|
|
323
520
|
# Returns a Ractor-safe proc that parses HTTP/2 frames from the
|
|
324
521
|
# state hash's buffered bytes.
|
|
325
522
|
#
|
|
@@ -347,7 +544,9 @@ module Raptor
|
|
|
347
544
|
streams = data[:http2_streams] ? data[:http2_streams].dup : {}
|
|
348
545
|
outgoing_frames = []
|
|
349
546
|
completed_requests = []
|
|
547
|
+
cancelled_streams = []
|
|
350
548
|
window_updates = []
|
|
549
|
+
ping_acknowledgements = []
|
|
351
550
|
peer_initial_window_size = nil
|
|
352
551
|
connection_window = data[:http2_window] || DEFAULT_WINDOW_SIZE
|
|
353
552
|
preface_received = data[:http2_preface_received] || false
|
|
@@ -360,7 +559,7 @@ module Raptor
|
|
|
360
559
|
buffer = buffer.byteslice(24..-1) || ""
|
|
361
560
|
preface_received = true
|
|
362
561
|
else
|
|
363
|
-
return build_result(data, buffer, hpack_table, streams, outgoing_frames, completed_requests, window_updates, peer_initial_window_size, connection_window, preface_received, last_client_stream_id, pending_headers, false)
|
|
562
|
+
return build_result(data, buffer, hpack_table, streams, outgoing_frames, completed_requests, cancelled_streams, window_updates, ping_acknowledgements, peer_initial_window_size, connection_window, preface_received, last_client_stream_id, pending_headers, false)
|
|
364
563
|
end
|
|
365
564
|
end
|
|
366
565
|
|
|
@@ -387,8 +586,9 @@ module Raptor
|
|
|
387
586
|
when :headers
|
|
388
587
|
stream_id = frame[:stream_id]
|
|
389
588
|
header_payload = frame[:payload]
|
|
589
|
+
trailers = streams.key?(stream_id)
|
|
390
590
|
|
|
391
|
-
unless
|
|
591
|
+
unless trailers
|
|
392
592
|
if stream_id.even? || stream_id <= last_client_stream_id
|
|
393
593
|
goaway_error = ERROR_PROTOCOL_ERROR
|
|
394
594
|
break
|
|
@@ -404,14 +604,12 @@ module Raptor
|
|
|
404
604
|
|
|
405
605
|
if frame[:flags].anybits?(FLAG_END_HEADERS)
|
|
406
606
|
decoded_headers, hpack_table = parser.parse_headers(header_payload, hpack_table)
|
|
407
|
-
|
|
408
|
-
streams
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
streams, completed_requests = finalize_headers(streams, completed_requests, stream_id, decoded_headers, end_stream)
|
|
412
|
-
end
|
|
607
|
+
streams, completed_requests = complete_header_block(
|
|
608
|
+
streams, completed_requests, outgoing_frames, parser,
|
|
609
|
+
stream_id, decoded_headers, end_stream, trailers
|
|
610
|
+
)
|
|
413
611
|
else
|
|
414
|
-
pending_headers = { stream_id: stream_id, buffer: header_payload, end_stream: end_stream }
|
|
612
|
+
pending_headers = { stream_id: stream_id, buffer: header_payload, end_stream: end_stream, trailers: trailers }
|
|
415
613
|
end
|
|
416
614
|
|
|
417
615
|
when :continuation
|
|
@@ -425,12 +623,10 @@ module Raptor
|
|
|
425
623
|
if frame[:flags].anybits?(FLAG_END_HEADERS)
|
|
426
624
|
stream_id = pending_headers[:stream_id]
|
|
427
625
|
decoded_headers, hpack_table = parser.parse_headers(pending_headers[:buffer], hpack_table)
|
|
428
|
-
|
|
429
|
-
streams
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
streams, completed_requests = finalize_headers(streams, completed_requests, stream_id, decoded_headers, pending_headers[:end_stream])
|
|
433
|
-
end
|
|
626
|
+
streams, completed_requests = complete_header_block(
|
|
627
|
+
streams, completed_requests, outgoing_frames, parser,
|
|
628
|
+
stream_id, decoded_headers, pending_headers[:end_stream], pending_headers[:trailers]
|
|
629
|
+
)
|
|
434
630
|
pending_headers = nil
|
|
435
631
|
end
|
|
436
632
|
|
|
@@ -475,7 +671,9 @@ module Raptor
|
|
|
475
671
|
window_updates << [frame[:stream_id], increment]
|
|
476
672
|
|
|
477
673
|
when :ping
|
|
478
|
-
if frame[:flags].
|
|
674
|
+
if frame[:flags].anybits?(FLAG_ACK)
|
|
675
|
+
ping_acknowledgements << frame[:payload]
|
|
676
|
+
else
|
|
479
677
|
outgoing_frames << parser.build_frame(:ping, FLAG_ACK, 0, frame[:payload])
|
|
480
678
|
end
|
|
481
679
|
|
|
@@ -483,7 +681,10 @@ module Raptor
|
|
|
483
681
|
break
|
|
484
682
|
|
|
485
683
|
when :rst_stream
|
|
486
|
-
|
|
684
|
+
stream_id = frame[:stream_id]
|
|
685
|
+
streams.delete(stream_id)
|
|
686
|
+
completed_requests.reject! { |request| request[:stream_id] == stream_id }
|
|
687
|
+
cancelled_streams << stream_id
|
|
487
688
|
end
|
|
488
689
|
end
|
|
489
690
|
|
|
@@ -492,8 +693,47 @@ module Raptor
|
|
|
492
693
|
outgoing_frames << parser.build_frame(:goaway, 0, 0, goaway_payload)
|
|
493
694
|
end
|
|
494
695
|
|
|
495
|
-
build_result(data, buffer, hpack_table, streams, outgoing_frames, completed_requests, window_updates, peer_initial_window_size, connection_window, preface_received, last_client_stream_id, pending_headers, !!goaway_error)
|
|
696
|
+
build_result(data, buffer, hpack_table, streams, outgoing_frames, completed_requests, cancelled_streams, window_updates, ping_acknowledgements, peer_initial_window_size, connection_window, preface_received, last_client_stream_id, pending_headers, !!goaway_error)
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
# Applies one complete request header block to its stream, resetting
|
|
700
|
+
# malformed initial headers or trailers and completing valid trailers.
|
|
701
|
+
#
|
|
702
|
+
# @param streams [Hash] current open-stream map
|
|
703
|
+
# @param completed_requests [Array<Hash>] accumulator of completed stream requests
|
|
704
|
+
# @param outgoing_frames [Array<String>] accumulator of protocol response frames
|
|
705
|
+
# @param parser [Http2Parser] frame parser and encoder
|
|
706
|
+
# @param stream_id [Integer] the stream identifier
|
|
707
|
+
# @param decoded_headers [Array<Array(String, String)>] decoded header pairs
|
|
708
|
+
# @param end_stream [Boolean] whether the source frame had END_STREAM set
|
|
709
|
+
# @param trailers [Boolean] whether this is a trailing header block
|
|
710
|
+
# @return [Array(Hash, Array<Hash>)] updated streams and completed requests
|
|
711
|
+
#
|
|
712
|
+
# @rbs (Hash[Integer, Hash[Symbol, untyped]] streams, Array[Hash[Symbol, untyped]] completed_requests, Array[String] outgoing_frames, Http2Parser parser, Integer stream_id, Array[[String, String]] decoded_headers, bool end_stream, bool trailers) -> [Hash[Integer, Hash[Symbol, untyped]], Array[Hash[Symbol, untyped]]]
|
|
713
|
+
def self.complete_header_block(streams, completed_requests, outgoing_frames, parser, stream_id, decoded_headers, end_stream, trailers)
|
|
714
|
+
invalid = if trailers
|
|
715
|
+
!end_stream || decoded_headers.any? { |name, _value| name.start_with?(":") }
|
|
716
|
+
else
|
|
717
|
+
invalid_pseudo_headers?(decoded_headers)
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
if invalid
|
|
721
|
+
streams.delete(stream_id)
|
|
722
|
+
outgoing_frames << parser.build_frame(:rst_stream, 0, stream_id, [ERROR_PROTOCOL_ERROR].pack("N"))
|
|
723
|
+
elsif trailers
|
|
724
|
+
stream = streams.delete(stream_id)
|
|
725
|
+
completed_requests << {
|
|
726
|
+
stream_id: stream_id,
|
|
727
|
+
headers: stream[:headers],
|
|
728
|
+
body: stream[:body] || ""
|
|
729
|
+
}
|
|
730
|
+
else
|
|
731
|
+
streams, completed_requests = finalize_headers(streams, completed_requests, stream_id, decoded_headers, end_stream)
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
[streams, completed_requests]
|
|
496
735
|
end
|
|
736
|
+
private_class_method :complete_header_block
|
|
497
737
|
|
|
498
738
|
# Merges a decoded header block into the stream's accumulated state,
|
|
499
739
|
# promoting the stream to `completed_requests` when END_STREAM is set.
|
|
@@ -534,7 +774,9 @@ module Raptor
|
|
|
534
774
|
# @param streams [Hash] updated stream states
|
|
535
775
|
# @param outgoing_frames [Array<String>] frames to write to the socket
|
|
536
776
|
# @param completed_requests [Array<Hash>] fully received stream requests
|
|
777
|
+
# @param cancelled_streams [Array<Integer>] streams reset by the peer
|
|
537
778
|
# @param window_updates [Array<Array(Integer, Integer)>] inbound WINDOW_UPDATE pairs as [stream_id, increment]
|
|
779
|
+
# @param ping_acknowledgements [Array<String>] acknowledged PING payloads
|
|
538
780
|
# @param peer_initial_window_size [Integer, nil] new SETTINGS_INITIAL_WINDOW_SIZE announced by the peer
|
|
539
781
|
# @param connection_window [Integer] current connection flow control window
|
|
540
782
|
# @param preface_received [Boolean] whether the connection preface has been received
|
|
@@ -543,8 +785,8 @@ module Raptor
|
|
|
543
785
|
# @param close_connection [Boolean] whether the connection should be closed after writing outgoing frames
|
|
544
786
|
# @return [Hash] frozen result hash
|
|
545
787
|
#
|
|
546
|
-
# @rbs (Hash[Symbol, untyped] data, String buffer, Array[untyped] hpack_table, Hash[Integer, Hash[Symbol, untyped]] streams, Array[String] outgoing_frames, Array[Hash[Symbol, untyped]] completed_requests, Array[[Integer, Integer]] window_updates, Integer? peer_initial_window_size, Integer connection_window, bool preface_received, Integer last_client_stream_id, Hash[Symbol, untyped]? pending_headers, bool close_connection) -> Hash[Symbol, untyped]
|
|
547
|
-
def self.build_result(data, buffer, hpack_table, streams, outgoing_frames, completed_requests, window_updates, peer_initial_window_size, connection_window, preface_received, last_client_stream_id, pending_headers, close_connection)
|
|
788
|
+
# @rbs (Hash[Symbol, untyped] data, String buffer, Array[untyped] hpack_table, Hash[Integer, Hash[Symbol, untyped]] streams, Array[String] outgoing_frames, Array[Hash[Symbol, untyped]] completed_requests, Array[Integer] cancelled_streams, Array[[Integer, Integer]] window_updates, Array[String] ping_acknowledgements, Integer? peer_initial_window_size, Integer connection_window, bool preface_received, Integer last_client_stream_id, Hash[Symbol, untyped]? pending_headers, bool close_connection) -> Hash[Symbol, untyped]
|
|
789
|
+
def self.build_result(data, buffer, hpack_table, streams, outgoing_frames, completed_requests, cancelled_streams, window_updates, ping_acknowledgements, peer_initial_window_size, connection_window, preface_received, last_client_stream_id, pending_headers, close_connection)
|
|
548
790
|
result = {
|
|
549
791
|
id: data[:id],
|
|
550
792
|
protocol: :http2,
|
|
@@ -561,7 +803,9 @@ module Raptor
|
|
|
561
803
|
remote_addr: data[:remote_addr],
|
|
562
804
|
url_scheme: data[:url_scheme]
|
|
563
805
|
}
|
|
806
|
+
result[:cancelled_streams] = cancelled_streams unless cancelled_streams.empty?
|
|
564
807
|
result[:window_updates] = window_updates unless window_updates.empty?
|
|
808
|
+
result[:ping_acknowledgements] = ping_acknowledgements unless ping_acknowledgements.empty?
|
|
565
809
|
result[:peer_initial_window_size] = peer_initial_window_size if peer_initial_window_size
|
|
566
810
|
Ractor.make_shareable(result)
|
|
567
811
|
end
|
|
@@ -582,8 +826,15 @@ module Raptor
|
|
|
582
826
|
#
|
|
583
827
|
# @rbs (OpenSSL::SSL::SSLSocket socket, Integer id, Reactor reactor, AtomicThreadPool thread_pool, String remote_addr, String url_scheme) -> void
|
|
584
828
|
def eager_accept(socket, id, reactor, thread_pool, remote_addr, url_scheme)
|
|
829
|
+
unless @running.true?
|
|
830
|
+
socket.close rescue nil
|
|
831
|
+
return
|
|
832
|
+
end
|
|
833
|
+
|
|
585
834
|
writer = create_writer
|
|
586
835
|
flow_control = FlowControl.new
|
|
836
|
+
ping_payload = [id].pack("Q>")
|
|
837
|
+
ping_frame = Http2Parser.new.build_frame(:ping, 0, 0, ping_payload).freeze
|
|
587
838
|
initial_state = {
|
|
588
839
|
id: id,
|
|
589
840
|
protocol: :http2,
|
|
@@ -591,7 +842,19 @@ module Raptor
|
|
|
591
842
|
url_scheme: url_scheme
|
|
592
843
|
}
|
|
593
844
|
|
|
594
|
-
reactor.attach_http2(
|
|
845
|
+
reactor.attach_http2(
|
|
846
|
+
id: id,
|
|
847
|
+
socket: socket,
|
|
848
|
+
state: initial_state,
|
|
849
|
+
writer: writer,
|
|
850
|
+
flow_control: flow_control,
|
|
851
|
+
ping_frame: ping_frame,
|
|
852
|
+
ping_payload: ping_payload
|
|
853
|
+
)
|
|
854
|
+
unless @running.true?
|
|
855
|
+
reactor.close_connection(id)
|
|
856
|
+
return
|
|
857
|
+
end
|
|
595
858
|
|
|
596
859
|
socket.write(@initial_settings_frame) rescue nil
|
|
597
860
|
|
|
@@ -605,7 +868,7 @@ module Raptor
|
|
|
605
868
|
return
|
|
606
869
|
end
|
|
607
870
|
|
|
608
|
-
while socket.pending.positive?
|
|
871
|
+
while socket.respond_to?(:pending) && socket.pending.positive?
|
|
609
872
|
buffer << socket.read_nonblock(socket.pending)
|
|
610
873
|
end
|
|
611
874
|
|
|
@@ -636,6 +899,9 @@ module Raptor
|
|
|
636
899
|
|
|
637
900
|
rounds = 0
|
|
638
901
|
loop do
|
|
902
|
+
reactor.acknowledge_http2_ping(result[:id], result[:ping_acknowledgements])
|
|
903
|
+
result[:cancelled_streams]&.each { |stream_id| flow_control.cancel_stream(stream_id) }
|
|
904
|
+
|
|
639
905
|
if flow_control && (result[:window_updates] || result[:peer_initial_window_size])
|
|
640
906
|
apply_flow_control_updates(flow_control, result)
|
|
641
907
|
end
|
|
@@ -651,6 +917,11 @@ module Raptor
|
|
|
651
917
|
stream_id = request[:stream_id]
|
|
652
918
|
remote_addr = result[:remote_addr] || Server::DEFAULT_REMOTE_ADDR
|
|
653
919
|
|
|
920
|
+
unless reactor.dispatch_http2_stream(result[:id], stream_id)
|
|
921
|
+
write_http2_reset_stream(socket, writer, stream_id, ERROR_REFUSED_STREAM)
|
|
922
|
+
next
|
|
923
|
+
end
|
|
924
|
+
|
|
654
925
|
thread_pool << proc do
|
|
655
926
|
dispatch_stream_request(
|
|
656
927
|
socket, writer, flow_control, stream_id,
|
|
@@ -716,7 +987,7 @@ module Raptor
|
|
|
716
987
|
buffer = String.new
|
|
717
988
|
buffer << data
|
|
718
989
|
|
|
719
|
-
while socket.pending.positive?
|
|
990
|
+
while socket.respond_to?(:pending) && socket.pending.positive?
|
|
720
991
|
buffer << socket.read_nonblock(socket.pending)
|
|
721
992
|
end
|
|
722
993
|
|
|
@@ -750,13 +1021,38 @@ module Raptor
|
|
|
750
1021
|
#
|
|
751
1022
|
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Array[[String, String]] headers, String body, remote_addr: String) -> void
|
|
752
1023
|
def perform_stream_request(socket, writer, flow_control, stream_id, headers, body, remote_addr:)
|
|
753
|
-
env =
|
|
1024
|
+
env = nil
|
|
1025
|
+
status = nil
|
|
1026
|
+
response_headers = nil
|
|
1027
|
+
response_started = false
|
|
1028
|
+
|
|
1029
|
+
flow_control.check(stream_id)
|
|
1030
|
+
env = build_rack_env(headers, body, socket, writer, flow_control, stream_id, remote_addr: remote_addr)
|
|
754
1031
|
status, response_headers, response_body = @app.call(env)
|
|
755
1032
|
|
|
756
|
-
response_size = write_http2_response(
|
|
1033
|
+
response_size = write_http2_response(
|
|
1034
|
+
socket,
|
|
1035
|
+
writer,
|
|
1036
|
+
flow_control,
|
|
1037
|
+
stream_id,
|
|
1038
|
+
status,
|
|
1039
|
+
response_headers,
|
|
1040
|
+
response_body,
|
|
1041
|
+
trailers: env[RESPONSE_TRAILERS],
|
|
1042
|
+
request_method: env[Rack::REQUEST_METHOD]
|
|
1043
|
+
) { response_started = true }
|
|
1044
|
+
response_body.close if response_body.respond_to?(:close)
|
|
757
1045
|
write_access_log(env, status, response_size, remote_addr) if @access_log_io
|
|
1046
|
+
Http.call_response_finished(env, status, response_headers, nil)
|
|
1047
|
+
rescue StreamClosedError => error
|
|
1048
|
+
Http.call_response_finished(env, status, response_headers, error)
|
|
758
1049
|
rescue => error
|
|
759
|
-
|
|
1050
|
+
Http.call_response_finished(env, status, response_headers, error)
|
|
1051
|
+
if response_started
|
|
1052
|
+
write_http2_reset_stream(socket, writer, stream_id, ERROR_INTERNAL_ERROR)
|
|
1053
|
+
else
|
|
1054
|
+
write_http2_error_response(socket, writer, stream_id)
|
|
1055
|
+
end
|
|
760
1056
|
|
|
761
1057
|
if @on_error
|
|
762
1058
|
@on_error.call(env, error) rescue nil
|
|
@@ -764,7 +1060,6 @@ module Raptor
|
|
|
764
1060
|
raise
|
|
765
1061
|
end
|
|
766
1062
|
ensure
|
|
767
|
-
response_body.close if response_body.respond_to?(:close)
|
|
768
1063
|
flow_control.discard_stream(stream_id) if flow_control
|
|
769
1064
|
end
|
|
770
1065
|
|
|
@@ -778,44 +1073,69 @@ module Raptor
|
|
|
778
1073
|
# @param status [Integer] HTTP status code
|
|
779
1074
|
# @param headers [Hash] response headers from the Rack application
|
|
780
1075
|
# @param body [Object] response body responding to each
|
|
1076
|
+
# @param trailers [Hash] trailing response headers populated by the Rack application
|
|
1077
|
+
# @param request_method [String] request method used to suppress HEAD response bodies
|
|
781
1078
|
# @return [String] the response body size in bytes
|
|
782
1079
|
#
|
|
783
|
-
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> String
|
|
784
|
-
def write_http2_response(socket, writer, flow_control, stream_id, status, headers, body)
|
|
1080
|
+
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body, trailers: Hash[String, String | Array[String]], request_method: String) ?{ () -> void } -> String
|
|
1081
|
+
def write_http2_response(socket, writer, flow_control, stream_id, status, headers, body, trailers:, request_method:)
|
|
785
1082
|
parser = Http2Parser.new
|
|
786
1083
|
|
|
787
1084
|
encoded_headers = parser.encode_response_headers(status, headers)
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
next if chunk.empty?
|
|
792
|
-
body_chunks << chunk
|
|
793
|
-
body_bytes += chunk.bytesize
|
|
794
|
-
end
|
|
795
|
-
|
|
796
|
-
if body_chunks.empty?
|
|
1085
|
+
flow_control.check(stream_id)
|
|
1086
|
+
no_body = request_method == "HEAD" || (status >= 100 && status < 200) || status == 204 || status == 304
|
|
1087
|
+
if no_body
|
|
797
1088
|
writer.write_frames(socket, [parser.build_frame(:headers, FLAG_END_STREAM | FLAG_END_HEADERS, stream_id, encoded_headers)])
|
|
1089
|
+
yield if block_given?
|
|
798
1090
|
return "0"
|
|
799
1091
|
end
|
|
800
1092
|
|
|
801
|
-
|
|
1093
|
+
writer.write_frames(socket, [parser.build_frame(:headers, FLAG_END_HEADERS, stream_id, encoded_headers)])
|
|
1094
|
+
yield if block_given?
|
|
802
1095
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
last_frame = chunk_index == last_chunk_index && remaining <= MAX_FRAME_SIZE
|
|
809
|
-
granted = flow_control.acquire(stream_id, remaining, end_stream: last_frame)
|
|
810
|
-
slice = offset.zero? && granted == chunk.bytesize ? chunk : chunk.byteslice(offset, granted)
|
|
811
|
-
offset += granted
|
|
812
|
-
end_stream = chunk_index == last_chunk_index && offset == chunk.bytesize
|
|
813
|
-
frames << parser.build_frame(:data, end_stream ? FLAG_END_STREAM : 0, stream_id, slice)
|
|
814
|
-
end
|
|
1096
|
+
stream = ResponseStream.new(socket, writer, flow_control, stream_id, trailers)
|
|
1097
|
+
if body.respond_to?(:each)
|
|
1098
|
+
body.each { |chunk| stream.write(chunk) }
|
|
1099
|
+
else
|
|
1100
|
+
body.call(stream)
|
|
815
1101
|
end
|
|
1102
|
+
stream.close
|
|
816
1103
|
|
|
817
|
-
|
|
818
|
-
|
|
1104
|
+
stream.bytes_written.to_s
|
|
1105
|
+
end
|
|
1106
|
+
|
|
1107
|
+
# Sends an HTTP 103 Early Hints response.
|
|
1108
|
+
#
|
|
1109
|
+
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
1110
|
+
# @param writer [Writer] frame writer for the connection
|
|
1111
|
+
# @param flow_control [FlowControl] per-connection outbound flow controller
|
|
1112
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
1113
|
+
# @param hints [Hash] response headers to send as early hints
|
|
1114
|
+
# @return [void]
|
|
1115
|
+
#
|
|
1116
|
+
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Hash[String, String | Array[String]] hints) -> void
|
|
1117
|
+
def send_early_hints(socket, writer, flow_control, stream_id, hints)
|
|
1118
|
+
return if hints.empty?
|
|
1119
|
+
|
|
1120
|
+
flow_control.check(stream_id)
|
|
1121
|
+
parser = Http2Parser.new
|
|
1122
|
+
encoded = parser.encode_response_headers(103, hints)
|
|
1123
|
+
writer.write_frames(socket, [parser.build_frame(:headers, FLAG_END_HEADERS, stream_id, encoded)])
|
|
1124
|
+
end
|
|
1125
|
+
|
|
1126
|
+
# Resets one HTTP/2 stream with the given error code.
|
|
1127
|
+
#
|
|
1128
|
+
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
1129
|
+
# @param writer [Writer] frame writer for the connection
|
|
1130
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
1131
|
+
# @param error_code [Integer] the HTTP/2 error code
|
|
1132
|
+
# @return [void]
|
|
1133
|
+
#
|
|
1134
|
+
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id, Integer error_code) -> void
|
|
1135
|
+
def write_http2_reset_stream(socket, writer, stream_id, error_code)
|
|
1136
|
+
parser = Http2Parser.new
|
|
1137
|
+
frame = parser.build_frame(:rst_stream, 0, stream_id, [error_code].pack("N"))
|
|
1138
|
+
writer.write_frames(socket, [frame])
|
|
819
1139
|
end
|
|
820
1140
|
|
|
821
1141
|
# Writes a 500 error response as HTTP/2 frames.
|
|
@@ -854,11 +1174,14 @@ module Raptor
|
|
|
854
1174
|
#
|
|
855
1175
|
# @param headers [Array<Array(String, String)>] HTTP/2 header pairs
|
|
856
1176
|
# @param body [String] the request body
|
|
1177
|
+
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
1178
|
+
# @param writer [Writer] frame writer for the connection
|
|
1179
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
857
1180
|
# @param remote_addr [String] the client IP address
|
|
858
1181
|
# @return [Hash] fully populated Rack environment hash
|
|
859
1182
|
#
|
|
860
|
-
# @rbs (Array[[String, String]] headers, String body, remote_addr: String) -> Hash[String, untyped]
|
|
861
|
-
def build_rack_env(headers, body, remote_addr:)
|
|
1183
|
+
# @rbs (Array[[String, String]] headers, String body, OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, remote_addr: String) -> Hash[String, untyped]
|
|
1184
|
+
def build_rack_env(headers, body, socket, writer, flow_control, stream_id, remote_addr:)
|
|
862
1185
|
env = {}
|
|
863
1186
|
|
|
864
1187
|
headers.each do |name, value|
|
|
@@ -887,6 +1210,9 @@ module Raptor
|
|
|
887
1210
|
env[Rack::RACK_INPUT] = StringIO.new(body).set_encoding(Encoding::ASCII_8BIT)
|
|
888
1211
|
env[Rack::RACK_ERRORS] = $stderr
|
|
889
1212
|
env[Rack::RACK_RESPONSE_FINISHED] = []
|
|
1213
|
+
env[Rack::RACK_EARLY_HINTS] = proc do |hints|
|
|
1214
|
+
send_early_hints(socket, writer, flow_control, stream_id, hints) rescue nil
|
|
1215
|
+
end
|
|
890
1216
|
env[Rack::RACK_IS_HIJACK] = false
|
|
891
1217
|
|
|
892
1218
|
env[Rack::SCRIPT_NAME] = "" unless env.key?(Rack::SCRIPT_NAME)
|
|
@@ -900,6 +1226,7 @@ module Raptor
|
|
|
900
1226
|
env[Http::REMOTE_ADDR] = remote_addr
|
|
901
1227
|
env[Http::SERVER_SOFTWARE] = Http::SERVER_SOFTWARE_VALUE
|
|
902
1228
|
env[Http::HTTP_VERSION] = SERVER_PROTOCOL
|
|
1229
|
+
env[RESPONSE_TRAILERS] = {}
|
|
903
1230
|
|
|
904
1231
|
populate_server_name_and_port(env)
|
|
905
1232
|
|