raptor 0.3.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 +13 -0
- data/README.md +13 -13
- data/ext/raptor_http2/raptor_http2.c +1 -0
- data/lib/rackup/handler/raptor.rb +3 -2
- data/lib/raptor/cli.rb +20 -4
- data/lib/raptor/cluster.rb +16 -10
- data/lib/raptor/http2.rb +321 -40
- data/lib/raptor/reactor.rb +67 -27
- data/lib/raptor/request.rb +100 -49
- data/lib/raptor/server.rb +112 -36
- data/lib/raptor/version.rb +1 -1
- data/sig/generated/raptor/cli.rbs +14 -0
- data/sig/generated/raptor/cluster.rbs +2 -2
- data/sig/generated/raptor/http2.rbs +125 -6
- data/sig/generated/raptor/reactor.rbs +22 -0
- data/sig/generated/raptor/request.rbs +34 -13
- data/sig/generated/raptor/server.rbs +51 -12
- 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,17 +69,170 @@ 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
|
|
@@ -110,7 +264,7 @@ module Raptor
|
|
|
110
264
|
parser = Http2Parser.new
|
|
111
265
|
settings_payload = parser.build_settings(
|
|
112
266
|
max_concurrent_streams: 100,
|
|
113
|
-
initial_window_size:
|
|
267
|
+
initial_window_size: DEFAULT_WINDOW_SIZE
|
|
114
268
|
)
|
|
115
269
|
parser.build_frame(:settings, 0, 0, settings_payload)
|
|
116
270
|
end
|
|
@@ -132,15 +286,20 @@ module Raptor
|
|
|
132
286
|
streams = data[:http2_streams] ? data[:http2_streams].dup : {}
|
|
133
287
|
outgoing_frames = []
|
|
134
288
|
completed_requests = []
|
|
135
|
-
|
|
289
|
+
window_updates = []
|
|
290
|
+
peer_initial_window_size = nil
|
|
291
|
+
connection_window = data[:http2_window] || DEFAULT_WINDOW_SIZE
|
|
136
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
|
|
137
296
|
|
|
138
297
|
unless preface_received
|
|
139
298
|
if buffer.bytesize >= 24 && buffer.byteslice(0, 24) == Http2Parser.connection_preface
|
|
140
299
|
buffer = buffer.byteslice(24..-1) || ""
|
|
141
300
|
preface_received = true
|
|
142
301
|
else
|
|
143
|
-
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)
|
|
144
303
|
end
|
|
145
304
|
end
|
|
146
305
|
|
|
@@ -151,9 +310,16 @@ module Raptor
|
|
|
151
310
|
frame, consumed = parsed
|
|
152
311
|
buffer = buffer.byteslice(consumed..-1) || ""
|
|
153
312
|
|
|
313
|
+
if pending_headers && frame[:type] != :continuation
|
|
314
|
+
goaway_error = ERROR_PROTOCOL_ERROR
|
|
315
|
+
break
|
|
316
|
+
end
|
|
317
|
+
|
|
154
318
|
case frame[:type]
|
|
155
319
|
when :settings
|
|
156
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)
|
|
157
323
|
outgoing_frames << parser.build_frame(:settings, FLAG_ACK, 0, nil)
|
|
158
324
|
end
|
|
159
325
|
|
|
@@ -161,37 +327,58 @@ module Raptor
|
|
|
161
327
|
stream_id = frame[:stream_id]
|
|
162
328
|
header_payload = frame[:payload]
|
|
163
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
|
+
|
|
164
338
|
if (frame[:flags] & FLAG_PRIORITY) != 0
|
|
165
339
|
header_payload = header_payload.byteslice(5..-1) || ""
|
|
166
340
|
end
|
|
167
341
|
|
|
168
|
-
|
|
169
|
-
stream = streams[stream_id] || {}
|
|
170
|
-
stream = stream.merge(headers: decoded_headers)
|
|
171
|
-
|
|
172
|
-
if (frame[:flags] & FLAG_END_STREAM) != 0
|
|
173
|
-
stream = stream.merge(end_stream: true)
|
|
174
|
-
completed_requests << {
|
|
175
|
-
stream_id: stream_id,
|
|
176
|
-
headers: decoded_headers,
|
|
177
|
-
body: stream[:body] || ""
|
|
178
|
-
}
|
|
342
|
+
end_stream = (frame[:flags] & FLAG_END_STREAM) != 0
|
|
179
343
|
|
|
180
|
-
|
|
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)
|
|
181
347
|
else
|
|
182
|
-
|
|
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
|
|
183
364
|
end
|
|
184
365
|
|
|
185
366
|
when :data
|
|
186
367
|
stream_id = frame[:stream_id]
|
|
187
|
-
|
|
368
|
+
|
|
369
|
+
unless streams.key?(stream_id)
|
|
370
|
+
goaway_error = ERROR_PROTOCOL_ERROR
|
|
371
|
+
break
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
stream = streams[stream_id]
|
|
188
375
|
existing_body = stream[:body] || ""
|
|
189
376
|
stream = stream.merge(body: existing_body + frame[:payload])
|
|
190
377
|
|
|
191
378
|
if frame[:payload].bytesize.positive?
|
|
192
379
|
connection_window -= frame[:payload].bytesize
|
|
193
|
-
if connection_window <
|
|
194
|
-
increment =
|
|
380
|
+
if connection_window < DEFAULT_WINDOW_SIZE / 2
|
|
381
|
+
increment = DEFAULT_WINDOW_SIZE - connection_window
|
|
195
382
|
wu_payload = [increment].pack("N")
|
|
196
383
|
outgoing_frames << parser.build_frame(:window_update, 0, 0, wu_payload)
|
|
197
384
|
outgoing_frames << parser.build_frame(:window_update, 0, stream_id, wu_payload)
|
|
@@ -213,7 +400,8 @@ module Raptor
|
|
|
213
400
|
end
|
|
214
401
|
|
|
215
402
|
when :window_update
|
|
216
|
-
parser.parse_window_update(frame[:payload])
|
|
403
|
+
increment = parser.parse_window_update(frame[:payload])
|
|
404
|
+
window_updates << [frame[:stream_id], increment]
|
|
217
405
|
|
|
218
406
|
when :ping
|
|
219
407
|
if (frame[:flags] & FLAG_ACK).zero?
|
|
@@ -228,9 +416,45 @@ module Raptor
|
|
|
228
416
|
end
|
|
229
417
|
end
|
|
230
418
|
|
|
231
|
-
|
|
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?)
|
|
232
425
|
end
|
|
233
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]
|
|
455
|
+
end
|
|
456
|
+
private_class_method :finalize_headers
|
|
457
|
+
|
|
234
458
|
# Builds a frozen result hash from the current processing state.
|
|
235
459
|
#
|
|
236
460
|
# @param data [Hash] original connection state
|
|
@@ -239,13 +463,18 @@ module Raptor
|
|
|
239
463
|
# @param streams [Hash] updated stream states
|
|
240
464
|
# @param outgoing_frames [Array<String>] frames to write to the socket
|
|
241
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
|
|
242
468
|
# @param connection_window [Integer] current connection flow control window
|
|
243
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
|
|
244
473
|
# @return [Hash] frozen result hash
|
|
245
474
|
#
|
|
246
|
-
# @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]
|
|
247
|
-
def self.build_result(data, buffer, hpack_table, streams, outgoing_frames, completed_requests, connection_window, preface_received)
|
|
248
|
-
|
|
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 = {
|
|
249
478
|
id: data[:id],
|
|
250
479
|
protocol: :http2,
|
|
251
480
|
buffer: buffer || "",
|
|
@@ -253,11 +482,17 @@ module Raptor
|
|
|
253
482
|
http2_streams: streams,
|
|
254
483
|
http2_window: connection_window,
|
|
255
484
|
http2_preface_received: preface_received,
|
|
485
|
+
http2_last_client_stream_id: last_client_stream_id,
|
|
486
|
+
http2_pending_headers: pending_headers,
|
|
256
487
|
outgoing_frames: outgoing_frames,
|
|
257
488
|
completed_requests: completed_requests,
|
|
489
|
+
close_connection: close_connection,
|
|
258
490
|
remote_addr: data[:remote_addr],
|
|
259
491
|
url_scheme: data[:url_scheme]
|
|
260
|
-
}
|
|
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)
|
|
261
496
|
end
|
|
262
497
|
private_class_method :build_result
|
|
263
498
|
|
|
@@ -277,9 +512,19 @@ module Raptor
|
|
|
277
512
|
return unless socket
|
|
278
513
|
|
|
279
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
|
|
280
520
|
|
|
281
521
|
writer.write_frames(socket, result[:outgoing_frames])
|
|
282
522
|
|
|
523
|
+
if result[:close_connection]
|
|
524
|
+
reactor.close_connection(result[:id])
|
|
525
|
+
return
|
|
526
|
+
end
|
|
527
|
+
|
|
283
528
|
reactor.update_http2_state(result)
|
|
284
529
|
|
|
285
530
|
result[:completed_requests]&.each do |request|
|
|
@@ -288,7 +533,7 @@ module Raptor
|
|
|
288
533
|
|
|
289
534
|
thread_pool << proc do
|
|
290
535
|
dispatch_stream_request(
|
|
291
|
-
socket, writer, stream_id,
|
|
536
|
+
socket, writer, flow_control, stream_id,
|
|
292
537
|
request[:headers], request[:body],
|
|
293
538
|
remote_addr: remote_addr
|
|
294
539
|
)
|
|
@@ -298,23 +543,46 @@ module Raptor
|
|
|
298
543
|
|
|
299
544
|
private
|
|
300
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
|
+
|
|
301
568
|
# Dispatches a completed stream request to the Rack app and writes
|
|
302
569
|
# the response back as HTTP/2 frames.
|
|
303
570
|
#
|
|
304
571
|
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
305
572
|
# @param writer [Writer] lock-free frame writer for the connection
|
|
573
|
+
# @param flow_control [FlowControl] per-connection outbound flow controller
|
|
306
574
|
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
307
575
|
# @param headers [Array<Array(String, String)>] request headers
|
|
308
576
|
# @param body [String] request body
|
|
309
577
|
# @param remote_addr [String] the client IP address
|
|
310
578
|
# @return [void]
|
|
311
579
|
#
|
|
312
|
-
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id, Array[[String, String]] headers, String body, remote_addr: String) -> void
|
|
313
|
-
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:)
|
|
314
582
|
env = build_rack_env(headers, body, remote_addr: remote_addr)
|
|
315
583
|
status, response_headers, response_body = @app.call(env)
|
|
316
584
|
|
|
317
|
-
write_http2_response(socket, writer, stream_id, status, response_headers, response_body)
|
|
585
|
+
write_http2_response(socket, writer, flow_control, stream_id, status, response_headers, response_body)
|
|
318
586
|
rescue => error
|
|
319
587
|
write_http2_error_response(socket, writer, stream_id)
|
|
320
588
|
|
|
@@ -325,20 +593,25 @@ module Raptor
|
|
|
325
593
|
end
|
|
326
594
|
ensure
|
|
327
595
|
response_body.close if response_body.respond_to?(:close)
|
|
596
|
+
flow_control.discard_stream(stream_id) if flow_control
|
|
328
597
|
end
|
|
329
598
|
|
|
330
599
|
# Writes a Rack response as HTTP/2 frames to the socket.
|
|
331
600
|
#
|
|
601
|
+
# DATA frames are partitioned through `flow_control` so each write fits
|
|
602
|
+
# within the peer's per-stream and connection windows.
|
|
603
|
+
#
|
|
332
604
|
# @param socket [OpenSSL::SSL::SSLSocket] the connection socket
|
|
333
605
|
# @param writer [Writer] lock-free frame writer for the connection
|
|
606
|
+
# @param flow_control [FlowControl] per-connection outbound flow controller
|
|
334
607
|
# @param stream_id [Integer] the HTTP/2 stream identifier
|
|
335
608
|
# @param status [Integer] HTTP status code
|
|
336
609
|
# @param headers [Hash] response headers from the Rack application
|
|
337
610
|
# @param body [Object] response body responding to each
|
|
338
611
|
# @return [void]
|
|
339
612
|
#
|
|
340
|
-
# @rbs (OpenSSL::SSL::SSLSocket socket, Writer writer, Integer stream_id, Integer status, Hash[String, String | Array[String]] headers, untyped body) -> void
|
|
341
|
-
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)
|
|
342
615
|
parser = Http2Parser.new
|
|
343
616
|
|
|
344
617
|
header_pairs = [[":status", status.to_s]]
|
|
@@ -358,16 +631,24 @@ module Raptor
|
|
|
358
631
|
body_chunks = []
|
|
359
632
|
body.each { |chunk| body_chunks << chunk unless chunk.empty? }
|
|
360
633
|
|
|
361
|
-
frames = []
|
|
362
634
|
if body_chunks.empty?
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
635
|
+
writer.write_frames(socket, [parser.build_frame(:headers, FLAG_END_STREAM | FLAG_END_HEADERS, stream_id, encoded_headers)])
|
|
636
|
+
return
|
|
637
|
+
end
|
|
366
638
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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)
|
|
371
652
|
end
|
|
372
653
|
end
|
|
373
654
|
|
data/lib/raptor/reactor.rb
CHANGED
|
@@ -76,6 +76,7 @@ module Raptor
|
|
|
76
76
|
# @rbs @socket_to_state: Hash[TCPSocket, Hash[Symbol, untyped]]
|
|
77
77
|
# @rbs @id_to_timeout: Hash[Integer, TimeoutClient]
|
|
78
78
|
# @rbs @id_to_writer: Hash[Integer, untyped]
|
|
79
|
+
# @rbs @id_to_flow_control: Hash[Integer, untyped]
|
|
79
80
|
|
|
80
81
|
# Creates a new Reactor instance.
|
|
81
82
|
#
|
|
@@ -101,6 +102,7 @@ module Raptor
|
|
|
101
102
|
@socket_to_state = {}
|
|
102
103
|
@id_to_timeout = {}
|
|
103
104
|
@id_to_writer = {}
|
|
105
|
+
@id_to_flow_control = {}
|
|
104
106
|
end
|
|
105
107
|
|
|
106
108
|
# Starts the reactor's main event loop in a new thread.
|
|
@@ -117,33 +119,38 @@ module Raptor
|
|
|
117
119
|
Thread.current.name = self.class.name
|
|
118
120
|
|
|
119
121
|
until @queue.closed? && @queue.empty?
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
122
|
+
begin
|
|
123
|
+
timeout = @timeouts.min&.timeout(Process.clock_gettime(Process::CLOCK_MONOTONIC))
|
|
124
|
+
@selector.select(timeout) do |monitor|
|
|
125
|
+
wakeup!(monitor.value)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
129
|
+
expired = []
|
|
130
|
+
@timeouts.traverse do |to_client|
|
|
131
|
+
break unless to_client.timeout(now) == 0
|
|
132
|
+
|
|
133
|
+
expired << to_client
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
expired.each do |to_client|
|
|
137
|
+
@timeouts.delete!(to_client)
|
|
138
|
+
id = to_client.client_data[:id]
|
|
139
|
+
@id_to_timeout.delete(id)
|
|
140
|
+
socket = @id_to_socket[id]
|
|
141
|
+
next unless socket
|
|
142
|
+
|
|
143
|
+
@selector.deregister(socket)
|
|
144
|
+
socket.write(TIMEOUT_RESPONSE) rescue nil
|
|
145
|
+
cleanup(socket)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
until @queue.empty?
|
|
149
|
+
register(@queue.pop)
|
|
150
|
+
end
|
|
151
|
+
rescue => error
|
|
152
|
+
warn "#{Thread.current.name} rescued:"
|
|
153
|
+
warn error.full_message
|
|
147
154
|
end
|
|
148
155
|
end
|
|
149
156
|
|
|
@@ -163,9 +170,11 @@ module Raptor
|
|
|
163
170
|
socket = state[:socket]
|
|
164
171
|
state.delete(:socket)
|
|
165
172
|
writer = state.delete(:writer)
|
|
173
|
+
flow_control = state.delete(:flow_control)
|
|
166
174
|
@id_to_socket[state[:id]] = socket
|
|
167
175
|
@socket_to_state[socket] = state
|
|
168
176
|
@id_to_writer[state[:id]] = writer if writer
|
|
177
|
+
@id_to_flow_control[state[:id]] = flow_control if flow_control
|
|
169
178
|
|
|
170
179
|
read_and_queue_for_parse(socket, state)
|
|
171
180
|
end
|
|
@@ -262,6 +271,18 @@ module Raptor
|
|
|
262
271
|
@id_to_writer[id]
|
|
263
272
|
end
|
|
264
273
|
|
|
274
|
+
# Returns the flow controller associated with a given connection, if one
|
|
275
|
+
# was supplied when the connection was added. Used by HTTP/2 stream
|
|
276
|
+
# dispatchers to honour the peer's flow-control windows.
|
|
277
|
+
#
|
|
278
|
+
# @param id [Integer] unique client identifier
|
|
279
|
+
# @return [Object, nil] the flow controller, if found
|
|
280
|
+
#
|
|
281
|
+
# @rbs (Integer id) -> untyped?
|
|
282
|
+
def flow_control_for(id)
|
|
283
|
+
@id_to_flow_control[id]
|
|
284
|
+
end
|
|
285
|
+
|
|
265
286
|
# Updates connection state for an HTTP/2 connection after frame processing.
|
|
266
287
|
#
|
|
267
288
|
# Re-registers the socket with the selector for further reads and stores
|
|
@@ -282,6 +303,24 @@ module Raptor
|
|
|
282
303
|
socket.close
|
|
283
304
|
end
|
|
284
305
|
|
|
306
|
+
# Closes the socket for the given connection and drops all reactor state
|
|
307
|
+
# associated with it. Used to terminate HTTP/2 connections after sending
|
|
308
|
+
# a GOAWAY frame.
|
|
309
|
+
#
|
|
310
|
+
# @param id [Integer] unique client identifier
|
|
311
|
+
# @return [void]
|
|
312
|
+
#
|
|
313
|
+
# @rbs (Integer id) -> void
|
|
314
|
+
def close_connection(id)
|
|
315
|
+
socket = @id_to_socket.delete(id)
|
|
316
|
+
return unless socket
|
|
317
|
+
|
|
318
|
+
@socket_to_state.delete(socket)
|
|
319
|
+
@id_to_writer.delete(id)
|
|
320
|
+
@id_to_flow_control.delete(id)
|
|
321
|
+
socket.close rescue nil
|
|
322
|
+
end
|
|
323
|
+
|
|
285
324
|
# Initiates reactor shutdown.
|
|
286
325
|
#
|
|
287
326
|
# Closes the registration queue and wakes up the selector to begin
|
|
@@ -385,6 +424,7 @@ module Raptor
|
|
|
385
424
|
state = @socket_to_state.delete(socket)
|
|
386
425
|
@id_to_socket.delete(state[:id])
|
|
387
426
|
@id_to_writer.delete(state[:id])
|
|
427
|
+
@id_to_flow_control.delete(state[:id])
|
|
388
428
|
socket.close
|
|
389
429
|
end
|
|
390
430
|
|