raptor 0.2.0 → 0.4.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 +23 -0
- data/README.md +16 -16
- data/ext/raptor_http2/raptor_http2.c +1 -0
- data/lib/rackup/handler/raptor.rb +24 -11
- data/lib/raptor/binder.rb +1 -0
- data/lib/raptor/cli.rb +100 -1
- data/lib/raptor/cluster.rb +96 -26
- data/lib/raptor/http2.rb +333 -44
- data/lib/raptor/reactor.rb +67 -27
- data/lib/raptor/request.rb +196 -69
- data/lib/raptor/server.rb +112 -36
- data/lib/raptor/version.rb +1 -1
- data/lib/raptor.rb +3 -3
- data/sig/generated/raptor/cli.rbs +51 -0
- data/sig/generated/raptor/cluster.rbs +31 -3
- data/sig/generated/raptor/http2.rbs +130 -8
- data/sig/generated/raptor/reactor.rbs +22 -0
- data/sig/generated/raptor/request.rbs +75 -22
- data/sig/generated/raptor/server.rbs +51 -12
- data/sig/generated/raptor.rbs +3 -3
- metadata +1 -1
data/lib/raptor/http2.rb
CHANGED
|
@@ -6,6 +6,7 @@ require "stringio"
|
|
|
6
6
|
require "atomic-ruby/atom"
|
|
7
7
|
require "rack"
|
|
8
8
|
|
|
9
|
+
require_relative "request"
|
|
9
10
|
require_relative "raptor_http2"
|
|
10
11
|
|
|
11
12
|
module Raptor
|
|
@@ -68,34 +69,190 @@ module Raptor
|
|
|
68
69
|
break if pending.empty?
|
|
69
70
|
|
|
70
71
|
pending.each do |frame|
|
|
71
|
-
|
|
72
|
+
Request.socket_write(socket, frame) rescue nil
|
|
72
73
|
end
|
|
73
74
|
end
|
|
74
75
|
end
|
|
75
76
|
end
|
|
76
77
|
|
|
78
|
+
# Per-connection outbound flow-control accounting.
|
|
79
|
+
#
|
|
80
|
+
# Tracks the peer's connection-level and per-stream receive windows so
|
|
81
|
+
# outbound DATA frames respect RFC 7540 §5.2. Threads dispatching stream
|
|
82
|
+
# responses call `acquire` to reserve send capacity; threads applying
|
|
83
|
+
# inbound WINDOW_UPDATE or SETTINGS frames call the mutating methods to
|
|
84
|
+
# replenish it. State is held in a single `Atom` so updates use CAS.
|
|
85
|
+
#
|
|
86
|
+
class FlowControl
|
|
87
|
+
ACQUIRE_POLL_INTERVAL = 0.001
|
|
88
|
+
|
|
89
|
+
# @rbs @connection_window: Atom
|
|
90
|
+
# @rbs @stream_windows: Atom
|
|
91
|
+
# @rbs @initial_stream_window: Atom
|
|
92
|
+
|
|
93
|
+
# Creates a new FlowControl with the spec-default windows.
|
|
94
|
+
#
|
|
95
|
+
# @rbs () -> void
|
|
96
|
+
def initialize
|
|
97
|
+
@connection_window = Atom.new(DEFAULT_WINDOW_SIZE)
|
|
98
|
+
@stream_windows = Atom.new({})
|
|
99
|
+
@initial_stream_window = Atom.new(DEFAULT_WINDOW_SIZE)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Reserves outbound capacity on the given stream, polling until at
|
|
103
|
+
# least one byte is available on both the connection and stream
|
|
104
|
+
# windows. The returned size is capped at `MAX_FRAME_SIZE`.
|
|
105
|
+
#
|
|
106
|
+
# When `end_stream` is true, `max_bytes` fits within the peer's
|
|
107
|
+
# initial stream window, and no per-stream override has been
|
|
108
|
+
# recorded, only the connection window is consulted. The stream
|
|
109
|
+
# closes on this frame, so its remaining send window will not be
|
|
110
|
+
# consulted again and need not be tracked.
|
|
111
|
+
#
|
|
112
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
113
|
+
# @param max_bytes [Integer] the largest size the caller would like to send
|
|
114
|
+
# @param end_stream [Boolean] true when this is the final frame on the stream
|
|
115
|
+
# @return [Integer] the number of bytes the caller may now send
|
|
116
|
+
#
|
|
117
|
+
# @rbs (Integer stream_id, Integer max_bytes, ?end_stream: bool) -> Integer
|
|
118
|
+
def acquire(stream_id, max_bytes, end_stream: false)
|
|
119
|
+
initial = @initial_stream_window.value
|
|
120
|
+
capped = max_bytes < MAX_FRAME_SIZE ? max_bytes : MAX_FRAME_SIZE
|
|
121
|
+
|
|
122
|
+
if end_stream && capped <= initial && !@stream_windows.value.key?(stream_id)
|
|
123
|
+
loop do
|
|
124
|
+
granted = 0
|
|
125
|
+
@connection_window.swap do |window|
|
|
126
|
+
granted = window > capped ? capped : window
|
|
127
|
+
granted > 0 ? window - granted : window
|
|
128
|
+
end
|
|
129
|
+
return granted if granted > 0
|
|
130
|
+
|
|
131
|
+
sleep ACQUIRE_POLL_INTERVAL
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
loop do
|
|
136
|
+
stream_window = @stream_windows.value[stream_id] || initial
|
|
137
|
+
capped_full = capped < stream_window ? capped : stream_window
|
|
138
|
+
|
|
139
|
+
granted = 0
|
|
140
|
+
if capped_full > 0
|
|
141
|
+
@connection_window.swap do |window|
|
|
142
|
+
granted = window > capped_full ? capped_full : window
|
|
143
|
+
granted > 0 ? window - granted : window
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
if granted > 0
|
|
148
|
+
@stream_windows.swap do |s|
|
|
149
|
+
current = s[stream_id] || initial
|
|
150
|
+
s.merge(stream_id => current - granted)
|
|
151
|
+
end
|
|
152
|
+
return granted
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
sleep ACQUIRE_POLL_INTERVAL
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Increments the connection-level send window. Called when the peer
|
|
160
|
+
# sends a WINDOW_UPDATE on stream 0.
|
|
161
|
+
#
|
|
162
|
+
# @param increment [Integer] the byte count to add
|
|
163
|
+
# @return [void]
|
|
164
|
+
#
|
|
165
|
+
# @rbs (Integer increment) -> void
|
|
166
|
+
def add_connection_window(increment)
|
|
167
|
+
@connection_window.swap { |window| window + increment }
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Increments the per-stream send window. Called when the peer sends
|
|
171
|
+
# a WINDOW_UPDATE on a specific stream.
|
|
172
|
+
#
|
|
173
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
174
|
+
# @param increment [Integer] the byte count to add
|
|
175
|
+
# @return [void]
|
|
176
|
+
#
|
|
177
|
+
# @rbs (Integer stream_id, Integer increment) -> void
|
|
178
|
+
def add_stream_window(stream_id, increment)
|
|
179
|
+
initial = @initial_stream_window.value
|
|
180
|
+
@stream_windows.swap do |s|
|
|
181
|
+
current = s[stream_id] || initial
|
|
182
|
+
s.merge(stream_id => current + increment)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Updates the peer's `SETTINGS_INITIAL_WINDOW_SIZE`. Shifts every
|
|
187
|
+
# existing stream window by the delta as required by RFC 7540 §6.9.2.
|
|
188
|
+
#
|
|
189
|
+
# @param new_size [Integer] the peer's new initial window size
|
|
190
|
+
# @return [void]
|
|
191
|
+
#
|
|
192
|
+
# @rbs (Integer new_size) -> void
|
|
193
|
+
def set_initial_stream_window(new_size)
|
|
194
|
+
old = @initial_stream_window.value
|
|
195
|
+
@initial_stream_window.swap { new_size }
|
|
196
|
+
delta = new_size - old
|
|
197
|
+
return if delta.zero?
|
|
198
|
+
|
|
199
|
+
@stream_windows.swap do |s|
|
|
200
|
+
s.transform_values { |size| size + delta }
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Discards any per-stream tracking for the given stream. Called
|
|
205
|
+
# after a stream closes so `@stream_windows` does not grow without
|
|
206
|
+
# bound across the lifetime of a connection.
|
|
207
|
+
#
|
|
208
|
+
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
209
|
+
# @return [void]
|
|
210
|
+
#
|
|
211
|
+
# @rbs (Integer stream_id) -> void
|
|
212
|
+
def discard_stream(stream_id)
|
|
213
|
+
return unless @stream_windows.value.key?(stream_id)
|
|
214
|
+
|
|
215
|
+
@stream_windows.swap do |s|
|
|
216
|
+
next s unless s.key?(stream_id)
|
|
217
|
+
|
|
218
|
+
new = s.dup
|
|
219
|
+
new.delete(stream_id)
|
|
220
|
+
new
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
77
225
|
FLAG_END_STREAM = 0x1
|
|
78
226
|
FLAG_END_HEADERS = 0x4
|
|
79
227
|
FLAG_ACK = 0x1
|
|
80
228
|
FLAG_PRIORITY = 0x20
|
|
81
229
|
|
|
230
|
+
ERROR_NO_ERROR = 0x0
|
|
231
|
+
ERROR_PROTOCOL_ERROR = 0x1
|
|
232
|
+
|
|
233
|
+
DEFAULT_WINDOW_SIZE = 65_535
|
|
234
|
+
MAX_FRAME_SIZE = 16_384
|
|
235
|
+
|
|
82
236
|
SERVER_PROTOCOL = "HTTP/2"
|
|
83
237
|
RACK_HEADER_PREFIX = "rack."
|
|
84
238
|
HOP_BY_HOP_HEADERS = Set.new(%w[connection transfer-encoding keep-alive upgrade proxy-connection]).freeze
|
|
85
239
|
|
|
86
240
|
# @rbs @app: ^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped]
|
|
87
241
|
# @rbs @server_port: Integer
|
|
242
|
+
# @rbs @on_error: ^(Hash[String, untyped]?, Exception) -> void | nil
|
|
88
243
|
|
|
89
244
|
# Creates a new Http2 handler.
|
|
90
245
|
#
|
|
91
246
|
# @param app [#call] the Rack application to dispatch requests to
|
|
92
247
|
# @param server_port [Integer] port number used to populate SERVER_PORT in the Rack env
|
|
248
|
+
# @param on_error [#call, nil] callback invoked with (env, exception) when the Rack app raises
|
|
93
249
|
# @return [void]
|
|
94
250
|
#
|
|
95
|
-
# @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Integer server_port) -> void
|
|
96
|
-
def initialize(app, server_port)
|
|
251
|
+
# @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Integer server_port, ?on_error: ^(Hash[String, untyped]?, Exception) -> void | nil) -> void
|
|
252
|
+
def initialize(app, server_port, on_error: nil)
|
|
97
253
|
@app = app
|
|
98
254
|
@server_port = server_port
|
|
255
|
+
@on_error = on_error
|
|
99
256
|
end
|
|
100
257
|
|
|
101
258
|
# Builds the initial server SETTINGS frame to send on connection establishment.
|
|
@@ -107,7 +264,7 @@ module Raptor
|
|
|
107
264
|
parser = Http2Parser.new
|
|
108
265
|
settings_payload = parser.build_settings(
|
|
109
266
|
max_concurrent_streams: 100,
|
|
110
|
-
initial_window_size:
|
|
267
|
+
initial_window_size: DEFAULT_WINDOW_SIZE
|
|
111
268
|
)
|
|
112
269
|
parser.build_frame(:settings, 0, 0, settings_payload)
|
|
113
270
|
end
|
|
@@ -129,15 +286,20 @@ module Raptor
|
|
|
129
286
|
streams = data[:http2_streams] ? data[:http2_streams].dup : {}
|
|
130
287
|
outgoing_frames = []
|
|
131
288
|
completed_requests = []
|
|
132
|
-
|
|
289
|
+
window_updates = []
|
|
290
|
+
peer_initial_window_size = nil
|
|
291
|
+
connection_window = data[:http2_window] || DEFAULT_WINDOW_SIZE
|
|
133
292
|
preface_received = data[:http2_preface_received] || false
|
|
293
|
+
last_client_stream_id = data[:http2_last_client_stream_id] || 0
|
|
294
|
+
pending_headers = data[:http2_pending_headers]
|
|
295
|
+
goaway_error = nil
|
|
134
296
|
|
|
135
297
|
unless preface_received
|
|
136
298
|
if buffer.bytesize >= 24 && buffer.byteslice(0, 24) == Http2Parser.connection_preface
|
|
137
299
|
buffer = buffer.byteslice(24..-1) || ""
|
|
138
300
|
preface_received = true
|
|
139
301
|
else
|
|
140
|
-
return build_result(data, buffer, hpack_table, streams, outgoing_frames, completed_requests, connection_window, preface_received)
|
|
302
|
+
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)
|
|
141
303
|
end
|
|
142
304
|
end
|
|
143
305
|
|
|
@@ -148,9 +310,16 @@ module Raptor
|
|
|
148
310
|
frame, consumed = parsed
|
|
149
311
|
buffer = buffer.byteslice(consumed..-1) || ""
|
|
150
312
|
|
|
313
|
+
if pending_headers && frame[:type] != :continuation
|
|
314
|
+
goaway_error = ERROR_PROTOCOL_ERROR
|
|
315
|
+
break
|
|
316
|
+
end
|
|
317
|
+
|
|
151
318
|
case frame[:type]
|
|
152
319
|
when :settings
|
|
153
320
|
if (frame[:flags] & FLAG_ACK).zero?
|
|
321
|
+
parsed_settings = parser.parse_settings(frame[:payload])
|
|
322
|
+
peer_initial_window_size = parsed_settings[:initial_window_size] if parsed_settings.key?(:initial_window_size)
|
|
154
323
|
outgoing_frames << parser.build_frame(:settings, FLAG_ACK, 0, nil)
|
|
155
324
|
end
|
|
156
325
|
|
|
@@ -158,37 +327,58 @@ module Raptor
|
|
|
158
327
|
stream_id = frame[:stream_id]
|
|
159
328
|
header_payload = frame[:payload]
|
|
160
329
|
|
|
330
|
+
unless streams.key?(stream_id)
|
|
331
|
+
if stream_id.even? || stream_id <= last_client_stream_id
|
|
332
|
+
goaway_error = ERROR_PROTOCOL_ERROR
|
|
333
|
+
break
|
|
334
|
+
end
|
|
335
|
+
last_client_stream_id = stream_id
|
|
336
|
+
end
|
|
337
|
+
|
|
161
338
|
if (frame[:flags] & FLAG_PRIORITY) != 0
|
|
162
339
|
header_payload = header_payload.byteslice(5..-1) || ""
|
|
163
340
|
end
|
|
164
341
|
|
|
165
|
-
|
|
166
|
-
stream = streams[stream_id] || {}
|
|
167
|
-
stream = stream.merge(headers: decoded_headers)
|
|
168
|
-
|
|
169
|
-
if (frame[:flags] & FLAG_END_STREAM) != 0
|
|
170
|
-
stream = stream.merge(end_stream: true)
|
|
171
|
-
completed_requests << {
|
|
172
|
-
stream_id: stream_id,
|
|
173
|
-
headers: decoded_headers,
|
|
174
|
-
body: stream[:body] || ""
|
|
175
|
-
}
|
|
342
|
+
end_stream = (frame[:flags] & FLAG_END_STREAM) != 0
|
|
176
343
|
|
|
177
|
-
|
|
344
|
+
if (frame[:flags] & FLAG_END_HEADERS) != 0
|
|
345
|
+
decoded_headers, hpack_table = parser.parse_headers(header_payload, hpack_table)
|
|
346
|
+
streams, completed_requests = finalize_headers(streams, completed_requests, stream_id, decoded_headers, end_stream)
|
|
178
347
|
else
|
|
179
|
-
|
|
348
|
+
pending_headers = { stream_id: stream_id, buffer: header_payload, end_stream: end_stream }
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
when :continuation
|
|
352
|
+
if pending_headers.nil? || frame[:stream_id] != pending_headers[:stream_id]
|
|
353
|
+
goaway_error = ERROR_PROTOCOL_ERROR
|
|
354
|
+
break
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
pending_headers = pending_headers.merge(buffer: pending_headers[:buffer] + frame[:payload])
|
|
358
|
+
|
|
359
|
+
if (frame[:flags] & FLAG_END_HEADERS) != 0
|
|
360
|
+
stream_id = pending_headers[:stream_id]
|
|
361
|
+
decoded_headers, hpack_table = parser.parse_headers(pending_headers[:buffer], hpack_table)
|
|
362
|
+
streams, completed_requests = finalize_headers(streams, completed_requests, stream_id, decoded_headers, pending_headers[:end_stream])
|
|
363
|
+
pending_headers = nil
|
|
180
364
|
end
|
|
181
365
|
|
|
182
366
|
when :data
|
|
183
367
|
stream_id = frame[:stream_id]
|
|
184
|
-
|
|
368
|
+
|
|
369
|
+
unless streams.key?(stream_id)
|
|
370
|
+
goaway_error = ERROR_PROTOCOL_ERROR
|
|
371
|
+
break
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
stream = streams[stream_id]
|
|
185
375
|
existing_body = stream[:body] || ""
|
|
186
376
|
stream = stream.merge(body: existing_body + frame[:payload])
|
|
187
377
|
|
|
188
378
|
if frame[:payload].bytesize.positive?
|
|
189
379
|
connection_window -= frame[:payload].bytesize
|
|
190
|
-
if connection_window <
|
|
191
|
-
increment =
|
|
380
|
+
if connection_window < DEFAULT_WINDOW_SIZE / 2
|
|
381
|
+
increment = DEFAULT_WINDOW_SIZE - connection_window
|
|
192
382
|
wu_payload = [increment].pack("N")
|
|
193
383
|
outgoing_frames << parser.build_frame(:window_update, 0, 0, wu_payload)
|
|
194
384
|
outgoing_frames << parser.build_frame(:window_update, 0, stream_id, wu_payload)
|
|
@@ -210,7 +400,8 @@ module Raptor
|
|
|
210
400
|
end
|
|
211
401
|
|
|
212
402
|
when :window_update
|
|
213
|
-
parser.parse_window_update(frame[:payload])
|
|
403
|
+
increment = parser.parse_window_update(frame[:payload])
|
|
404
|
+
window_updates << [frame[:stream_id], increment]
|
|
214
405
|
|
|
215
406
|
when :ping
|
|
216
407
|
if (frame[:flags] & FLAG_ACK).zero?
|
|
@@ -225,8 +416,44 @@ module Raptor
|
|
|
225
416
|
end
|
|
226
417
|
end
|
|
227
418
|
|
|
228
|
-
|
|
419
|
+
if goaway_error
|
|
420
|
+
goaway_payload = [last_client_stream_id, goaway_error].pack("NN")
|
|
421
|
+
outgoing_frames << parser.build_frame(:goaway, 0, 0, goaway_payload)
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
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.nil?)
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# Merges a decoded header block into the stream's accumulated state,
|
|
428
|
+
# promoting the stream to `completed_requests` when END_STREAM is set.
|
|
429
|
+
#
|
|
430
|
+
# @param streams [Hash] current open-stream map
|
|
431
|
+
# @param completed_requests [Array<Hash>] accumulator of completed stream requests
|
|
432
|
+
# @param stream_id [Integer] the stream identifier
|
|
433
|
+
# @param decoded_headers [Array<Array(String, String)>] decoded header pairs
|
|
434
|
+
# @param end_stream [Boolean] whether the source frame had END_STREAM set
|
|
435
|
+
# @return [Array(Hash, Array<Hash>)] updated streams and completed_requests
|
|
436
|
+
#
|
|
437
|
+
# @rbs (Hash[Integer, Hash[Symbol, untyped]] streams, Array[Hash[Symbol, untyped]] completed_requests, Integer stream_id, Array[[String, String]] decoded_headers, bool end_stream) -> [Hash[Integer, Hash[Symbol, untyped]], Array[Hash[Symbol, untyped]]]
|
|
438
|
+
def self.finalize_headers(streams, completed_requests, stream_id, decoded_headers, end_stream)
|
|
439
|
+
stream = streams[stream_id] || {}
|
|
440
|
+
stream = stream.merge(headers: decoded_headers)
|
|
441
|
+
|
|
442
|
+
if end_stream
|
|
443
|
+
completed_requests << {
|
|
444
|
+
stream_id: stream_id,
|
|
445
|
+
headers: decoded_headers,
|
|
446
|
+
body: stream[:body] || ""
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
streams.delete(stream_id)
|
|
450
|
+
else
|
|
451
|
+
streams[stream_id] = stream
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
[streams, completed_requests]
|
|
229
455
|
end
|
|
456
|
+
private_class_method :finalize_headers
|
|
230
457
|
|
|
231
458
|
# Builds a frozen result hash from the current processing state.
|
|
232
459
|
#
|
|
@@ -236,13 +463,18 @@ module Raptor
|
|
|
236
463
|
# @param streams [Hash] updated stream states
|
|
237
464
|
# @param outgoing_frames [Array<String>] frames to write to the socket
|
|
238
465
|
# @param completed_requests [Array<Hash>] fully received stream requests
|
|
466
|
+
# @param window_updates [Array<Array(Integer, Integer)>] inbound WINDOW_UPDATE pairs as [stream_id, increment]
|
|
467
|
+
# @param peer_initial_window_size [Integer, nil] new SETTINGS_INITIAL_WINDOW_SIZE announced by the peer
|
|
239
468
|
# @param connection_window [Integer] current connection flow control window
|
|
240
469
|
# @param preface_received [Boolean] whether the connection preface has been received
|
|
470
|
+
# @param last_client_stream_id [Integer] highest client-initiated stream ID seen
|
|
471
|
+
# @param pending_headers [Hash, nil] in-progress HEADERS+CONTINUATION assembly
|
|
472
|
+
# @param close_connection [Boolean] whether the connection should be closed after writing outgoing frames
|
|
241
473
|
# @return [Hash] frozen result hash
|
|
242
474
|
#
|
|
243
|
-
# @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, Integer connection_window, bool preface_received) -> Hash[Symbol, untyped]
|
|
244
|
-
def self.build_result(data, buffer, hpack_table, streams, outgoing_frames, completed_requests, connection_window, preface_received)
|
|
245
|
-
|
|
475
|
+
# @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]
|
|
476
|
+
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)
|
|
477
|
+
result = {
|
|
246
478
|
id: data[:id],
|
|
247
479
|
protocol: :http2,
|
|
248
480
|
buffer: buffer || "",
|
|
@@ -250,11 +482,17 @@ module Raptor
|
|
|
250
482
|
http2_streams: streams,
|
|
251
483
|
http2_window: connection_window,
|
|
252
484
|
http2_preface_received: preface_received,
|
|
485
|
+
http2_last_client_stream_id: last_client_stream_id,
|
|
486
|
+
http2_pending_headers: pending_headers,
|
|
253
487
|
outgoing_frames: outgoing_frames,
|
|
254
488
|
completed_requests: completed_requests,
|
|
489
|
+
close_connection: close_connection,
|
|
255
490
|
remote_addr: data[:remote_addr],
|
|
256
491
|
url_scheme: data[:url_scheme]
|
|
257
|
-
}
|
|
492
|
+
}
|
|
493
|
+
result[:window_updates] = window_updates unless window_updates.empty?
|
|
494
|
+
result[:peer_initial_window_size] = peer_initial_window_size if peer_initial_window_size
|
|
495
|
+
Ractor.make_shareable(result)
|
|
258
496
|
end
|
|
259
497
|
private_class_method :build_result
|
|
260
498
|
|
|
@@ -274,9 +512,19 @@ module Raptor
|
|
|
274
512
|
return unless socket
|
|
275
513
|
|
|
276
514
|
writer = reactor.writer_for(result[:id])
|
|
515
|
+
flow_control = reactor.flow_control_for(result[:id])
|
|
516
|
+
|
|
517
|
+
if flow_control && (result[:window_updates] || result[:peer_initial_window_size])
|
|
518
|
+
apply_flow_control_updates(flow_control, result)
|
|
519
|
+
end
|
|
277
520
|
|
|
278
521
|
writer.write_frames(socket, result[:outgoing_frames])
|
|
279
522
|
|
|
523
|
+
if result[:close_connection]
|
|
524
|
+
reactor.close_connection(result[:id])
|
|
525
|
+
return
|
|
526
|
+
end
|
|
527
|
+
|
|
280
528
|
reactor.update_http2_state(result)
|
|
281
529
|
|
|
282
530
|
result[:completed_requests]&.each do |request|
|
|
@@ -285,7 +533,7 @@ module Raptor
|
|
|
285
533
|
|
|
286
534
|
thread_pool << proc do
|
|
287
535
|
dispatch_stream_request(
|
|
288
|
-
socket, writer, stream_id,
|
|
536
|
+
socket, writer, flow_control, stream_id,
|
|
289
537
|
request[:headers], request[:body],
|
|
290
538
|
remote_addr: remote_addr
|
|
291
539
|
)
|
|
@@ -295,42 +543,75 @@ module Raptor
|
|
|
295
543
|
|
|
296
544
|
private
|
|
297
545
|
|
|
546
|
+
# Applies inbound flow-control updates from a parsed result to the
|
|
547
|
+
# connection's `FlowControl`.
|
|
548
|
+
#
|
|
549
|
+
# @param flow_control [FlowControl] the per-connection flow controller
|
|
550
|
+
# @param result [Hash] the parsed result from `process_frames`
|
|
551
|
+
# @return [void]
|
|
552
|
+
#
|
|
553
|
+
# @rbs (FlowControl flow_control, Hash[Symbol, untyped] result) -> void
|
|
554
|
+
def apply_flow_control_updates(flow_control, result)
|
|
555
|
+
result[:window_updates]&.each do |stream_id, increment|
|
|
556
|
+
if stream_id.zero?
|
|
557
|
+
flow_control.add_connection_window(increment)
|
|
558
|
+
else
|
|
559
|
+
flow_control.add_stream_window(stream_id, increment)
|
|
560
|
+
end
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
if (new_size = result[:peer_initial_window_size])
|
|
564
|
+
flow_control.set_initial_stream_window(new_size)
|
|
565
|
+
end
|
|
566
|
+
end
|
|
567
|
+
|
|
298
568
|
# Dispatches a completed stream request to the Rack app and writes
|
|
299
569
|
# the response back as HTTP/2 frames.
|
|
300
570
|
#
|
|
301
571
|
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
302
572
|
# @param writer [Writer] lock-free frame writer for the connection
|
|
573
|
+
# @param flow_control [FlowControl] per-connection outbound flow controller
|
|
303
574
|
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
304
575
|
# @param headers [Array<Array(String, String)>] request headers
|
|
305
576
|
# @param body [String] request body
|
|
306
577
|
# @param remote_addr [String] the client IP address
|
|
307
578
|
# @return [void]
|
|
308
579
|
#
|
|
309
|
-
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id, Array[[String, String]] headers, String body, remote_addr: String) -> void
|
|
310
|
-
def dispatch_stream_request(socket, writer, stream_id, headers, body, remote_addr:)
|
|
580
|
+
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Array[[String, String]] headers, String body, remote_addr: String) -> void
|
|
581
|
+
def dispatch_stream_request(socket, writer, flow_control, stream_id, headers, body, remote_addr:)
|
|
311
582
|
env = build_rack_env(headers, body, remote_addr: remote_addr)
|
|
312
583
|
status, response_headers, response_body = @app.call(env)
|
|
313
584
|
|
|
314
|
-
write_http2_response(socket, writer, stream_id, status, response_headers, response_body)
|
|
315
|
-
rescue
|
|
585
|
+
write_http2_response(socket, writer, flow_control, stream_id, status, response_headers, response_body)
|
|
586
|
+
rescue => error
|
|
316
587
|
write_http2_error_response(socket, writer, stream_id)
|
|
317
|
-
|
|
588
|
+
|
|
589
|
+
if @on_error
|
|
590
|
+
@on_error.call(env, error) rescue nil
|
|
591
|
+
else
|
|
592
|
+
raise
|
|
593
|
+
end
|
|
318
594
|
ensure
|
|
319
595
|
response_body.close if response_body.respond_to?(:close)
|
|
596
|
+
flow_control.discard_stream(stream_id) if flow_control
|
|
320
597
|
end
|
|
321
598
|
|
|
322
599
|
# Writes a Rack response as HTTP/2 frames to the socket.
|
|
323
600
|
#
|
|
601
|
+
# DATA frames are partitioned through `flow_control` so each write fits
|
|
602
|
+
# within the peer's per-stream and connection windows.
|
|
603
|
+
#
|
|
324
604
|
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
325
605
|
# @param writer [Writer] lock-free frame writer for the connection
|
|
606
|
+
# @param flow_control [FlowControl] per-connection outbound flow controller
|
|
326
607
|
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
327
608
|
# @param status [Integer] HTTP status code
|
|
328
609
|
# @param headers [Hash] response headers from the Rack application
|
|
329
610
|
# @param body [Object] response body responding to each
|
|
330
611
|
# @return [void]
|
|
331
612
|
#
|
|
332
|
-
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> void
|
|
333
|
-
def write_http2_response(socket, writer, stream_id, status, headers, body)
|
|
613
|
+
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, FlowControl flow_control, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> void
|
|
614
|
+
def write_http2_response(socket, writer, flow_control, stream_id, status, headers, body)
|
|
334
615
|
parser = Http2Parser.new
|
|
335
616
|
|
|
336
617
|
header_pairs = [[":status", status.to_s]]
|
|
@@ -350,16 +631,24 @@ module Raptor
|
|
|
350
631
|
body_chunks = []
|
|
351
632
|
body.each { |chunk| body_chunks << chunk unless chunk.empty? }
|
|
352
633
|
|
|
353
|
-
frames = []
|
|
354
634
|
if body_chunks.empty?
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
635
|
+
writer.write_frames(socket, [parser.build_frame(:headers, FLAG_END_STREAM | FLAG_END_HEADERS, stream_id, encoded_headers)])
|
|
636
|
+
return
|
|
637
|
+
end
|
|
358
638
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
639
|
+
frames = [parser.build_frame(:headers, FLAG_END_HEADERS, stream_id, encoded_headers)]
|
|
640
|
+
|
|
641
|
+
last_chunk_index = body_chunks.size - 1
|
|
642
|
+
body_chunks.each_with_index do |chunk, chunk_index|
|
|
643
|
+
offset = 0
|
|
644
|
+
while offset < chunk.bytesize
|
|
645
|
+
remaining = chunk.bytesize - offset
|
|
646
|
+
last_frame = chunk_index == last_chunk_index && remaining <= MAX_FRAME_SIZE
|
|
647
|
+
granted = flow_control.acquire(stream_id, remaining, end_stream: last_frame)
|
|
648
|
+
slice = offset == 0 && granted == chunk.bytesize ? chunk : chunk.byteslice(offset, granted)
|
|
649
|
+
offset += granted
|
|
650
|
+
end_stream = chunk_index == last_chunk_index && offset == chunk.bytesize
|
|
651
|
+
frames << parser.build_frame(:data, end_stream ? FLAG_END_STREAM : 0, stream_id, slice)
|
|
363
652
|
end
|
|
364
653
|
end
|
|
365
654
|
|