raptor 0.22.2 → 0.23.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 +35 -0
- data/Dockerfile +3 -1
- data/README.md +38 -31
- data/docs/raptor-vs-puma.md +44 -38
- data/docs/talk.md +1295 -0
- data/ext/raptor_bpf/reuseport_select.bpf.c +18 -9
- data/ext/raptor_http/extconf.rb +2 -0
- data/ext/raptor_http/raptor_http.c +91 -119
- data/ext/raptor_http2/raptor_http2.c +272 -223
- data/ext/raptor_native/raptor_native.c +38 -20
- data/lib/rackup/handler/raptor.rb +12 -11
- data/lib/raptor/binder.rb +12 -12
- data/lib/raptor/cli.rb +18 -18
- data/lib/raptor/cluster.rb +44 -44
- data/lib/raptor/control_server.rb +3 -2
- data/lib/raptor/detached_body.rb +6 -2
- data/lib/raptor/http.rb +7 -5
- data/lib/raptor/http1.rb +233 -208
- data/lib/raptor/http2.rb +314 -267
- data/lib/raptor/reactor.rb +139 -149
- data/lib/raptor/reuseport_bpf.rb +62 -38
- data/lib/raptor/server.rb +53 -38
- data/lib/raptor/stats.rb +15 -3
- data/lib/raptor/thread_locals.rb +4 -3
- data/lib/raptor/version.rb +1 -1
- data/sig/generated/rackup/handler/raptor.rbs +2 -1
- data/sig/generated/raptor/binder.rbs +9 -9
- data/sig/generated/raptor/cli.rbs +6 -6
- data/sig/generated/raptor/cluster.rbs +9 -9
- data/sig/generated/raptor/control_server.rbs +2 -1
- data/sig/generated/raptor/detached_body.rbs +5 -1
- data/sig/generated/raptor/http.rbs +3 -3
- data/sig/generated/raptor/http1.rbs +100 -91
- data/sig/generated/raptor/http2.rbs +200 -189
- data/sig/generated/raptor/reactor.rbs +107 -118
- data/sig/generated/raptor/reuseport_bpf.rbs +54 -17
- data/sig/generated/raptor/server.rbs +26 -26
- data/sig/generated/raptor/stats.rbs +3 -2
- data/sig/generated/raptor/thread_locals.rbs +4 -3
- metadata +2 -2
- data/docs/brisrails-talk.md +0 -2398
data/lib/raptor/http2.rb
CHANGED
|
@@ -10,14 +10,44 @@ require "rack"
|
|
|
10
10
|
|
|
11
11
|
require_relative "detached_body"
|
|
12
12
|
require_relative "http"
|
|
13
|
+
require_relative "log"
|
|
13
14
|
require_relative "raptor_http2"
|
|
15
|
+
require_relative "server"
|
|
14
16
|
require_relative "thread_locals"
|
|
15
17
|
|
|
16
18
|
module Raptor
|
|
17
19
|
# Handles HTTP/2 request processing and Rack application integration.
|
|
18
20
|
#
|
|
19
21
|
class Http2
|
|
22
|
+
EAGER_READ_BUFFER_SIZE = 64 * 1024
|
|
23
|
+
EAGER_MAX_ROUNDS = 8
|
|
24
|
+
|
|
25
|
+
FLAG_END_STREAM = 0x1
|
|
26
|
+
FLAG_ACK = 0x1
|
|
27
|
+
FLAG_END_HEADERS = 0x4
|
|
28
|
+
FLAG_PADDED = 0x8
|
|
29
|
+
FLAG_PRIORITY = 0x20
|
|
30
|
+
|
|
31
|
+
ERROR_NO_ERROR = 0x0
|
|
32
|
+
ERROR_PROTOCOL_ERROR = 0x1
|
|
33
|
+
ERROR_INTERNAL_ERROR = 0x2
|
|
34
|
+
ERROR_FRAME_SIZE_ERROR = 0x6
|
|
35
|
+
ERROR_REFUSED_STREAM = 0x7
|
|
36
|
+
ERROR_COMPRESSION_ERROR = 0x9
|
|
37
|
+
|
|
38
|
+
DEFAULT_WINDOW_SIZE = 65_535
|
|
39
|
+
MAX_FRAME_SIZE = 16_384
|
|
40
|
+
|
|
20
41
|
RESPONSE_TRAILERS = "raptor.response_trailers"
|
|
42
|
+
SERVER_PROTOCOL = "HTTP/2"
|
|
43
|
+
|
|
44
|
+
REQUEST_PSEUDO_HEADERS = [":method", ":scheme", ":path", ":authority"].freeze
|
|
45
|
+
REQUIRED_REQUEST_PSEUDO_HEADERS = [":method", ":scheme", ":path"].freeze
|
|
46
|
+
|
|
47
|
+
class StreamClosedError < StandardError
|
|
48
|
+
# @rbs () -> String
|
|
49
|
+
def message = "HTTP/2 stream closed"
|
|
50
|
+
end
|
|
21
51
|
|
|
22
52
|
# Queues concurrent frame writes while the reactor remains the sole
|
|
23
53
|
# owner of the connection socket.
|
|
@@ -54,11 +84,11 @@ module Raptor
|
|
|
54
84
|
# Queues frames for the reactor to write. Before attachment, writes
|
|
55
85
|
# directly so the writer remains usable during connection setup.
|
|
56
86
|
#
|
|
57
|
-
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
87
|
+
# @param socket [TCPSocket, OpenSSL::SSL::SSLSocket] the connection socket
|
|
58
88
|
# @param frames [Array<String>, nil] frame bytes to write in order
|
|
59
89
|
# @return [void]
|
|
60
90
|
#
|
|
61
|
-
# @rbs (OpenSSL::SSL::SSLSocket socket, Array[String]? frames) -> void
|
|
91
|
+
# @rbs (TCPSocket | OpenSSL::SSL::SSLSocket socket, Array[String]? frames) -> void
|
|
62
92
|
def write_frames(socket, frames)
|
|
63
93
|
return if !frames || frames.empty?
|
|
64
94
|
|
|
@@ -82,11 +112,6 @@ module Raptor
|
|
|
82
112
|
end
|
|
83
113
|
end
|
|
84
114
|
|
|
85
|
-
class StreamClosedError < StandardError
|
|
86
|
-
# @rbs () -> String
|
|
87
|
-
def message = "HTTP/2 stream closed"
|
|
88
|
-
end
|
|
89
|
-
|
|
90
115
|
# Coordinates outbound flow control and stream cancellation without
|
|
91
116
|
# polling or mutexes.
|
|
92
117
|
#
|
|
@@ -112,21 +137,22 @@ module Raptor
|
|
|
112
137
|
|
|
113
138
|
# Reserves outbound capacity on the given stream, waiting until at
|
|
114
139
|
# least one byte is available on both the connection and stream
|
|
115
|
-
# windows.
|
|
140
|
+
# windows.
|
|
116
141
|
#
|
|
117
142
|
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
118
143
|
# @param max_bytes [Integer] the largest size the caller would like to send
|
|
119
144
|
# @return [Integer] the number of bytes the caller may now send
|
|
145
|
+
# @raise [StreamClosedError] if the stream or connection has closed
|
|
120
146
|
#
|
|
121
147
|
# @rbs (Integer stream_id, Integer max_bytes) -> Integer
|
|
122
148
|
def acquire(stream_id, max_bytes)
|
|
123
|
-
|
|
124
|
-
capped = max_bytes < MAX_FRAME_SIZE ? max_bytes : MAX_FRAME_SIZE
|
|
149
|
+
capped = [max_bytes, MAX_FRAME_SIZE].min
|
|
125
150
|
|
|
126
151
|
@available.wait do
|
|
127
152
|
check(stream_id)
|
|
153
|
+
initial = @initial_stream_window.value
|
|
128
154
|
stream_window = @stream_windows.value[stream_id] || initial
|
|
129
|
-
capped_full = capped
|
|
155
|
+
capped_full = [capped, stream_window].min
|
|
130
156
|
granted = capped_full.positive? ? reserve_connection(capped_full) : 0
|
|
131
157
|
|
|
132
158
|
if granted.positive?
|
|
@@ -144,6 +170,7 @@ module Raptor
|
|
|
144
170
|
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
145
171
|
# @param max_bytes [Integer] the largest size the caller would like to send
|
|
146
172
|
# @return [Integer] available bytes, or 0 when flow control is exhausted
|
|
173
|
+
# @raise [StreamClosedError] if the stream or connection has closed
|
|
147
174
|
#
|
|
148
175
|
# @rbs (Integer stream_id, Integer max_bytes) -> Integer
|
|
149
176
|
def try_acquire(stream_id, max_bytes)
|
|
@@ -191,7 +218,7 @@ module Raptor
|
|
|
191
218
|
end
|
|
192
219
|
|
|
193
220
|
# Updates the peer's `SETTINGS_INITIAL_WINDOW_SIZE`. Shifts every
|
|
194
|
-
# existing stream window by the delta as required by RFC
|
|
221
|
+
# existing stream window by the delta as required by RFC 9113 section 6.9.2.
|
|
195
222
|
#
|
|
196
223
|
# @param new_size [Integer] the peer's new initial window size
|
|
197
224
|
# @return [void]
|
|
@@ -267,18 +294,16 @@ module Raptor
|
|
|
267
294
|
|
|
268
295
|
private
|
|
269
296
|
|
|
270
|
-
# Reserves up to `capped` bytes from the connection window
|
|
271
|
-
# returning the number of bytes granted (0 when the window is
|
|
272
|
-
# exhausted).
|
|
297
|
+
# Reserves up to `capped` bytes from the connection window.
|
|
273
298
|
#
|
|
274
299
|
# @param capped [Integer] the largest reservation the caller will accept
|
|
275
|
-
# @return [Integer]
|
|
300
|
+
# @return [Integer] bytes granted, or 0 when the window is exhausted
|
|
276
301
|
#
|
|
277
302
|
# @rbs (Integer capped) -> Integer
|
|
278
303
|
def reserve_connection(capped)
|
|
279
304
|
granted = 0
|
|
280
305
|
@connection_window.swap do |window|
|
|
281
|
-
granted = window
|
|
306
|
+
granted = [window, capped].min
|
|
282
307
|
granted.positive? ? window - granted : window
|
|
283
308
|
end
|
|
284
309
|
granted
|
|
@@ -288,7 +313,7 @@ module Raptor
|
|
|
288
313
|
# Adapts one HTTP/2 response stream to Rack's streaming body interface.
|
|
289
314
|
#
|
|
290
315
|
class ResponseStream
|
|
291
|
-
# @rbs @socket: OpenSSL::SSL::SSLSocket
|
|
316
|
+
# @rbs @socket: TCPSocket | OpenSSL::SSL::SSLSocket
|
|
292
317
|
# @rbs @writer: Writer
|
|
293
318
|
# @rbs @flow_control: FlowControl
|
|
294
319
|
# @rbs @stream_id: Integer
|
|
@@ -304,14 +329,14 @@ module Raptor
|
|
|
304
329
|
|
|
305
330
|
# Creates a response stream for a Rack streaming body.
|
|
306
331
|
#
|
|
307
|
-
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
332
|
+
# @param socket [TCPSocket, OpenSSL::SSL::SSLSocket] the connection socket
|
|
308
333
|
# @param writer [Writer] frame writer for the connection
|
|
309
|
-
# @param flow_control [FlowControl] outbound flow controller
|
|
334
|
+
# @param flow_control [FlowControl] per-connection outbound flow controller
|
|
310
335
|
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
311
336
|
# @param trailers [Hash] trailing response headers populated by the Rack application
|
|
312
337
|
# @return [void]
|
|
313
338
|
#
|
|
314
|
-
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Hash[String, String | Array[String]] trailers) -> void
|
|
339
|
+
# @rbs (TCPSocket | OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Hash[String, String | Array[String]] trailers) -> void
|
|
315
340
|
def initialize(socket, writer, flow_control, stream_id, trailers)
|
|
316
341
|
@socket = socket
|
|
317
342
|
@writer = writer
|
|
@@ -323,9 +348,9 @@ module Raptor
|
|
|
323
348
|
@closed = false
|
|
324
349
|
end
|
|
325
350
|
|
|
326
|
-
#
|
|
351
|
+
# Rejects reads; streaming responses are write-only.
|
|
327
352
|
#
|
|
328
|
-
# @raise [IOError]
|
|
353
|
+
# @raise [IOError] always
|
|
329
354
|
#
|
|
330
355
|
# @rbs (*untyped) -> bot
|
|
331
356
|
def read(*)
|
|
@@ -336,6 +361,9 @@ module Raptor
|
|
|
336
361
|
#
|
|
337
362
|
# @param chunk [String] response body bytes
|
|
338
363
|
# @return [Integer] number of bytes written
|
|
364
|
+
# @raise [IOError] if the stream has already closed
|
|
365
|
+
# @raise [TypeError] if the chunk is not a String
|
|
366
|
+
# @raise [StreamClosedError] if the stream or connection has closed
|
|
339
367
|
#
|
|
340
368
|
# @rbs (String chunk) -> Integer
|
|
341
369
|
def write(chunk)
|
|
@@ -365,7 +393,8 @@ module Raptor
|
|
|
365
393
|
self
|
|
366
394
|
end
|
|
367
395
|
|
|
368
|
-
# Flushes the stream.
|
|
396
|
+
# Flushes the stream. Frames are already handed to the writer as they
|
|
397
|
+
# are produced.
|
|
369
398
|
#
|
|
370
399
|
# @return [ResponseStream]
|
|
371
400
|
#
|
|
@@ -377,6 +406,7 @@ module Raptor
|
|
|
377
406
|
# Finishes the stream.
|
|
378
407
|
#
|
|
379
408
|
# @return [void]
|
|
409
|
+
# @raise [StreamClosedError] if the stream or connection has closed
|
|
380
410
|
#
|
|
381
411
|
# @rbs () -> void
|
|
382
412
|
def close
|
|
@@ -395,7 +425,7 @@ module Raptor
|
|
|
395
425
|
|
|
396
426
|
# Closes the unsupported read side of the stream.
|
|
397
427
|
#
|
|
398
|
-
# @raise [IOError]
|
|
428
|
+
# @raise [IOError] always
|
|
399
429
|
#
|
|
400
430
|
# @rbs () -> bot
|
|
401
431
|
def close_read
|
|
@@ -421,143 +451,6 @@ module Raptor
|
|
|
421
451
|
end
|
|
422
452
|
end
|
|
423
453
|
|
|
424
|
-
EAGER_READ_BUFFER_SIZE = 64 * 1024
|
|
425
|
-
EAGER_MAX_ROUNDS = 8
|
|
426
|
-
|
|
427
|
-
FLAG_END_STREAM = 0x1
|
|
428
|
-
FLAG_END_HEADERS = 0x4
|
|
429
|
-
FLAG_ACK = 0x1
|
|
430
|
-
FLAG_PRIORITY = 0x20
|
|
431
|
-
|
|
432
|
-
ERROR_NO_ERROR = 0x0
|
|
433
|
-
ERROR_PROTOCOL_ERROR = 0x1
|
|
434
|
-
ERROR_INTERNAL_ERROR = 0x2
|
|
435
|
-
ERROR_REFUSED_STREAM = 0x7
|
|
436
|
-
|
|
437
|
-
DEFAULT_WINDOW_SIZE = 65_535
|
|
438
|
-
MAX_FRAME_SIZE = 16_384
|
|
439
|
-
|
|
440
|
-
SERVER_PROTOCOL = "HTTP/2"
|
|
441
|
-
|
|
442
|
-
REQUEST_PSEUDO_HEADERS = [":method", ":scheme", ":path", ":authority"].freeze
|
|
443
|
-
REQUIRED_REQUEST_PSEUDO_HEADERS = [":method", ":scheme", ":path"].freeze
|
|
444
|
-
|
|
445
|
-
# Returns true when a decoded header block violates the HTTP/2 pseudo-header
|
|
446
|
-
# rules from RFC 9113 section 8.3: an unknown pseudo-header, a duplicate
|
|
447
|
-
# pseudo-header, a pseudo-header appearing after any regular header, or a
|
|
448
|
-
# required pseudo-header (`:method`, `:scheme`, `:path`) missing on
|
|
449
|
-
# non-`CONNECT` requests.
|
|
450
|
-
#
|
|
451
|
-
# @param headers [Array<Array(String, String)>] decoded header pairs
|
|
452
|
-
# @return [Boolean]
|
|
453
|
-
#
|
|
454
|
-
# @rbs (Array[[String, String]] headers) -> bool
|
|
455
|
-
def self.invalid_pseudo_headers?(headers)
|
|
456
|
-
seen_pseudo = {}
|
|
457
|
-
seen_regular = false
|
|
458
|
-
|
|
459
|
-
headers.each do |name, _value|
|
|
460
|
-
if name.start_with?(":")
|
|
461
|
-
return true if seen_regular
|
|
462
|
-
return true unless REQUEST_PSEUDO_HEADERS.include?(name)
|
|
463
|
-
return true if seen_pseudo[name]
|
|
464
|
-
seen_pseudo[name] = true
|
|
465
|
-
else
|
|
466
|
-
seen_regular = true
|
|
467
|
-
end
|
|
468
|
-
end
|
|
469
|
-
|
|
470
|
-
return false if seen_pseudo[":method"] && headers.assoc(":method")&.last == "CONNECT"
|
|
471
|
-
|
|
472
|
-
REQUIRED_REQUEST_PSEUDO_HEADERS.any? { |name| !seen_pseudo[name] }
|
|
473
|
-
end
|
|
474
|
-
|
|
475
|
-
# @rbs @app: ^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped]
|
|
476
|
-
# @rbs @server_port: Integer
|
|
477
|
-
# @rbs @write_timeout: Integer
|
|
478
|
-
# @rbs @access_log_io: IO?
|
|
479
|
-
# @rbs @clean_thread_locals: bool
|
|
480
|
-
# @rbs @clean_fiber_locals: bool
|
|
481
|
-
# @rbs @on_error: ^(Hash[String, untyped]?, Exception) -> void | nil
|
|
482
|
-
# @rbs @initial_settings_frame: String
|
|
483
|
-
# @rbs @running: AtomicBoolean
|
|
484
|
-
|
|
485
|
-
# Returns the initial server SETTINGS frame to send on every new
|
|
486
|
-
# HTTP/2 connection.
|
|
487
|
-
#
|
|
488
|
-
# @return [String]
|
|
489
|
-
attr_reader :initial_settings_frame
|
|
490
|
-
|
|
491
|
-
# Creates a new Http2 handler.
|
|
492
|
-
#
|
|
493
|
-
# @param app [#call] the Rack application to dispatch requests to
|
|
494
|
-
# @param server_port [Integer] port number used to populate SERVER_PORT in the Rack env
|
|
495
|
-
# @param connection_options [Hash] per-connection settings shared across protocols
|
|
496
|
-
# @option connection_options [Integer] :write_timeout per-write socket timeout in seconds
|
|
497
|
-
# @param http2_options [Hash] HTTP/2-specific settings
|
|
498
|
-
# @option http2_options [Integer] :max_concurrent_streams maximum HTTP/2 concurrent streams per connection
|
|
499
|
-
# @param access_log_io [IO, nil] IO to write Common Log Format access entries to, or nil to disable
|
|
500
|
-
# @param clean_thread_locals [Boolean] whether to clear application thread locals after each request
|
|
501
|
-
# @param clean_fiber_locals [Boolean] whether to process each request in a fresh Fiber
|
|
502
|
-
# @param on_error [#call, nil] callback invoked with (env, exception) when the Rack app raises
|
|
503
|
-
# @return [void]
|
|
504
|
-
#
|
|
505
|
-
# @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Integer server_port, ?connection_options: Hash[Symbol, untyped], ?http2_options: Hash[Symbol, untyped], ?access_log_io: IO?, ?clean_thread_locals: bool, ?clean_fiber_locals: bool, ?on_error: ^(Hash[String, untyped]?, Exception) -> void | nil) -> void
|
|
506
|
-
def initialize(app, server_port, connection_options: {}, http2_options: {}, access_log_io: nil, clean_thread_locals: true, clean_fiber_locals: false, on_error: nil)
|
|
507
|
-
@app = app
|
|
508
|
-
@server_port = server_port
|
|
509
|
-
@write_timeout = connection_options[:write_timeout] || Http::WRITE_TIMEOUT
|
|
510
|
-
@access_log_io = access_log_io
|
|
511
|
-
@clean_thread_locals = clean_thread_locals
|
|
512
|
-
@clean_fiber_locals = clean_fiber_locals
|
|
513
|
-
@on_error = on_error
|
|
514
|
-
@running = AtomicBoolean.new(true)
|
|
515
|
-
|
|
516
|
-
parser = Http2Parser.new
|
|
517
|
-
settings_payload = parser.build_settings(
|
|
518
|
-
max_concurrent_streams: http2_options[:max_concurrent_streams],
|
|
519
|
-
initial_window_size: DEFAULT_WINDOW_SIZE
|
|
520
|
-
)
|
|
521
|
-
@initial_settings_frame = parser.build_frame(:settings, 0, 0, settings_payload).freeze
|
|
522
|
-
end
|
|
523
|
-
|
|
524
|
-
# Creates a per-connection {Writer} configured with the handler's write timeout.
|
|
525
|
-
#
|
|
526
|
-
# @return [Writer] a new per-connection frame writer
|
|
527
|
-
#
|
|
528
|
-
# @rbs () -> Writer
|
|
529
|
-
def create_writer
|
|
530
|
-
Writer.new(write_timeout: @write_timeout)
|
|
531
|
-
end
|
|
532
|
-
|
|
533
|
-
# Stops new stream dispatch and asks clients to finish streams already
|
|
534
|
-
# handed to the application.
|
|
535
|
-
#
|
|
536
|
-
# @param reactor [Reactor] the reactor managing HTTP/2 connections
|
|
537
|
-
# @return [void]
|
|
538
|
-
#
|
|
539
|
-
# @rbs (Reactor reactor) -> void
|
|
540
|
-
def shutdown(reactor)
|
|
541
|
-
@running.make_false
|
|
542
|
-
parser = Http2Parser.new
|
|
543
|
-
reactor.drain_http2 do |stream_id|
|
|
544
|
-
payload = [stream_id, ERROR_NO_ERROR].pack("NN")
|
|
545
|
-
parser.build_frame(:goaway, 0, 0, payload)
|
|
546
|
-
end
|
|
547
|
-
end
|
|
548
|
-
|
|
549
|
-
# Returns a Ractor-safe proc that parses HTTP/2 frames from the
|
|
550
|
-
# state hash's buffered bytes.
|
|
551
|
-
#
|
|
552
|
-
# @return [Proc]
|
|
553
|
-
#
|
|
554
|
-
# @rbs () -> ^(Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
|
|
555
|
-
def parser_worker
|
|
556
|
-
proc do |data|
|
|
557
|
-
Raptor::Http2.process_frames(data)
|
|
558
|
-
end
|
|
559
|
-
end
|
|
560
|
-
|
|
561
454
|
# Advances HTTP/2 frame parsing from the connection buffer, returning
|
|
562
455
|
# updated connection state along with any outgoing protocol frames and
|
|
563
456
|
# completed stream requests.
|
|
@@ -593,6 +486,12 @@ module Raptor
|
|
|
593
486
|
end
|
|
594
487
|
|
|
595
488
|
loop do
|
|
489
|
+
# Reject oversized frames before waiting for their payload.
|
|
490
|
+
if buffer.bytesize >= 3 && (buffer.getbyte(0) << 16 | buffer.getbyte(1) << 8 | buffer.getbyte(2)) > MAX_FRAME_SIZE
|
|
491
|
+
goaway_error = ERROR_FRAME_SIZE_ERROR
|
|
492
|
+
break
|
|
493
|
+
end
|
|
494
|
+
|
|
596
495
|
parsed = parser.parse_frame(buffer)
|
|
597
496
|
break unless parsed
|
|
598
497
|
|
|
@@ -614,9 +513,14 @@ module Raptor
|
|
|
614
513
|
|
|
615
514
|
when :headers
|
|
616
515
|
stream_id = frame[:stream_id]
|
|
617
|
-
header_payload = frame
|
|
516
|
+
header_payload = unpadded_payload(frame)
|
|
618
517
|
trailers = streams.key?(stream_id)
|
|
619
518
|
|
|
519
|
+
unless header_payload
|
|
520
|
+
goaway_error = ERROR_PROTOCOL_ERROR
|
|
521
|
+
break
|
|
522
|
+
end
|
|
523
|
+
|
|
620
524
|
unless trailers
|
|
621
525
|
if stream_id.even? || stream_id <= last_client_stream_id
|
|
622
526
|
goaway_error = ERROR_PROTOCOL_ERROR
|
|
@@ -648,6 +552,10 @@ module Raptor
|
|
|
648
552
|
end
|
|
649
553
|
|
|
650
554
|
pending_headers = pending_headers.merge(buffer: pending_headers[:buffer] + frame[:payload])
|
|
555
|
+
if pending_headers[:buffer].bytesize > Http2Parser::MAX_HEADER_LIST_SIZE
|
|
556
|
+
goaway_error = ERROR_PROTOCOL_ERROR
|
|
557
|
+
break
|
|
558
|
+
end
|
|
651
559
|
|
|
652
560
|
if frame[:flags].anybits?(FLAG_END_HEADERS)
|
|
653
561
|
stream_id = pending_headers[:stream_id]
|
|
@@ -661,26 +569,29 @@ module Raptor
|
|
|
661
569
|
|
|
662
570
|
when :data
|
|
663
571
|
stream_id = frame[:stream_id]
|
|
572
|
+
chunk = unpadded_payload(frame)
|
|
664
573
|
|
|
665
|
-
|
|
574
|
+
if stream_id.zero? || stream_id > last_client_stream_id || !chunk
|
|
666
575
|
goaway_error = ERROR_PROTOCOL_ERROR
|
|
667
576
|
break
|
|
668
577
|
end
|
|
669
578
|
|
|
579
|
+
# DATA for closed streams still counts toward connection flow control.
|
|
580
|
+
connection_window -= frame[:length]
|
|
581
|
+
if connection_window < DEFAULT_WINDOW_SIZE / 2
|
|
582
|
+
outgoing_frames << parser.build_frame(:window_update, 0, 0, [DEFAULT_WINDOW_SIZE - connection_window].pack("N"))
|
|
583
|
+
connection_window = DEFAULT_WINDOW_SIZE
|
|
584
|
+
end
|
|
585
|
+
|
|
670
586
|
stream = streams[stream_id]
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
if frame[:
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
increment = DEFAULT_WINDOW_SIZE - connection_window
|
|
678
|
-
wu_payload = [increment].pack("N")
|
|
679
|
-
outgoing_frames << parser.build_frame(:window_update, 0, 0, wu_payload)
|
|
680
|
-
outgoing_frames << parser.build_frame(:window_update, 0, stream_id, wu_payload)
|
|
681
|
-
connection_window += increment
|
|
682
|
-
end
|
|
587
|
+
next unless stream
|
|
588
|
+
|
|
589
|
+
stream_window_used = (stream[:window_used] || 0) + frame[:length]
|
|
590
|
+
if stream_window_used >= DEFAULT_WINDOW_SIZE / 2 && frame[:flags].nobits?(FLAG_END_STREAM)
|
|
591
|
+
outgoing_frames << parser.build_frame(:window_update, 0, stream_id, [stream_window_used].pack("N"))
|
|
592
|
+
stream_window_used = 0
|
|
683
593
|
end
|
|
594
|
+
stream = stream.merge(body: (stream[:body] || "") + chunk, window_used: stream_window_used)
|
|
684
595
|
|
|
685
596
|
if frame[:flags].anybits?(FLAG_END_STREAM)
|
|
686
597
|
stream_headers = stream[:headers] || []
|
|
@@ -715,6 +626,14 @@ module Raptor
|
|
|
715
626
|
completed_requests.reject! { |request| request[:stream_id] == stream_id }
|
|
716
627
|
cancelled_streams << stream_id
|
|
717
628
|
end
|
|
629
|
+
rescue Http2ParserError
|
|
630
|
+
# A failed header decode can desynchronize HPACK state for the whole connection.
|
|
631
|
+
goaway_error = if frame[:type] == :headers || frame[:type] == :continuation
|
|
632
|
+
ERROR_COMPRESSION_ERROR
|
|
633
|
+
else
|
|
634
|
+
ERROR_FRAME_SIZE_ERROR
|
|
635
|
+
end
|
|
636
|
+
break
|
|
718
637
|
end
|
|
719
638
|
|
|
720
639
|
if goaway_error
|
|
@@ -725,6 +644,50 @@ module Raptor
|
|
|
725
644
|
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)
|
|
726
645
|
end
|
|
727
646
|
|
|
647
|
+
# Returns a DATA or HEADERS payload without padding, or nil if padding
|
|
648
|
+
# is invalid.
|
|
649
|
+
#
|
|
650
|
+
# @param frame [Hash] the parsed frame
|
|
651
|
+
# @return [String, nil]
|
|
652
|
+
#
|
|
653
|
+
# @rbs (Hash[Symbol, untyped] frame) -> String?
|
|
654
|
+
def self.unpadded_payload(frame)
|
|
655
|
+
payload = frame[:payload]
|
|
656
|
+
return payload if frame[:flags].nobits?(FLAG_PADDED)
|
|
657
|
+
|
|
658
|
+
pad_length = payload.getbyte(0)
|
|
659
|
+
payload.byteslice(1, payload.bytesize - 1 - pad_length) if pad_length && pad_length < payload.bytesize
|
|
660
|
+
end
|
|
661
|
+
private_class_method :unpadded_payload
|
|
662
|
+
|
|
663
|
+
# Returns whether request pseudo-headers violate RFC 9113 section 8.3.
|
|
664
|
+
#
|
|
665
|
+
# @param headers [Array<Array(String, String)>] decoded header pairs
|
|
666
|
+
# @return [Boolean]
|
|
667
|
+
#
|
|
668
|
+
# @rbs (Array[[String, String]] headers) -> bool
|
|
669
|
+
def self.invalid_pseudo_headers?(headers)
|
|
670
|
+
seen_pseudo = {}
|
|
671
|
+
seen_regular = false
|
|
672
|
+
|
|
673
|
+
headers.each do |name, _value|
|
|
674
|
+
if name.start_with?(":")
|
|
675
|
+
return true if seen_regular
|
|
676
|
+
return true unless REQUEST_PSEUDO_HEADERS.include?(name)
|
|
677
|
+
return true if seen_pseudo[name]
|
|
678
|
+
|
|
679
|
+
seen_pseudo[name] = true
|
|
680
|
+
else
|
|
681
|
+
seen_regular = true
|
|
682
|
+
end
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
return false if seen_pseudo[":method"] && headers.assoc(":method")&.last == "CONNECT"
|
|
686
|
+
|
|
687
|
+
REQUIRED_REQUEST_PSEUDO_HEADERS.any? { |name| !seen_pseudo[name] }
|
|
688
|
+
end
|
|
689
|
+
private_class_method :invalid_pseudo_headers?
|
|
690
|
+
|
|
728
691
|
# Applies one complete request header block to its stream, resetting
|
|
729
692
|
# malformed initial headers or trailers and completing valid trailers.
|
|
730
693
|
#
|
|
@@ -732,7 +695,7 @@ module Raptor
|
|
|
732
695
|
# @param completed_requests [Array<Hash>] accumulator of completed stream requests
|
|
733
696
|
# @param outgoing_frames [Array<String>] accumulator of protocol response frames
|
|
734
697
|
# @param parser [Http2Parser] frame parser and encoder
|
|
735
|
-
# @param stream_id [Integer] the stream identifier
|
|
698
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
736
699
|
# @param decoded_headers [Array<Array(String, String)>] decoded header pairs
|
|
737
700
|
# @param end_stream [Boolean] whether the source frame had END_STREAM set
|
|
738
701
|
# @param trailers [Boolean] whether this is a trailing header block
|
|
@@ -769,7 +732,7 @@ module Raptor
|
|
|
769
732
|
#
|
|
770
733
|
# @param streams [Hash] current open-stream map
|
|
771
734
|
# @param completed_requests [Array<Hash>] accumulator of completed stream requests
|
|
772
|
-
# @param stream_id [Integer] the stream identifier
|
|
735
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
773
736
|
# @param decoded_headers [Array<Array(String, String)>] decoded header pairs
|
|
774
737
|
# @param end_stream [Boolean] whether the source frame had END_STREAM set
|
|
775
738
|
# @return [Array(Hash, Array<Hash>)] updated streams and completed_requests
|
|
@@ -840,20 +803,92 @@ module Raptor
|
|
|
840
803
|
end
|
|
841
804
|
private_class_method :build_result
|
|
842
805
|
|
|
806
|
+
# @rbs @app: ^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped]
|
|
807
|
+
# @rbs @server_port_string: String
|
|
808
|
+
# @rbs @write_timeout: Integer
|
|
809
|
+
# @rbs @access_log_io: IO?
|
|
810
|
+
# @rbs @clean_thread_locals: bool
|
|
811
|
+
# @rbs @clean_fiber_locals: bool
|
|
812
|
+
# @rbs @on_error: (^(Hash[String, untyped]?, Exception) -> void)?
|
|
813
|
+
# @rbs @running: AtomicBoolean
|
|
814
|
+
# @rbs @initial_settings_frame: String
|
|
815
|
+
|
|
816
|
+
# Creates a new Http2 handler.
|
|
817
|
+
#
|
|
818
|
+
# @param app [#call] the Rack application to dispatch requests to
|
|
819
|
+
# @param server_port [Integer] port number used to populate SERVER_PORT in the Rack env
|
|
820
|
+
# @param connection_options [Hash] per-connection settings shared across protocols
|
|
821
|
+
# @option connection_options [Integer] :write_timeout per-write socket timeout in seconds
|
|
822
|
+
# @param http2_options [Hash] HTTP/2-specific settings
|
|
823
|
+
# @option http2_options [Integer] :max_concurrent_streams maximum HTTP/2 concurrent streams per connection
|
|
824
|
+
# @param access_log_io [IO, nil] IO to write Common Log Format access entries to, or nil to disable
|
|
825
|
+
# @param clean_thread_locals [Boolean] whether to clear application thread locals after each request
|
|
826
|
+
# @param clean_fiber_locals [Boolean] whether to process each request in a fresh Fiber
|
|
827
|
+
# @param on_error [#call, nil] callback invoked with (env, exception) when the Rack app raises
|
|
828
|
+
# @return [void]
|
|
829
|
+
#
|
|
830
|
+
# @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Integer server_port, ?connection_options: Hash[Symbol, untyped], ?http2_options: Hash[Symbol, untyped], ?access_log_io: IO?, ?clean_thread_locals: bool, ?clean_fiber_locals: bool, ?on_error: (^(Hash[String, untyped]?, Exception) -> void)?) -> void
|
|
831
|
+
def initialize(app, server_port, connection_options: {}, http2_options: {}, access_log_io: nil, clean_thread_locals: true, clean_fiber_locals: false, on_error: nil)
|
|
832
|
+
@app = app
|
|
833
|
+
@server_port_string = server_port.to_s.freeze
|
|
834
|
+
@write_timeout = connection_options[:write_timeout] || Http::WRITE_TIMEOUT
|
|
835
|
+
@access_log_io = access_log_io
|
|
836
|
+
@clean_thread_locals = clean_thread_locals
|
|
837
|
+
@clean_fiber_locals = clean_fiber_locals
|
|
838
|
+
@on_error = on_error
|
|
839
|
+
@running = AtomicBoolean.new(true)
|
|
840
|
+
|
|
841
|
+
parser = Http2Parser.new
|
|
842
|
+
settings_payload = parser.build_settings(
|
|
843
|
+
max_concurrent_streams: http2_options[:max_concurrent_streams],
|
|
844
|
+
initial_window_size: DEFAULT_WINDOW_SIZE,
|
|
845
|
+
max_header_list_size: Http2Parser::MAX_HEADER_LIST_SIZE
|
|
846
|
+
)
|
|
847
|
+
@initial_settings_frame = parser.build_frame(:settings, 0, 0, settings_payload).freeze
|
|
848
|
+
end
|
|
849
|
+
|
|
850
|
+
# Returns a Ractor-safe proc that parses HTTP/2 frames from the
|
|
851
|
+
# state hash's buffered bytes.
|
|
852
|
+
#
|
|
853
|
+
# @return [Proc]
|
|
854
|
+
#
|
|
855
|
+
# @rbs () -> ^(Hash[Symbol, untyped]) -> Hash[Symbol, untyped]
|
|
856
|
+
def parser_worker
|
|
857
|
+
proc do |data|
|
|
858
|
+
Http2.process_frames(data)
|
|
859
|
+
end
|
|
860
|
+
end
|
|
861
|
+
|
|
862
|
+
# Stops new stream dispatch and asks clients to finish streams already
|
|
863
|
+
# handed to the application.
|
|
864
|
+
#
|
|
865
|
+
# @param reactor [Reactor] the reactor managing HTTP/2 connections
|
|
866
|
+
# @return [void]
|
|
867
|
+
#
|
|
868
|
+
# @rbs (Reactor reactor) -> void
|
|
869
|
+
def shutdown(reactor)
|
|
870
|
+
@running.make_false
|
|
871
|
+
parser = Http2Parser.new
|
|
872
|
+
reactor.drain_http2 do |stream_id|
|
|
873
|
+
payload = [stream_id, ERROR_NO_ERROR].pack("NN")
|
|
874
|
+
parser.build_frame(:goaway, 0, 0, payload)
|
|
875
|
+
end
|
|
876
|
+
end
|
|
877
|
+
|
|
843
878
|
# Sends the server SETTINGS frame on a freshly negotiated HTTP/2
|
|
844
879
|
# connection, then eagerly reads and parses the first client frame
|
|
845
880
|
# batch, dispatching completed streams directly to the thread pool.
|
|
846
881
|
# Falls back to the reactor when no initial data is ready.
|
|
847
882
|
#
|
|
848
|
-
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
849
|
-
# @param id [Integer] unique
|
|
883
|
+
# @param socket [TCPSocket, OpenSSL::SSL::SSLSocket] the connection socket
|
|
884
|
+
# @param id [Integer] unique connection identifier
|
|
850
885
|
# @param reactor [Reactor] the reactor managing the connection
|
|
851
886
|
# @param thread_pool [AtomicThreadPool] thread pool for application processing
|
|
852
|
-
# @param remote_addr [String] client IP address
|
|
853
|
-
# @param url_scheme [String] "https"
|
|
887
|
+
# @param remote_addr [String] the client IP address
|
|
888
|
+
# @param url_scheme [String] "http" or "https"
|
|
854
889
|
# @return [void]
|
|
855
890
|
#
|
|
856
|
-
# @rbs (OpenSSL::SSL::SSLSocket socket, Integer id, Reactor reactor, AtomicThreadPool thread_pool, String remote_addr, String url_scheme) -> void
|
|
891
|
+
# @rbs (TCPSocket | OpenSSL::SSL::SSLSocket socket, Integer id, Reactor reactor, AtomicThreadPool thread_pool, String remote_addr, String url_scheme) -> void
|
|
857
892
|
def eager_accept(socket, id, reactor, thread_pool, remote_addr, url_scheme)
|
|
858
893
|
unless @running.true?
|
|
859
894
|
socket.close rescue nil
|
|
@@ -892,7 +927,7 @@ module Raptor
|
|
|
892
927
|
rescue IO::WaitReadable
|
|
893
928
|
reactor.watch(id)
|
|
894
929
|
return
|
|
895
|
-
rescue EOFError, IOError
|
|
930
|
+
rescue EOFError, IOError, SystemCallError
|
|
896
931
|
reactor.close_connection(id)
|
|
897
932
|
return
|
|
898
933
|
end
|
|
@@ -901,7 +936,7 @@ module Raptor
|
|
|
901
936
|
buffer << socket.read_nonblock(socket.pending)
|
|
902
937
|
end
|
|
903
938
|
|
|
904
|
-
result =
|
|
939
|
+
result = Http2.process_frames(initial_state.merge(buffer: buffer))
|
|
905
940
|
handle_parsed_request(result, reactor, thread_pool)
|
|
906
941
|
rescue => error
|
|
907
942
|
Log.rescued_error(error)
|
|
@@ -913,45 +948,45 @@ module Raptor
|
|
|
913
948
|
# further buffered frame batches before returning control to the
|
|
914
949
|
# reactor.
|
|
915
950
|
#
|
|
916
|
-
# @param
|
|
951
|
+
# @param parsed_request [Hash] the parsed result produced by {.process_frames}
|
|
917
952
|
# @param reactor [Reactor] the reactor managing the connection
|
|
918
|
-
# @param thread_pool [AtomicThreadPool] thread pool for
|
|
953
|
+
# @param thread_pool [AtomicThreadPool] thread pool for application processing
|
|
919
954
|
# @return [void]
|
|
920
955
|
#
|
|
921
|
-
# @rbs (Hash[Symbol, untyped]
|
|
922
|
-
def handle_parsed_request(
|
|
923
|
-
socket = reactor.socket_for(
|
|
956
|
+
# @rbs (Hash[Symbol, untyped] parsed_request, Reactor reactor, AtomicThreadPool thread_pool) -> void
|
|
957
|
+
def handle_parsed_request(parsed_request, reactor, thread_pool)
|
|
958
|
+
socket = reactor.socket_for(parsed_request[:id])
|
|
924
959
|
return unless socket
|
|
925
960
|
|
|
926
|
-
writer = reactor.writer_for(
|
|
927
|
-
flow_control = reactor.flow_control_for(
|
|
961
|
+
writer = reactor.writer_for(parsed_request[:id])
|
|
962
|
+
flow_control = reactor.flow_control_for(parsed_request[:id])
|
|
928
963
|
|
|
929
964
|
rounds = 0
|
|
930
965
|
loop do
|
|
931
|
-
reactor.acknowledge_http2_ping(
|
|
932
|
-
|
|
966
|
+
reactor.acknowledge_http2_ping(parsed_request[:id], parsed_request[:ping_acknowledgements])
|
|
967
|
+
parsed_request[:cancelled_streams]&.each do |stream_id|
|
|
933
968
|
flow_control.cancel_stream(stream_id)
|
|
934
|
-
reactor.cancel_http2_body(
|
|
969
|
+
reactor.cancel_http2_body(parsed_request[:id], stream_id)
|
|
935
970
|
end
|
|
936
971
|
|
|
937
|
-
if flow_control && (
|
|
938
|
-
apply_flow_control_updates(flow_control,
|
|
939
|
-
reactor.resume_http2_bodies(
|
|
972
|
+
if flow_control && (parsed_request[:window_updates]&.any? || parsed_request[:peer_initial_window_size])
|
|
973
|
+
apply_flow_control_updates(flow_control, parsed_request)
|
|
974
|
+
reactor.resume_http2_bodies(parsed_request[:id])
|
|
940
975
|
end
|
|
941
976
|
|
|
942
|
-
writer.write_frames(socket,
|
|
977
|
+
writer.write_frames(socket, parsed_request[:outgoing_frames])
|
|
943
978
|
|
|
944
|
-
if
|
|
945
|
-
reactor.close_connection(
|
|
979
|
+
if parsed_request[:close_connection]
|
|
980
|
+
reactor.close_connection(parsed_request[:id])
|
|
946
981
|
return
|
|
947
982
|
end
|
|
948
983
|
|
|
949
|
-
|
|
984
|
+
parsed_request[:completed_requests]&.each do |request|
|
|
950
985
|
stream_id = request[:stream_id]
|
|
951
|
-
remote_addr =
|
|
986
|
+
remote_addr = parsed_request[:remote_addr] || Server::DEFAULT_REMOTE_ADDR
|
|
952
987
|
|
|
953
|
-
unless reactor.dispatch_http2_stream(
|
|
954
|
-
|
|
988
|
+
unless reactor.dispatch_http2_stream(parsed_request[:id], stream_id)
|
|
989
|
+
write_reset_stream(socket, writer, stream_id, ERROR_REFUSED_STREAM)
|
|
955
990
|
next
|
|
956
991
|
end
|
|
957
992
|
|
|
@@ -971,19 +1006,28 @@ module Raptor
|
|
|
971
1006
|
next_batch = eager_read_next_batch(socket)
|
|
972
1007
|
break unless next_batch
|
|
973
1008
|
|
|
974
|
-
|
|
1009
|
+
parsed_request = Http2.process_frames(parsed_request.merge(buffer: parsed_request[:buffer] + next_batch))
|
|
975
1010
|
end
|
|
976
1011
|
|
|
977
|
-
reactor.
|
|
1012
|
+
reactor.update_state(parsed_request)
|
|
978
1013
|
end
|
|
979
1014
|
|
|
980
1015
|
private
|
|
981
1016
|
|
|
1017
|
+
# Creates a per-connection {Writer} configured with the handler's write timeout.
|
|
1018
|
+
#
|
|
1019
|
+
# @return [Writer] a new per-connection frame writer
|
|
1020
|
+
#
|
|
1021
|
+
# @rbs () -> Writer
|
|
1022
|
+
def create_writer
|
|
1023
|
+
Writer.new(write_timeout: @write_timeout)
|
|
1024
|
+
end
|
|
1025
|
+
|
|
982
1026
|
# Applies inbound flow-control updates from a parsed result to the
|
|
983
|
-
# connection's
|
|
1027
|
+
# connection's {FlowControl}.
|
|
984
1028
|
#
|
|
985
|
-
# @param flow_control [FlowControl]
|
|
986
|
-
# @param result [Hash] the parsed result from
|
|
1029
|
+
# @param flow_control [FlowControl] per-connection outbound flow controller
|
|
1030
|
+
# @param result [Hash] the parsed result from {.process_frames}
|
|
987
1031
|
# @return [void]
|
|
988
1032
|
#
|
|
989
1033
|
# @rbs (FlowControl flow_control, Hash[Symbol, untyped] result) -> void
|
|
@@ -1004,22 +1048,19 @@ module Raptor
|
|
|
1004
1048
|
# Reads the next frame batch from `socket` if it has already arrived,
|
|
1005
1049
|
# without waiting on the shared collector thread.
|
|
1006
1050
|
#
|
|
1007
|
-
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
1051
|
+
# @param socket [TCPSocket, OpenSSL::SSL::SSLSocket] the connection socket
|
|
1008
1052
|
# @return [String, nil] the bytes read, or nil if nothing was available
|
|
1009
1053
|
#
|
|
1010
|
-
# @rbs (OpenSSL::SSL::SSLSocket socket) -> String?
|
|
1054
|
+
# @rbs (TCPSocket | OpenSSL::SSL::SSLSocket socket) -> String?
|
|
1011
1055
|
def eager_read_next_batch(socket)
|
|
1012
1056
|
return unless socket.wait_readable(0)
|
|
1013
1057
|
|
|
1014
|
-
|
|
1058
|
+
buffer = begin
|
|
1015
1059
|
socket.read_nonblock(EAGER_READ_BUFFER_SIZE)
|
|
1016
|
-
rescue IO::WaitReadable, EOFError, IOError
|
|
1060
|
+
rescue IO::WaitReadable, EOFError, IOError, SystemCallError
|
|
1017
1061
|
return
|
|
1018
1062
|
end
|
|
1019
1063
|
|
|
1020
|
-
buffer = String.new
|
|
1021
|
-
buffer << data
|
|
1022
|
-
|
|
1023
1064
|
while socket.respond_to?(:pending) && socket.pending.positive?
|
|
1024
1065
|
buffer << socket.read_nonblock(socket.pending)
|
|
1025
1066
|
end
|
|
@@ -1030,8 +1071,8 @@ module Raptor
|
|
|
1030
1071
|
# Dispatches a completed stream request to the Rack app and writes
|
|
1031
1072
|
# the response back as HTTP/2 frames.
|
|
1032
1073
|
#
|
|
1033
|
-
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
1034
|
-
# @param writer [Writer]
|
|
1074
|
+
# @param socket [TCPSocket, OpenSSL::SSL::SSLSocket] the connection socket
|
|
1075
|
+
# @param writer [Writer] frame writer for the connection
|
|
1035
1076
|
# @param flow_control [FlowControl] per-connection outbound flow controller
|
|
1036
1077
|
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
1037
1078
|
# @param headers [Array<Array(String, String)>] request headers
|
|
@@ -1039,12 +1080,12 @@ module Raptor
|
|
|
1039
1080
|
# @param remote_addr [String] the client IP address
|
|
1040
1081
|
# @return [void]
|
|
1041
1082
|
#
|
|
1042
|
-
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Array[[String, String]] headers, String body, remote_addr: String) -> void
|
|
1083
|
+
# @rbs (TCPSocket | OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Array[[String, String]] headers, String body, remote_addr: String) -> void
|
|
1043
1084
|
def dispatch_stream_request(socket, writer, flow_control, stream_id, headers, body, remote_addr:)
|
|
1044
1085
|
if @clean_fiber_locals
|
|
1045
|
-
Fiber.new { perform_stream_request(socket, writer, flow_control, stream_id, headers, body, remote_addr:) }.resume
|
|
1086
|
+
Fiber.new { perform_stream_request(socket, writer, flow_control, stream_id, headers, body, remote_addr: remote_addr) }.resume
|
|
1046
1087
|
else
|
|
1047
|
-
perform_stream_request(socket, writer, flow_control, stream_id, headers, body, remote_addr:)
|
|
1088
|
+
perform_stream_request(socket, writer, flow_control, stream_id, headers, body, remote_addr: remote_addr)
|
|
1048
1089
|
end
|
|
1049
1090
|
ensure
|
|
1050
1091
|
ThreadLocals.clear if @clean_thread_locals
|
|
@@ -1052,8 +1093,8 @@ module Raptor
|
|
|
1052
1093
|
|
|
1053
1094
|
# Calls the Rack app and writes its response for one stream.
|
|
1054
1095
|
#
|
|
1055
|
-
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
1056
|
-
# @param writer [Writer]
|
|
1096
|
+
# @param socket [TCPSocket, OpenSSL::SSL::SSLSocket] the connection socket
|
|
1097
|
+
# @param writer [Writer] frame writer for the connection
|
|
1057
1098
|
# @param flow_control [FlowControl] per-connection outbound flow controller
|
|
1058
1099
|
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
1059
1100
|
# @param headers [Array<Array(String, String)>] request headers
|
|
@@ -1061,7 +1102,7 @@ module Raptor
|
|
|
1061
1102
|
# @param remote_addr [String] the client IP address
|
|
1062
1103
|
# @return [void]
|
|
1063
1104
|
#
|
|
1064
|
-
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Array[[String, String]] headers, String body, remote_addr: String) -> void
|
|
1105
|
+
# @rbs (TCPSocket | OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Array[[String, String]] headers, String body, remote_addr: String) -> void
|
|
1065
1106
|
def perform_stream_request(socket, writer, flow_control, stream_id, headers, body, remote_addr:)
|
|
1066
1107
|
env = nil
|
|
1067
1108
|
status = nil
|
|
@@ -1081,14 +1122,14 @@ module Raptor
|
|
|
1081
1122
|
Http.call_response_finished(env, status, response_headers, error)
|
|
1082
1123
|
flow_control.discard_stream(stream_id)
|
|
1083
1124
|
end
|
|
1084
|
-
|
|
1125
|
+
write_detached_response(socket, writer, flow_control, stream_id, status, response_headers, response_body, finished) do
|
|
1085
1126
|
response_started = true
|
|
1086
1127
|
end
|
|
1087
1128
|
detached = true
|
|
1088
1129
|
return
|
|
1089
1130
|
end
|
|
1090
1131
|
|
|
1091
|
-
response_size =
|
|
1132
|
+
response_size = write_response(
|
|
1092
1133
|
socket,
|
|
1093
1134
|
writer,
|
|
1094
1135
|
flow_control,
|
|
@@ -1111,9 +1152,9 @@ module Raptor
|
|
|
1111
1152
|
rescue => error
|
|
1112
1153
|
Http.call_response_finished(env, status, response_headers, error)
|
|
1113
1154
|
if response_started
|
|
1114
|
-
|
|
1155
|
+
write_reset_stream(socket, writer, stream_id, ERROR_INTERNAL_ERROR)
|
|
1115
1156
|
else
|
|
1116
|
-
|
|
1157
|
+
write_error_response(socket, writer, stream_id)
|
|
1117
1158
|
end
|
|
1118
1159
|
|
|
1119
1160
|
if @on_error
|
|
@@ -1127,8 +1168,8 @@ module Raptor
|
|
|
1127
1168
|
|
|
1128
1169
|
# Starts a detached HTTP/2 response after writing its response headers.
|
|
1129
1170
|
#
|
|
1130
|
-
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
1131
|
-
# @param writer [Writer]
|
|
1171
|
+
# @param socket [TCPSocket, OpenSSL::SSL::SSLSocket] the connection socket
|
|
1172
|
+
# @param writer [Writer] frame writer for the connection
|
|
1132
1173
|
# @param flow_control [FlowControl] per-connection outbound flow controller
|
|
1133
1174
|
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
1134
1175
|
# @param status [Integer] HTTP status code
|
|
@@ -1138,22 +1179,22 @@ module Raptor
|
|
|
1138
1179
|
# @yield once the response headers are queued
|
|
1139
1180
|
# @return [void]
|
|
1140
1181
|
#
|
|
1141
|
-
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, DetachedBody body, ^(Symbol) -> void finished) { () -> void } -> void
|
|
1142
|
-
def
|
|
1182
|
+
# @rbs (TCPSocket | OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, DetachedBody body, ^(Symbol) -> void finished) { () -> void } -> void
|
|
1183
|
+
def write_detached_response(socket, writer, flow_control, stream_id, status, headers, body, finished)
|
|
1143
1184
|
flow_control.check(stream_id)
|
|
1144
1185
|
parser = Http2Parser.new
|
|
1145
1186
|
encoded = parser.encode_response_headers(status, headers)
|
|
1146
1187
|
writer.write_frames(socket, [parser.build_frame(:headers, FLAG_END_HEADERS, stream_id, encoded)])
|
|
1147
1188
|
yield
|
|
1148
1189
|
attached = writer.attach_body(stream_id, body, finished)
|
|
1149
|
-
|
|
1190
|
+
write_reset_stream(socket, writer, stream_id, ERROR_REFUSED_STREAM) unless attached
|
|
1150
1191
|
end
|
|
1151
1192
|
|
|
1152
1193
|
# Writes a Rack response as HTTP/2 frames to the socket, partitioning
|
|
1153
1194
|
# DATA frames through `flow_control` to fit within the peer's windows.
|
|
1154
1195
|
#
|
|
1155
|
-
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
1156
|
-
# @param writer [Writer]
|
|
1196
|
+
# @param socket [TCPSocket, OpenSSL::SSL::SSLSocket] the connection socket
|
|
1197
|
+
# @param writer [Writer] frame writer for the connection
|
|
1157
1198
|
# @param flow_control [FlowControl] per-connection outbound flow controller
|
|
1158
1199
|
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
1159
1200
|
# @param status [Integer] HTTP status code
|
|
@@ -1161,10 +1202,11 @@ module Raptor
|
|
|
1161
1202
|
# @param body [Object] response body responding to each
|
|
1162
1203
|
# @param trailers [Hash] trailing response headers populated by the Rack application
|
|
1163
1204
|
# @param request_method [String] request method used to suppress HEAD response bodies
|
|
1205
|
+
# @yield once the response headers are queued
|
|
1164
1206
|
# @return [String] the response body size in bytes
|
|
1165
1207
|
#
|
|
1166
|
-
# @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
|
|
1167
|
-
def
|
|
1208
|
+
# @rbs (TCPSocket | 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
|
|
1209
|
+
def write_response(socket, writer, flow_control, stream_id, status, headers, body, trailers:, request_method:)
|
|
1168
1210
|
parser = Http2Parser.new
|
|
1169
1211
|
|
|
1170
1212
|
encoded_headers = parser.encode_response_headers(status, headers)
|
|
@@ -1192,14 +1234,14 @@ module Raptor
|
|
|
1192
1234
|
|
|
1193
1235
|
# Sends an HTTP 103 Early Hints response.
|
|
1194
1236
|
#
|
|
1195
|
-
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
1237
|
+
# @param socket [TCPSocket, OpenSSL::SSL::SSLSocket] the connection socket
|
|
1196
1238
|
# @param writer [Writer] frame writer for the connection
|
|
1197
1239
|
# @param flow_control [FlowControl] per-connection outbound flow controller
|
|
1198
1240
|
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
1199
1241
|
# @param hints [Hash] response headers to send as early hints
|
|
1200
1242
|
# @return [void]
|
|
1201
1243
|
#
|
|
1202
|
-
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Hash[String, String | Array[String]] hints) -> void
|
|
1244
|
+
# @rbs (TCPSocket | OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Hash[String, String | Array[String]] hints) -> void
|
|
1203
1245
|
def send_early_hints(socket, writer, flow_control, stream_id, hints)
|
|
1204
1246
|
return if hints.empty?
|
|
1205
1247
|
|
|
@@ -1211,14 +1253,14 @@ module Raptor
|
|
|
1211
1253
|
|
|
1212
1254
|
# Resets one HTTP/2 stream with the given error code.
|
|
1213
1255
|
#
|
|
1214
|
-
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
1256
|
+
# @param socket [TCPSocket, OpenSSL::SSL::SSLSocket] the connection socket
|
|
1215
1257
|
# @param writer [Writer] frame writer for the connection
|
|
1216
1258
|
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
1217
1259
|
# @param error_code [Integer] the HTTP/2 error code
|
|
1218
1260
|
# @return [void]
|
|
1219
1261
|
#
|
|
1220
|
-
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id, Integer error_code) -> void
|
|
1221
|
-
def
|
|
1262
|
+
# @rbs (TCPSocket | OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id, Integer error_code) -> void
|
|
1263
|
+
def write_reset_stream(socket, writer, stream_id, error_code)
|
|
1222
1264
|
parser = Http2Parser.new
|
|
1223
1265
|
frame = parser.build_frame(:rst_stream, 0, stream_id, [error_code].pack("N"))
|
|
1224
1266
|
writer.write_frames(socket, [frame])
|
|
@@ -1226,13 +1268,13 @@ module Raptor
|
|
|
1226
1268
|
|
|
1227
1269
|
# Writes a 500 error response as HTTP/2 frames.
|
|
1228
1270
|
#
|
|
1229
|
-
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
1230
|
-
# @param writer [Writer]
|
|
1271
|
+
# @param socket [TCPSocket, OpenSSL::SSL::SSLSocket] the connection socket
|
|
1272
|
+
# @param writer [Writer] frame writer for the connection
|
|
1231
1273
|
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
1232
1274
|
# @return [void]
|
|
1233
1275
|
#
|
|
1234
|
-
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id) -> void
|
|
1235
|
-
def
|
|
1276
|
+
# @rbs (TCPSocket | OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id) -> void
|
|
1277
|
+
def write_error_response(socket, writer, stream_id)
|
|
1236
1278
|
parser = Http2Parser.new
|
|
1237
1279
|
encoded = parser.encode_headers([[":status", "500"]])
|
|
1238
1280
|
|
|
@@ -1242,31 +1284,18 @@ module Raptor
|
|
|
1242
1284
|
)
|
|
1243
1285
|
end
|
|
1244
1286
|
|
|
1245
|
-
# Instance-level wrapper around {Http.write_access_log} that routes to
|
|
1246
|
-
# the configured `@access_log_io`.
|
|
1247
|
-
#
|
|
1248
|
-
# @param env [Hash] the Rack environment
|
|
1249
|
-
# @param status [Integer] the response status code
|
|
1250
|
-
# @param size [String] the response body size in bytes, or `-` if unknown
|
|
1251
|
-
# @param remote_addr [String] the client IP address
|
|
1252
|
-
# @return [void]
|
|
1253
|
-
#
|
|
1254
|
-
# @rbs (Hash[String, untyped] env, Integer status, String size, String remote_addr) -> void
|
|
1255
|
-
def write_access_log(env, status, size, remote_addr)
|
|
1256
|
-
Http.write_access_log(@access_log_io, env, status, size, remote_addr)
|
|
1257
|
-
end
|
|
1258
|
-
|
|
1259
1287
|
# Builds a Rack environment hash from HTTP/2 headers and body.
|
|
1260
1288
|
#
|
|
1261
1289
|
# @param headers [Array<Array(String, String)>] HTTP/2 header pairs
|
|
1262
1290
|
# @param body [String] the request body
|
|
1263
|
-
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
1291
|
+
# @param socket [TCPSocket, OpenSSL::SSL::SSLSocket] the connection socket
|
|
1264
1292
|
# @param writer [Writer] frame writer for the connection
|
|
1293
|
+
# @param flow_control [FlowControl] per-connection outbound flow controller
|
|
1265
1294
|
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
1266
1295
|
# @param remote_addr [String] the client IP address
|
|
1267
1296
|
# @return [Hash] fully populated Rack environment hash
|
|
1268
1297
|
#
|
|
1269
|
-
# @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]
|
|
1298
|
+
# @rbs (Array[[String, String]] headers, String body, TCPSocket | OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, remote_addr: String) -> Hash[String, untyped]
|
|
1270
1299
|
def build_rack_env(headers, body, socket, writer, flow_control, stream_id, remote_addr:)
|
|
1271
1300
|
env = {}
|
|
1272
1301
|
|
|
@@ -1287,7 +1316,12 @@ module Raptor
|
|
|
1287
1316
|
env[Http::CONTENT_LENGTH] = value
|
|
1288
1317
|
else
|
|
1289
1318
|
rack_key = "HTTP_#{name.upcase.tr("-", "_")}"
|
|
1290
|
-
env[rack_key]
|
|
1319
|
+
if (existing = env[rack_key])
|
|
1320
|
+
separator = rack_key == Rack::HTTP_COOKIE ? "; " : ", "
|
|
1321
|
+
env[rack_key] = "#{existing}#{separator}#{value}"
|
|
1322
|
+
else
|
|
1323
|
+
env[rack_key] = value
|
|
1324
|
+
end
|
|
1291
1325
|
end
|
|
1292
1326
|
end
|
|
1293
1327
|
|
|
@@ -1335,12 +1369,25 @@ module Raptor
|
|
|
1335
1369
|
else
|
|
1336
1370
|
host, port = http_host.split(":", 2)
|
|
1337
1371
|
end
|
|
1338
|
-
env[Rack::SERVER_NAME] ||= host
|
|
1339
|
-
env[Rack::SERVER_PORT] ||= port || @
|
|
1372
|
+
env[Rack::SERVER_NAME] ||= host || Server::DEFAULT_SERVER_NAME
|
|
1373
|
+
env[Rack::SERVER_PORT] ||= port || @server_port_string
|
|
1340
1374
|
else
|
|
1341
1375
|
env[Rack::SERVER_NAME] ||= Server::DEFAULT_SERVER_NAME
|
|
1342
|
-
env[Rack::SERVER_PORT] ||= @
|
|
1376
|
+
env[Rack::SERVER_PORT] ||= @server_port_string
|
|
1343
1377
|
end
|
|
1344
1378
|
end
|
|
1379
|
+
|
|
1380
|
+
# Writes a completed request to the access log.
|
|
1381
|
+
#
|
|
1382
|
+
# @param env [Hash] the Rack environment
|
|
1383
|
+
# @param status [Integer] the response status code
|
|
1384
|
+
# @param size [String] the response body size in bytes, or `-` if unknown
|
|
1385
|
+
# @param remote_addr [String] the client IP address
|
|
1386
|
+
# @return [void]
|
|
1387
|
+
#
|
|
1388
|
+
# @rbs (Hash[String, untyped] env, Integer status, String size, String remote_addr) -> void
|
|
1389
|
+
def write_access_log(env, status, size, remote_addr)
|
|
1390
|
+
Http.write_access_log(@access_log_io, env, status, size, remote_addr)
|
|
1391
|
+
end
|
|
1345
1392
|
end
|
|
1346
1393
|
end
|