raptor 0.20.2 → 0.22.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 +19 -0
- data/README.md +41 -64
- data/docs/raptor-vs-puma.md +22 -20
- 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 +6 -3
- data/lib/raptor/detached_body.rb +293 -0
- data/lib/raptor/http.rb +18 -0
- data/lib/raptor/http1.rb +87 -35
- data/lib/raptor/http2.rb +501 -112
- data/lib/raptor/reactor.rb +992 -50
- data/lib/raptor/server.rb +12 -0
- data/lib/raptor/version.rb +1 -1
- data/lib/raptor.rb +1 -0
- data/sig/generated/raptor/binder.rbs +26 -13
- data/sig/generated/raptor/detached_body.rbs +127 -0
- data/sig/generated/raptor/http.rbs +12 -0
- data/sig/generated/raptor/http1.rbs +22 -16
- data/sig/generated/raptor/http2.rbs +240 -25
- data/sig/generated/raptor/reactor.rbs +388 -19
- metadata +4 -2
data/lib/raptor/http2.rb
CHANGED
|
@@ -4,8 +4,11 @@
|
|
|
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
|
|
|
11
|
+
require_relative "detached_body"
|
|
9
12
|
require_relative "http"
|
|
10
13
|
require_relative "raptor_http2"
|
|
11
14
|
require_relative "thread_locals"
|
|
@@ -14,13 +17,14 @@ module Raptor
|
|
|
14
17
|
# Handles HTTP/2 request processing and Rack application integration.
|
|
15
18
|
#
|
|
16
19
|
class Http2
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
RESPONSE_TRAILERS = "raptor.response_trailers"
|
|
21
|
+
|
|
22
|
+
# Queues concurrent frame writes while the reactor remains the sole
|
|
23
|
+
# owner of the connection socket.
|
|
19
24
|
#
|
|
20
25
|
class Writer
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
# @rbs @state: Atom
|
|
26
|
+
# @rbs @reactor: Reactor?
|
|
27
|
+
# @rbs @connection_id: Integer?
|
|
24
28
|
# @rbs @write_timeout: Integer
|
|
25
29
|
|
|
26
30
|
# Creates a new Writer.
|
|
@@ -30,57 +34,63 @@ module Raptor
|
|
|
30
34
|
#
|
|
31
35
|
# @rbs (write_timeout: Integer) -> void
|
|
32
36
|
def initialize(write_timeout:)
|
|
33
|
-
@
|
|
37
|
+
@reactor = nil
|
|
38
|
+
@connection_id = nil
|
|
34
39
|
@write_timeout = write_timeout
|
|
35
40
|
end
|
|
36
41
|
|
|
37
|
-
#
|
|
38
|
-
#
|
|
42
|
+
# Routes subsequent frame writes through the connection's reactor.
|
|
43
|
+
#
|
|
44
|
+
# @param reactor [Reactor] the reactor that owns the connection
|
|
45
|
+
# @param connection_id [Integer] unique connection identifier
|
|
46
|
+
# @return [void]
|
|
47
|
+
#
|
|
48
|
+
# @rbs (Reactor reactor, Integer connection_id) -> void
|
|
49
|
+
def attach(reactor, connection_id)
|
|
50
|
+
@reactor = reactor
|
|
51
|
+
@connection_id = connection_id
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Queues frames for the reactor to write. Before attachment, writes
|
|
55
|
+
# directly so the writer remains usable during connection setup.
|
|
39
56
|
#
|
|
40
57
|
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
41
|
-
# @param frames [Array<String
|
|
58
|
+
# @param frames [Array<String>, nil] frame bytes to write in order
|
|
42
59
|
# @return [void]
|
|
43
60
|
#
|
|
44
|
-
# @rbs (OpenSSL::SSL::SSLSocket socket, Array[String] frames) -> void
|
|
61
|
+
# @rbs (OpenSSL::SSL::SSLSocket socket, Array[String]? frames) -> void
|
|
45
62
|
def write_frames(socket, frames)
|
|
46
63
|
return if !frames || frames.empty?
|
|
47
64
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
frames
|
|
53
|
-
else
|
|
54
|
-
claimed = false
|
|
55
|
-
current + frames
|
|
56
|
-
end
|
|
65
|
+
if @reactor
|
|
66
|
+
@reactor.write_http2_frames(@connection_id, frames)
|
|
67
|
+
else
|
|
68
|
+
Http.socket_write(socket, frames.join, timeout: @write_timeout) rescue nil
|
|
57
69
|
end
|
|
70
|
+
end
|
|
58
71
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
pending = current
|
|
65
|
-
current.empty? ? IDLE : []
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
break if pending.empty?
|
|
69
|
-
|
|
70
|
-
Http.socket_write(socket, pending.join, timeout: @write_timeout) rescue nil
|
|
71
|
-
end
|
|
72
|
+
# Attaches a response body that outlives its application thread.
|
|
73
|
+
#
|
|
74
|
+
# @rbs (Integer stream_id, DetachedBody body, ^(Symbol) -> void finished) -> bool
|
|
75
|
+
def attach_body(stream_id, body, finished)
|
|
76
|
+
@reactor.attach_http2_body(@connection_id, stream_id, body, finished)
|
|
72
77
|
end
|
|
73
78
|
end
|
|
74
79
|
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
class StreamClosedError < StandardError
|
|
81
|
+
# @rbs () -> String
|
|
82
|
+
def message = "HTTP/2 stream closed"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Coordinates outbound flow control and stream cancellation without
|
|
86
|
+
# polling or mutexes.
|
|
77
87
|
#
|
|
78
88
|
class FlowControl
|
|
79
|
-
ACQUIRE_POLL_INTERVAL = 0.001
|
|
80
|
-
|
|
81
89
|
# @rbs @connection_window: Atom
|
|
82
90
|
# @rbs @stream_windows: Atom
|
|
83
91
|
# @rbs @initial_stream_window: Atom
|
|
92
|
+
# @rbs @cancelled_streams: Atom
|
|
93
|
+
# @rbs @available: AtomicConditionVariable
|
|
84
94
|
|
|
85
95
|
# Creates a new FlowControl with the spec-default windows.
|
|
86
96
|
#
|
|
@@ -91,32 +101,25 @@ module Raptor
|
|
|
91
101
|
@connection_window = Atom.new(DEFAULT_WINDOW_SIZE)
|
|
92
102
|
@stream_windows = Atom.new({})
|
|
93
103
|
@initial_stream_window = Atom.new(DEFAULT_WINDOW_SIZE)
|
|
104
|
+
@cancelled_streams = Atom.new({})
|
|
105
|
+
@available = AtomicConditionVariable.new
|
|
94
106
|
end
|
|
95
107
|
|
|
96
|
-
# Reserves outbound capacity on the given stream,
|
|
108
|
+
# Reserves outbound capacity on the given stream, waiting until at
|
|
97
109
|
# least one byte is available on both the connection and stream
|
|
98
|
-
# windows.
|
|
110
|
+
# windows. Raises when the stream or connection closes.
|
|
99
111
|
#
|
|
100
112
|
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
101
113
|
# @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
114
|
# @return [Integer] the number of bytes the caller may now send
|
|
104
115
|
#
|
|
105
|
-
# @rbs (Integer stream_id, Integer max_bytes
|
|
106
|
-
def acquire(stream_id, max_bytes
|
|
116
|
+
# @rbs (Integer stream_id, Integer max_bytes) -> Integer
|
|
117
|
+
def acquire(stream_id, max_bytes)
|
|
107
118
|
initial = @initial_stream_window.value
|
|
108
119
|
capped = max_bytes < MAX_FRAME_SIZE ? max_bytes : MAX_FRAME_SIZE
|
|
109
120
|
|
|
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
|
|
121
|
+
@available.wait do
|
|
122
|
+
check(stream_id)
|
|
120
123
|
stream_window = @stream_windows.value[stream_id] || initial
|
|
121
124
|
capped_full = capped < stream_window ? capped : stream_window
|
|
122
125
|
granted = capped_full.positive? ? reserve_connection(capped_full) : 0
|
|
@@ -126,11 +129,33 @@ module Raptor
|
|
|
126
129
|
current = windows[stream_id] || initial
|
|
127
130
|
windows.merge(stream_id => current - granted)
|
|
128
131
|
end
|
|
129
|
-
|
|
132
|
+
granted
|
|
130
133
|
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
131
136
|
|
|
132
|
-
|
|
137
|
+
# Reserves outbound capacity without waiting for a window update.
|
|
138
|
+
#
|
|
139
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
140
|
+
# @param max_bytes [Integer] the largest size the caller would like to send
|
|
141
|
+
# @return [Integer] available bytes, or 0 when flow control is exhausted
|
|
142
|
+
#
|
|
143
|
+
# @rbs (Integer stream_id, Integer max_bytes) -> Integer
|
|
144
|
+
def try_acquire(stream_id, max_bytes)
|
|
145
|
+
check(stream_id)
|
|
146
|
+
initial = @initial_stream_window.value
|
|
147
|
+
stream_window = @stream_windows.value[stream_id] || initial
|
|
148
|
+
capped = [max_bytes, MAX_FRAME_SIZE, stream_window].min
|
|
149
|
+
return 0 unless capped.positive?
|
|
150
|
+
|
|
151
|
+
granted = reserve_connection(capped)
|
|
152
|
+
if granted.positive?
|
|
153
|
+
@stream_windows.swap do |windows|
|
|
154
|
+
current = windows[stream_id] || initial
|
|
155
|
+
windows.merge(stream_id => current - granted)
|
|
156
|
+
end
|
|
133
157
|
end
|
|
158
|
+
granted
|
|
134
159
|
end
|
|
135
160
|
|
|
136
161
|
# Increments the connection-level send window by `increment` bytes.
|
|
@@ -141,6 +166,7 @@ module Raptor
|
|
|
141
166
|
# @rbs (Integer increment) -> void
|
|
142
167
|
def add_connection_window(increment)
|
|
143
168
|
@connection_window.swap { |window| window + increment }
|
|
169
|
+
@available.broadcast
|
|
144
170
|
end
|
|
145
171
|
|
|
146
172
|
# Increments the send window for the given stream by `increment` bytes.
|
|
@@ -156,6 +182,7 @@ module Raptor
|
|
|
156
182
|
current = windows[stream_id] || initial
|
|
157
183
|
windows.merge(stream_id => current + increment)
|
|
158
184
|
end
|
|
185
|
+
@available.broadcast
|
|
159
186
|
end
|
|
160
187
|
|
|
161
188
|
# Updates the peer's `SETTINGS_INITIAL_WINDOW_SIZE`. Shifts every
|
|
@@ -174,6 +201,40 @@ module Raptor
|
|
|
174
201
|
@stream_windows.swap do |windows|
|
|
175
202
|
windows.transform_values { |size| size + delta }
|
|
176
203
|
end
|
|
204
|
+
@available.broadcast
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Raises when the given stream can no longer write a response.
|
|
208
|
+
#
|
|
209
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
210
|
+
# @return [void]
|
|
211
|
+
# @raise [StreamClosedError] if the stream or connection has closed
|
|
212
|
+
#
|
|
213
|
+
# @rbs (Integer stream_id) -> void
|
|
214
|
+
def check(stream_id)
|
|
215
|
+
cancelled = @cancelled_streams.value
|
|
216
|
+
raise StreamClosedError if cancelled.key?(0) || cancelled.key?(stream_id)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Cancels pending and future writes for the given stream.
|
|
220
|
+
#
|
|
221
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
222
|
+
# @return [void]
|
|
223
|
+
#
|
|
224
|
+
# @rbs (Integer stream_id) -> void
|
|
225
|
+
def cancel_stream(stream_id)
|
|
226
|
+
@cancelled_streams.swap { |streams| streams.merge(stream_id => true) }
|
|
227
|
+
@available.broadcast
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Cancels pending and future writes for the connection.
|
|
231
|
+
#
|
|
232
|
+
# @return [void]
|
|
233
|
+
#
|
|
234
|
+
# @rbs () -> void
|
|
235
|
+
def close
|
|
236
|
+
@cancelled_streams.swap { |streams| streams.merge(0 => true) }
|
|
237
|
+
@available.broadcast
|
|
177
238
|
end
|
|
178
239
|
|
|
179
240
|
# Discards any per-stream tracking for the given stream.
|
|
@@ -183,8 +244,6 @@ module Raptor
|
|
|
183
244
|
#
|
|
184
245
|
# @rbs (Integer stream_id) -> void
|
|
185
246
|
def discard_stream(stream_id)
|
|
186
|
-
return unless @stream_windows.value.key?(stream_id)
|
|
187
|
-
|
|
188
247
|
@stream_windows.swap do |windows|
|
|
189
248
|
next windows unless windows.key?(stream_id)
|
|
190
249
|
|
|
@@ -192,6 +251,13 @@ module Raptor
|
|
|
192
251
|
pruned.delete(stream_id)
|
|
193
252
|
pruned
|
|
194
253
|
end
|
|
254
|
+
@cancelled_streams.swap do |streams|
|
|
255
|
+
next streams unless streams.key?(stream_id)
|
|
256
|
+
|
|
257
|
+
pruned = streams.dup
|
|
258
|
+
pruned.delete(stream_id)
|
|
259
|
+
pruned
|
|
260
|
+
end
|
|
195
261
|
end
|
|
196
262
|
|
|
197
263
|
private
|
|
@@ -214,6 +280,142 @@ module Raptor
|
|
|
214
280
|
end
|
|
215
281
|
end
|
|
216
282
|
|
|
283
|
+
# Adapts one HTTP/2 response stream to Rack's streaming body interface.
|
|
284
|
+
#
|
|
285
|
+
class ResponseStream
|
|
286
|
+
# @rbs @socket: OpenSSL::SSL::SSLSocket
|
|
287
|
+
# @rbs @writer: Writer
|
|
288
|
+
# @rbs @flow_control: FlowControl
|
|
289
|
+
# @rbs @stream_id: Integer
|
|
290
|
+
# @rbs @trailers: Hash[String, String | Array[String]]
|
|
291
|
+
# @rbs @parser: Http2Parser
|
|
292
|
+
# @rbs @bytes_written: Integer
|
|
293
|
+
# @rbs @closed: bool
|
|
294
|
+
|
|
295
|
+
# Returns the number of response body bytes written to the stream.
|
|
296
|
+
#
|
|
297
|
+
# @return [Integer]
|
|
298
|
+
attr_reader :bytes_written #: Integer
|
|
299
|
+
|
|
300
|
+
# Creates a response stream for a Rack streaming body.
|
|
301
|
+
#
|
|
302
|
+
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
303
|
+
# @param writer [Writer] frame writer for the connection
|
|
304
|
+
# @param flow_control [FlowControl] outbound flow controller
|
|
305
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
306
|
+
# @param trailers [Hash] trailing response headers populated by the Rack application
|
|
307
|
+
# @return [void]
|
|
308
|
+
#
|
|
309
|
+
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Hash[String, String | Array[String]] trailers) -> void
|
|
310
|
+
def initialize(socket, writer, flow_control, stream_id, trailers)
|
|
311
|
+
@socket = socket
|
|
312
|
+
@writer = writer
|
|
313
|
+
@flow_control = flow_control
|
|
314
|
+
@stream_id = stream_id
|
|
315
|
+
@trailers = trailers
|
|
316
|
+
@parser = Http2Parser.new
|
|
317
|
+
@bytes_written = 0
|
|
318
|
+
@closed = false
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Streaming responses are write-only.
|
|
322
|
+
#
|
|
323
|
+
# @raise [IOError]
|
|
324
|
+
#
|
|
325
|
+
# @rbs (*untyped) -> bot
|
|
326
|
+
def read(*)
|
|
327
|
+
raise IOError, "not opened for reading"
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# Writes response body bytes to the stream.
|
|
331
|
+
#
|
|
332
|
+
# @param chunk [String] response body bytes
|
|
333
|
+
# @return [Integer] number of bytes written
|
|
334
|
+
#
|
|
335
|
+
# @rbs (String chunk) -> Integer
|
|
336
|
+
def write(chunk)
|
|
337
|
+
raise IOError, "closed stream" if @closed
|
|
338
|
+
raise TypeError, "body must yield String values" unless chunk.is_a?(String)
|
|
339
|
+
|
|
340
|
+
offset = 0
|
|
341
|
+
while offset < chunk.bytesize
|
|
342
|
+
granted = @flow_control.acquire(@stream_id, chunk.bytesize - offset)
|
|
343
|
+
slice = offset.zero? && granted == chunk.bytesize ? chunk : chunk.byteslice(offset, granted)
|
|
344
|
+
offset += granted
|
|
345
|
+
@writer.write_frames(@socket, [@parser.build_frame(:data, 0, @stream_id, slice)])
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
@bytes_written += chunk.bytesize
|
|
349
|
+
chunk.bytesize
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# Writes response body bytes and returns the stream.
|
|
353
|
+
#
|
|
354
|
+
# @param chunk [String] response body bytes
|
|
355
|
+
# @return [ResponseStream]
|
|
356
|
+
#
|
|
357
|
+
# @rbs (String chunk) -> ResponseStream
|
|
358
|
+
def <<(chunk)
|
|
359
|
+
write(chunk)
|
|
360
|
+
self
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Flushes the stream. Frame writes are already immediate.
|
|
364
|
+
#
|
|
365
|
+
# @return [ResponseStream]
|
|
366
|
+
#
|
|
367
|
+
# @rbs () -> ResponseStream
|
|
368
|
+
def flush
|
|
369
|
+
self
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# Finishes the stream.
|
|
373
|
+
#
|
|
374
|
+
# @return [void]
|
|
375
|
+
#
|
|
376
|
+
# @rbs () -> void
|
|
377
|
+
def close
|
|
378
|
+
return if @closed
|
|
379
|
+
|
|
380
|
+
@flow_control.check(@stream_id)
|
|
381
|
+
@closed = true
|
|
382
|
+
if @trailers.empty?
|
|
383
|
+
frame = @parser.build_frame(:data, FLAG_END_STREAM, @stream_id, nil)
|
|
384
|
+
else
|
|
385
|
+
encoded = @parser.encode_response_trailers(@trailers)
|
|
386
|
+
frame = @parser.build_frame(:headers, FLAG_END_STREAM | FLAG_END_HEADERS, @stream_id, encoded)
|
|
387
|
+
end
|
|
388
|
+
@writer.write_frames(@socket, [frame])
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
# Closes the unsupported read side of the stream.
|
|
392
|
+
#
|
|
393
|
+
# @raise [IOError]
|
|
394
|
+
#
|
|
395
|
+
# @rbs () -> bot
|
|
396
|
+
def close_read
|
|
397
|
+
raise IOError, "closing non-duplex IO for reading"
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# Finishes the writable side of the stream.
|
|
401
|
+
#
|
|
402
|
+
# @return [void]
|
|
403
|
+
#
|
|
404
|
+
# @rbs () -> void
|
|
405
|
+
def close_write
|
|
406
|
+
close
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# Returns whether the stream has finished.
|
|
410
|
+
#
|
|
411
|
+
# @return [Boolean]
|
|
412
|
+
#
|
|
413
|
+
# @rbs () -> bool
|
|
414
|
+
def closed?
|
|
415
|
+
@closed
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
|
|
217
419
|
EAGER_READ_TIMEOUT = 0.001
|
|
218
420
|
EAGER_READ_BUFFER_SIZE = 64 * 1024
|
|
219
421
|
EAGER_MAX_ROUNDS = 8
|
|
@@ -225,6 +427,8 @@ module Raptor
|
|
|
225
427
|
|
|
226
428
|
ERROR_NO_ERROR = 0x0
|
|
227
429
|
ERROR_PROTOCOL_ERROR = 0x1
|
|
430
|
+
ERROR_INTERNAL_ERROR = 0x2
|
|
431
|
+
ERROR_REFUSED_STREAM = 0x7
|
|
228
432
|
|
|
229
433
|
DEFAULT_WINDOW_SIZE = 65_535
|
|
230
434
|
MAX_FRAME_SIZE = 16_384
|
|
@@ -272,6 +476,7 @@ module Raptor
|
|
|
272
476
|
# @rbs @clean_fiber_locals: bool
|
|
273
477
|
# @rbs @on_error: ^(Hash[String, untyped]?, Exception) -> void | nil
|
|
274
478
|
# @rbs @initial_settings_frame: String
|
|
479
|
+
# @rbs @running: AtomicBoolean
|
|
275
480
|
|
|
276
481
|
# Returns the initial server SETTINGS frame to send on every new
|
|
277
482
|
# HTTP/2 connection.
|
|
@@ -302,6 +507,7 @@ module Raptor
|
|
|
302
507
|
@clean_thread_locals = clean_thread_locals
|
|
303
508
|
@clean_fiber_locals = clean_fiber_locals
|
|
304
509
|
@on_error = on_error
|
|
510
|
+
@running = AtomicBoolean.new(true)
|
|
305
511
|
|
|
306
512
|
parser = Http2Parser.new
|
|
307
513
|
settings_payload = parser.build_settings(
|
|
@@ -320,6 +526,22 @@ module Raptor
|
|
|
320
526
|
Writer.new(write_timeout: @write_timeout)
|
|
321
527
|
end
|
|
322
528
|
|
|
529
|
+
# Stops new stream dispatch and asks clients to finish streams already
|
|
530
|
+
# handed to the application.
|
|
531
|
+
#
|
|
532
|
+
# @param reactor [Reactor] the reactor managing HTTP/2 connections
|
|
533
|
+
# @return [void]
|
|
534
|
+
#
|
|
535
|
+
# @rbs (Reactor reactor) -> void
|
|
536
|
+
def shutdown(reactor)
|
|
537
|
+
@running.make_false
|
|
538
|
+
parser = Http2Parser.new
|
|
539
|
+
reactor.drain_http2 do |stream_id|
|
|
540
|
+
payload = [stream_id, ERROR_NO_ERROR].pack("NN")
|
|
541
|
+
parser.build_frame(:goaway, 0, 0, payload)
|
|
542
|
+
end
|
|
543
|
+
end
|
|
544
|
+
|
|
323
545
|
# Returns a Ractor-safe proc that parses HTTP/2 frames from the
|
|
324
546
|
# state hash's buffered bytes.
|
|
325
547
|
#
|
|
@@ -347,7 +569,9 @@ module Raptor
|
|
|
347
569
|
streams = data[:http2_streams] ? data[:http2_streams].dup : {}
|
|
348
570
|
outgoing_frames = []
|
|
349
571
|
completed_requests = []
|
|
572
|
+
cancelled_streams = []
|
|
350
573
|
window_updates = []
|
|
574
|
+
ping_acknowledgements = []
|
|
351
575
|
peer_initial_window_size = nil
|
|
352
576
|
connection_window = data[:http2_window] || DEFAULT_WINDOW_SIZE
|
|
353
577
|
preface_received = data[:http2_preface_received] || false
|
|
@@ -360,7 +584,7 @@ module Raptor
|
|
|
360
584
|
buffer = buffer.byteslice(24..-1) || ""
|
|
361
585
|
preface_received = true
|
|
362
586
|
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)
|
|
587
|
+
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
588
|
end
|
|
365
589
|
end
|
|
366
590
|
|
|
@@ -387,8 +611,9 @@ module Raptor
|
|
|
387
611
|
when :headers
|
|
388
612
|
stream_id = frame[:stream_id]
|
|
389
613
|
header_payload = frame[:payload]
|
|
614
|
+
trailers = streams.key?(stream_id)
|
|
390
615
|
|
|
391
|
-
unless
|
|
616
|
+
unless trailers
|
|
392
617
|
if stream_id.even? || stream_id <= last_client_stream_id
|
|
393
618
|
goaway_error = ERROR_PROTOCOL_ERROR
|
|
394
619
|
break
|
|
@@ -404,14 +629,12 @@ module Raptor
|
|
|
404
629
|
|
|
405
630
|
if frame[:flags].anybits?(FLAG_END_HEADERS)
|
|
406
631
|
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
|
|
632
|
+
streams, completed_requests = complete_header_block(
|
|
633
|
+
streams, completed_requests, outgoing_frames, parser,
|
|
634
|
+
stream_id, decoded_headers, end_stream, trailers
|
|
635
|
+
)
|
|
413
636
|
else
|
|
414
|
-
pending_headers = { stream_id: stream_id, buffer: header_payload, end_stream: end_stream }
|
|
637
|
+
pending_headers = { stream_id: stream_id, buffer: header_payload, end_stream: end_stream, trailers: trailers }
|
|
415
638
|
end
|
|
416
639
|
|
|
417
640
|
when :continuation
|
|
@@ -425,12 +648,10 @@ module Raptor
|
|
|
425
648
|
if frame[:flags].anybits?(FLAG_END_HEADERS)
|
|
426
649
|
stream_id = pending_headers[:stream_id]
|
|
427
650
|
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
|
|
651
|
+
streams, completed_requests = complete_header_block(
|
|
652
|
+
streams, completed_requests, outgoing_frames, parser,
|
|
653
|
+
stream_id, decoded_headers, pending_headers[:end_stream], pending_headers[:trailers]
|
|
654
|
+
)
|
|
434
655
|
pending_headers = nil
|
|
435
656
|
end
|
|
436
657
|
|
|
@@ -475,7 +696,9 @@ module Raptor
|
|
|
475
696
|
window_updates << [frame[:stream_id], increment]
|
|
476
697
|
|
|
477
698
|
when :ping
|
|
478
|
-
if frame[:flags].
|
|
699
|
+
if frame[:flags].anybits?(FLAG_ACK)
|
|
700
|
+
ping_acknowledgements << frame[:payload]
|
|
701
|
+
else
|
|
479
702
|
outgoing_frames << parser.build_frame(:ping, FLAG_ACK, 0, frame[:payload])
|
|
480
703
|
end
|
|
481
704
|
|
|
@@ -483,7 +706,10 @@ module Raptor
|
|
|
483
706
|
break
|
|
484
707
|
|
|
485
708
|
when :rst_stream
|
|
486
|
-
|
|
709
|
+
stream_id = frame[:stream_id]
|
|
710
|
+
streams.delete(stream_id)
|
|
711
|
+
completed_requests.reject! { |request| request[:stream_id] == stream_id }
|
|
712
|
+
cancelled_streams << stream_id
|
|
487
713
|
end
|
|
488
714
|
end
|
|
489
715
|
|
|
@@ -492,8 +718,47 @@ module Raptor
|
|
|
492
718
|
outgoing_frames << parser.build_frame(:goaway, 0, 0, goaway_payload)
|
|
493
719
|
end
|
|
494
720
|
|
|
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)
|
|
721
|
+
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)
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
# Applies one complete request header block to its stream, resetting
|
|
725
|
+
# malformed initial headers or trailers and completing valid trailers.
|
|
726
|
+
#
|
|
727
|
+
# @param streams [Hash] current open-stream map
|
|
728
|
+
# @param completed_requests [Array<Hash>] accumulator of completed stream requests
|
|
729
|
+
# @param outgoing_frames [Array<String>] accumulator of protocol response frames
|
|
730
|
+
# @param parser [Http2Parser] frame parser and encoder
|
|
731
|
+
# @param stream_id [Integer] the stream identifier
|
|
732
|
+
# @param decoded_headers [Array<Array(String, String)>] decoded header pairs
|
|
733
|
+
# @param end_stream [Boolean] whether the source frame had END_STREAM set
|
|
734
|
+
# @param trailers [Boolean] whether this is a trailing header block
|
|
735
|
+
# @return [Array(Hash, Array<Hash>)] updated streams and completed requests
|
|
736
|
+
#
|
|
737
|
+
# @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]]]
|
|
738
|
+
def self.complete_header_block(streams, completed_requests, outgoing_frames, parser, stream_id, decoded_headers, end_stream, trailers)
|
|
739
|
+
invalid = if trailers
|
|
740
|
+
!end_stream || decoded_headers.any? { |name, _value| name.start_with?(":") }
|
|
741
|
+
else
|
|
742
|
+
invalid_pseudo_headers?(decoded_headers)
|
|
743
|
+
end
|
|
744
|
+
|
|
745
|
+
if invalid
|
|
746
|
+
streams.delete(stream_id)
|
|
747
|
+
outgoing_frames << parser.build_frame(:rst_stream, 0, stream_id, [ERROR_PROTOCOL_ERROR].pack("N"))
|
|
748
|
+
elsif trailers
|
|
749
|
+
stream = streams.delete(stream_id)
|
|
750
|
+
completed_requests << {
|
|
751
|
+
stream_id: stream_id,
|
|
752
|
+
headers: stream[:headers],
|
|
753
|
+
body: stream[:body] || ""
|
|
754
|
+
}
|
|
755
|
+
else
|
|
756
|
+
streams, completed_requests = finalize_headers(streams, completed_requests, stream_id, decoded_headers, end_stream)
|
|
757
|
+
end
|
|
758
|
+
|
|
759
|
+
[streams, completed_requests]
|
|
496
760
|
end
|
|
761
|
+
private_class_method :complete_header_block
|
|
497
762
|
|
|
498
763
|
# Merges a decoded header block into the stream's accumulated state,
|
|
499
764
|
# promoting the stream to `completed_requests` when END_STREAM is set.
|
|
@@ -534,7 +799,9 @@ module Raptor
|
|
|
534
799
|
# @param streams [Hash] updated stream states
|
|
535
800
|
# @param outgoing_frames [Array<String>] frames to write to the socket
|
|
536
801
|
# @param completed_requests [Array<Hash>] fully received stream requests
|
|
802
|
+
# @param cancelled_streams [Array<Integer>] streams reset by the peer
|
|
537
803
|
# @param window_updates [Array<Array(Integer, Integer)>] inbound WINDOW_UPDATE pairs as [stream_id, increment]
|
|
804
|
+
# @param ping_acknowledgements [Array<String>] acknowledged PING payloads
|
|
538
805
|
# @param peer_initial_window_size [Integer, nil] new SETTINGS_INITIAL_WINDOW_SIZE announced by the peer
|
|
539
806
|
# @param connection_window [Integer] current connection flow control window
|
|
540
807
|
# @param preface_received [Boolean] whether the connection preface has been received
|
|
@@ -543,8 +810,8 @@ module Raptor
|
|
|
543
810
|
# @param close_connection [Boolean] whether the connection should be closed after writing outgoing frames
|
|
544
811
|
# @return [Hash] frozen result hash
|
|
545
812
|
#
|
|
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)
|
|
813
|
+
# @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]
|
|
814
|
+
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
815
|
result = {
|
|
549
816
|
id: data[:id],
|
|
550
817
|
protocol: :http2,
|
|
@@ -561,7 +828,9 @@ module Raptor
|
|
|
561
828
|
remote_addr: data[:remote_addr],
|
|
562
829
|
url_scheme: data[:url_scheme]
|
|
563
830
|
}
|
|
831
|
+
result[:cancelled_streams] = cancelled_streams unless cancelled_streams.empty?
|
|
564
832
|
result[:window_updates] = window_updates unless window_updates.empty?
|
|
833
|
+
result[:ping_acknowledgements] = ping_acknowledgements unless ping_acknowledgements.empty?
|
|
565
834
|
result[:peer_initial_window_size] = peer_initial_window_size if peer_initial_window_size
|
|
566
835
|
Ractor.make_shareable(result)
|
|
567
836
|
end
|
|
@@ -582,8 +851,15 @@ module Raptor
|
|
|
582
851
|
#
|
|
583
852
|
# @rbs (OpenSSL::SSL::SSLSocket socket, Integer id, Reactor reactor, AtomicThreadPool thread_pool, String remote_addr, String url_scheme) -> void
|
|
584
853
|
def eager_accept(socket, id, reactor, thread_pool, remote_addr, url_scheme)
|
|
854
|
+
unless @running.true?
|
|
855
|
+
socket.close rescue nil
|
|
856
|
+
return
|
|
857
|
+
end
|
|
858
|
+
|
|
585
859
|
writer = create_writer
|
|
586
860
|
flow_control = FlowControl.new
|
|
861
|
+
ping_payload = [id].pack("Q>")
|
|
862
|
+
ping_frame = Http2Parser.new.build_frame(:ping, 0, 0, ping_payload).freeze
|
|
587
863
|
initial_state = {
|
|
588
864
|
id: id,
|
|
589
865
|
protocol: :http2,
|
|
@@ -591,7 +867,19 @@ module Raptor
|
|
|
591
867
|
url_scheme: url_scheme
|
|
592
868
|
}
|
|
593
869
|
|
|
594
|
-
reactor.attach_http2(
|
|
870
|
+
reactor.attach_http2(
|
|
871
|
+
id: id,
|
|
872
|
+
socket: socket,
|
|
873
|
+
state: initial_state,
|
|
874
|
+
writer: writer,
|
|
875
|
+
flow_control: flow_control,
|
|
876
|
+
ping_frame: ping_frame,
|
|
877
|
+
ping_payload: ping_payload
|
|
878
|
+
)
|
|
879
|
+
unless @running.true?
|
|
880
|
+
reactor.close_connection(id)
|
|
881
|
+
return
|
|
882
|
+
end
|
|
595
883
|
|
|
596
884
|
socket.write(@initial_settings_frame) rescue nil
|
|
597
885
|
|
|
@@ -605,7 +893,7 @@ module Raptor
|
|
|
605
893
|
return
|
|
606
894
|
end
|
|
607
895
|
|
|
608
|
-
while socket.pending.positive?
|
|
896
|
+
while socket.respond_to?(:pending) && socket.pending.positive?
|
|
609
897
|
buffer << socket.read_nonblock(socket.pending)
|
|
610
898
|
end
|
|
611
899
|
|
|
@@ -636,8 +924,15 @@ module Raptor
|
|
|
636
924
|
|
|
637
925
|
rounds = 0
|
|
638
926
|
loop do
|
|
639
|
-
|
|
927
|
+
reactor.acknowledge_http2_ping(result[:id], result[:ping_acknowledgements])
|
|
928
|
+
result[:cancelled_streams]&.each do |stream_id|
|
|
929
|
+
flow_control.cancel_stream(stream_id)
|
|
930
|
+
reactor.cancel_http2_body(result[:id], stream_id)
|
|
931
|
+
end
|
|
932
|
+
|
|
933
|
+
if flow_control && (result[:window_updates]&.any? || result[:peer_initial_window_size])
|
|
640
934
|
apply_flow_control_updates(flow_control, result)
|
|
935
|
+
reactor.resume_http2_bodies(result[:id])
|
|
641
936
|
end
|
|
642
937
|
|
|
643
938
|
writer.write_frames(socket, result[:outgoing_frames])
|
|
@@ -651,6 +946,11 @@ module Raptor
|
|
|
651
946
|
stream_id = request[:stream_id]
|
|
652
947
|
remote_addr = result[:remote_addr] || Server::DEFAULT_REMOTE_ADDR
|
|
653
948
|
|
|
949
|
+
unless reactor.dispatch_http2_stream(result[:id], stream_id)
|
|
950
|
+
write_http2_reset_stream(socket, writer, stream_id, ERROR_REFUSED_STREAM)
|
|
951
|
+
next
|
|
952
|
+
end
|
|
953
|
+
|
|
654
954
|
thread_pool << proc do
|
|
655
955
|
dispatch_stream_request(
|
|
656
956
|
socket, writer, flow_control, stream_id,
|
|
@@ -716,7 +1016,7 @@ module Raptor
|
|
|
716
1016
|
buffer = String.new
|
|
717
1017
|
buffer << data
|
|
718
1018
|
|
|
719
|
-
while socket.pending.positive?
|
|
1019
|
+
while socket.respond_to?(:pending) && socket.pending.positive?
|
|
720
1020
|
buffer << socket.read_nonblock(socket.pending)
|
|
721
1021
|
end
|
|
722
1022
|
|
|
@@ -750,13 +1050,57 @@ module Raptor
|
|
|
750
1050
|
#
|
|
751
1051
|
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Array[[String, String]] headers, String body, remote_addr: String) -> void
|
|
752
1052
|
def perform_stream_request(socket, writer, flow_control, stream_id, headers, body, remote_addr:)
|
|
753
|
-
env =
|
|
1053
|
+
env = nil
|
|
1054
|
+
status = nil
|
|
1055
|
+
response_headers = nil
|
|
1056
|
+
response_started = false
|
|
1057
|
+
detached = false
|
|
1058
|
+
|
|
1059
|
+
flow_control.check(stream_id)
|
|
1060
|
+
env = build_rack_env(headers, body, socket, writer, flow_control, stream_id, remote_addr: remote_addr)
|
|
754
1061
|
status, response_headers, response_body = @app.call(env)
|
|
755
1062
|
|
|
756
|
-
|
|
1063
|
+
no_body = env[Rack::REQUEST_METHOD] == "HEAD" || (status >= 100 && status < 200) || status == 204 || status == 304
|
|
1064
|
+
if response_body.is_a?(DetachedBody) && !no_body
|
|
1065
|
+
finished = proc do |reason|
|
|
1066
|
+
error = StreamClosedError.new unless reason == :closed
|
|
1067
|
+
write_access_log(env, status, "-", remote_addr) if @access_log_io
|
|
1068
|
+
Http.call_response_finished(env, status, response_headers, error)
|
|
1069
|
+
flow_control.discard_stream(stream_id)
|
|
1070
|
+
end
|
|
1071
|
+
detached = write_http2_detached_response(socket, writer, flow_control, stream_id, status, response_headers, response_body, finished) do
|
|
1072
|
+
response_started = true
|
|
1073
|
+
end
|
|
1074
|
+
return
|
|
1075
|
+
end
|
|
1076
|
+
|
|
1077
|
+
response_size = write_http2_response(
|
|
1078
|
+
socket,
|
|
1079
|
+
writer,
|
|
1080
|
+
flow_control,
|
|
1081
|
+
stream_id,
|
|
1082
|
+
status,
|
|
1083
|
+
response_headers,
|
|
1084
|
+
response_body,
|
|
1085
|
+
trailers: env[RESPONSE_TRAILERS],
|
|
1086
|
+
request_method: env[Rack::REQUEST_METHOD]
|
|
1087
|
+
) { response_started = true }
|
|
1088
|
+
if response_body.is_a?(DetachedBody)
|
|
1089
|
+
response_body.finish(:closed)
|
|
1090
|
+
elsif response_body.respond_to?(:close)
|
|
1091
|
+
response_body.close
|
|
1092
|
+
end
|
|
757
1093
|
write_access_log(env, status, response_size, remote_addr) if @access_log_io
|
|
1094
|
+
Http.call_response_finished(env, status, response_headers, nil)
|
|
1095
|
+
rescue StreamClosedError => error
|
|
1096
|
+
Http.call_response_finished(env, status, response_headers, error)
|
|
758
1097
|
rescue => error
|
|
759
|
-
|
|
1098
|
+
Http.call_response_finished(env, status, response_headers, error)
|
|
1099
|
+
if response_started
|
|
1100
|
+
write_http2_reset_stream(socket, writer, stream_id, ERROR_INTERNAL_ERROR)
|
|
1101
|
+
else
|
|
1102
|
+
write_http2_error_response(socket, writer, stream_id)
|
|
1103
|
+
end
|
|
760
1104
|
|
|
761
1105
|
if @on_error
|
|
762
1106
|
@on_error.call(env, error) rescue nil
|
|
@@ -764,8 +1108,21 @@ module Raptor
|
|
|
764
1108
|
raise
|
|
765
1109
|
end
|
|
766
1110
|
ensure
|
|
767
|
-
|
|
768
|
-
|
|
1111
|
+
flow_control.discard_stream(stream_id) if flow_control && !detached
|
|
1112
|
+
end
|
|
1113
|
+
|
|
1114
|
+
# Starts a detached HTTP/2 response after writing its response headers.
|
|
1115
|
+
#
|
|
1116
|
+
# @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 } -> bool
|
|
1117
|
+
def write_http2_detached_response(socket, writer, flow_control, stream_id, status, headers, body, finished)
|
|
1118
|
+
flow_control.check(stream_id)
|
|
1119
|
+
parser = Http2Parser.new
|
|
1120
|
+
encoded = parser.encode_response_headers(status, headers)
|
|
1121
|
+
writer.write_frames(socket, [parser.build_frame(:headers, FLAG_END_HEADERS, stream_id, encoded)])
|
|
1122
|
+
yield
|
|
1123
|
+
attached = writer.attach_body(stream_id, body, finished)
|
|
1124
|
+
write_http2_reset_stream(socket, writer, stream_id, ERROR_REFUSED_STREAM) unless attached
|
|
1125
|
+
true
|
|
769
1126
|
end
|
|
770
1127
|
|
|
771
1128
|
# Writes a Rack response as HTTP/2 frames to the socket, partitioning
|
|
@@ -778,44 +1135,69 @@ module Raptor
|
|
|
778
1135
|
# @param status [Integer] HTTP status code
|
|
779
1136
|
# @param headers [Hash] response headers from the Rack application
|
|
780
1137
|
# @param body [Object] response body responding to each
|
|
1138
|
+
# @param trailers [Hash] trailing response headers populated by the Rack application
|
|
1139
|
+
# @param request_method [String] request method used to suppress HEAD response bodies
|
|
781
1140
|
# @return [String] the response body size in bytes
|
|
782
1141
|
#
|
|
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)
|
|
1142
|
+
# @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
|
|
1143
|
+
def write_http2_response(socket, writer, flow_control, stream_id, status, headers, body, trailers:, request_method:)
|
|
785
1144
|
parser = Http2Parser.new
|
|
786
1145
|
|
|
787
1146
|
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?
|
|
1147
|
+
flow_control.check(stream_id)
|
|
1148
|
+
no_body = request_method == "HEAD" || (status >= 100 && status < 200) || status == 204 || status == 304
|
|
1149
|
+
if no_body
|
|
797
1150
|
writer.write_frames(socket, [parser.build_frame(:headers, FLAG_END_STREAM | FLAG_END_HEADERS, stream_id, encoded_headers)])
|
|
1151
|
+
yield if block_given?
|
|
798
1152
|
return "0"
|
|
799
1153
|
end
|
|
800
1154
|
|
|
801
|
-
|
|
1155
|
+
writer.write_frames(socket, [parser.build_frame(:headers, FLAG_END_HEADERS, stream_id, encoded_headers)])
|
|
1156
|
+
yield if block_given?
|
|
802
1157
|
|
|
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
|
|
1158
|
+
stream = ResponseStream.new(socket, writer, flow_control, stream_id, trailers)
|
|
1159
|
+
if body.respond_to?(:each)
|
|
1160
|
+
body.each { |chunk| stream.write(chunk) }
|
|
1161
|
+
else
|
|
1162
|
+
body.call(stream)
|
|
815
1163
|
end
|
|
1164
|
+
stream.close
|
|
816
1165
|
|
|
817
|
-
|
|
818
|
-
|
|
1166
|
+
stream.bytes_written.to_s
|
|
1167
|
+
end
|
|
1168
|
+
|
|
1169
|
+
# Sends an HTTP 103 Early Hints response.
|
|
1170
|
+
#
|
|
1171
|
+
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
1172
|
+
# @param writer [Writer] frame writer for the connection
|
|
1173
|
+
# @param flow_control [FlowControl] per-connection outbound flow controller
|
|
1174
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
1175
|
+
# @param hints [Hash] response headers to send as early hints
|
|
1176
|
+
# @return [void]
|
|
1177
|
+
#
|
|
1178
|
+
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Hash[String, String | Array[String]] hints) -> void
|
|
1179
|
+
def send_early_hints(socket, writer, flow_control, stream_id, hints)
|
|
1180
|
+
return if hints.empty?
|
|
1181
|
+
|
|
1182
|
+
flow_control.check(stream_id)
|
|
1183
|
+
parser = Http2Parser.new
|
|
1184
|
+
encoded = parser.encode_response_headers(103, hints)
|
|
1185
|
+
writer.write_frames(socket, [parser.build_frame(:headers, FLAG_END_HEADERS, stream_id, encoded)])
|
|
1186
|
+
end
|
|
1187
|
+
|
|
1188
|
+
# Resets one HTTP/2 stream with the given error code.
|
|
1189
|
+
#
|
|
1190
|
+
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
1191
|
+
# @param writer [Writer] frame writer for the connection
|
|
1192
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
1193
|
+
# @param error_code [Integer] the HTTP/2 error code
|
|
1194
|
+
# @return [void]
|
|
1195
|
+
#
|
|
1196
|
+
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id, Integer error_code) -> void
|
|
1197
|
+
def write_http2_reset_stream(socket, writer, stream_id, error_code)
|
|
1198
|
+
parser = Http2Parser.new
|
|
1199
|
+
frame = parser.build_frame(:rst_stream, 0, stream_id, [error_code].pack("N"))
|
|
1200
|
+
writer.write_frames(socket, [frame])
|
|
819
1201
|
end
|
|
820
1202
|
|
|
821
1203
|
# Writes a 500 error response as HTTP/2 frames.
|
|
@@ -854,11 +1236,14 @@ module Raptor
|
|
|
854
1236
|
#
|
|
855
1237
|
# @param headers [Array<Array(String, String)>] HTTP/2 header pairs
|
|
856
1238
|
# @param body [String] the request body
|
|
1239
|
+
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
1240
|
+
# @param writer [Writer] frame writer for the connection
|
|
1241
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
857
1242
|
# @param remote_addr [String] the client IP address
|
|
858
1243
|
# @return [Hash] fully populated Rack environment hash
|
|
859
1244
|
#
|
|
860
|
-
# @rbs (Array[[String, String]] headers, String body, remote_addr: String) -> Hash[String, untyped]
|
|
861
|
-
def build_rack_env(headers, body, remote_addr:)
|
|
1245
|
+
# @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]
|
|
1246
|
+
def build_rack_env(headers, body, socket, writer, flow_control, stream_id, remote_addr:)
|
|
862
1247
|
env = {}
|
|
863
1248
|
|
|
864
1249
|
headers.each do |name, value|
|
|
@@ -887,6 +1272,9 @@ module Raptor
|
|
|
887
1272
|
env[Rack::RACK_INPUT] = StringIO.new(body).set_encoding(Encoding::ASCII_8BIT)
|
|
888
1273
|
env[Rack::RACK_ERRORS] = $stderr
|
|
889
1274
|
env[Rack::RACK_RESPONSE_FINISHED] = []
|
|
1275
|
+
env[Rack::RACK_EARLY_HINTS] = proc do |hints|
|
|
1276
|
+
send_early_hints(socket, writer, flow_control, stream_id, hints) rescue nil
|
|
1277
|
+
end
|
|
890
1278
|
env[Rack::RACK_IS_HIJACK] = false
|
|
891
1279
|
|
|
892
1280
|
env[Rack::SCRIPT_NAME] = "" unless env.key?(Rack::SCRIPT_NAME)
|
|
@@ -900,6 +1288,7 @@ module Raptor
|
|
|
900
1288
|
env[Http::REMOTE_ADDR] = remote_addr
|
|
901
1289
|
env[Http::SERVER_SOFTWARE] = Http::SERVER_SOFTWARE_VALUE
|
|
902
1290
|
env[Http::HTTP_VERSION] = SERVER_PROTOCOL
|
|
1291
|
+
env[RESPONSE_TRAILERS] = {}
|
|
903
1292
|
|
|
904
1293
|
populate_server_name_and_port(env)
|
|
905
1294
|
|