raptor 0.22.3 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/Dockerfile +3 -1
- data/README.md +38 -31
- data/docs/raptor-vs-puma.md +44 -38
- data/docs/talk.md +1295 -0
- data/ext/raptor_bpf/reuseport_select.bpf.c +18 -9
- data/ext/raptor_http/extconf.rb +2 -0
- data/ext/raptor_http/raptor_http.c +91 -119
- data/ext/raptor_http2/raptor_http2.c +272 -223
- data/ext/raptor_native/raptor_native.c +38 -20
- data/lib/rackup/handler/raptor.rb +12 -11
- data/lib/raptor/binder.rb +12 -12
- data/lib/raptor/cli.rb +18 -18
- data/lib/raptor/cluster.rb +44 -44
- data/lib/raptor/control_server.rb +3 -2
- data/lib/raptor/detached_body.rb +6 -2
- data/lib/raptor/http.rb +7 -5
- data/lib/raptor/http1.rb +232 -207
- data/lib/raptor/http2.rb +312 -265
- data/lib/raptor/reactor.rb +138 -148
- data/lib/raptor/reuseport_bpf.rb +62 -38
- data/lib/raptor/server.rb +45 -34
- data/lib/raptor/stats.rb +15 -3
- data/lib/raptor/thread_locals.rb +4 -3
- data/lib/raptor/version.rb +1 -1
- data/sig/generated/rackup/handler/raptor.rbs +2 -1
- data/sig/generated/raptor/binder.rbs +9 -9
- data/sig/generated/raptor/cli.rbs +6 -6
- data/sig/generated/raptor/cluster.rbs +9 -9
- data/sig/generated/raptor/control_server.rbs +2 -1
- data/sig/generated/raptor/detached_body.rbs +5 -1
- data/sig/generated/raptor/http.rbs +3 -3
- data/sig/generated/raptor/http1.rbs +100 -91
- data/sig/generated/raptor/http2.rbs +200 -189
- data/sig/generated/raptor/reactor.rbs +107 -118
- data/sig/generated/raptor/reuseport_bpf.rbs +54 -17
- data/sig/generated/raptor/server.rbs +26 -26
- data/sig/generated/raptor/stats.rbs +3 -2
- data/sig/generated/raptor/thread_locals.rbs +4 -3
- metadata +2 -2
- data/docs/brisrails-talk.md +0 -2398
data/lib/raptor/http1.rb
CHANGED
|
@@ -11,6 +11,7 @@ require "rack"
|
|
|
11
11
|
require_relative "detached_body"
|
|
12
12
|
require_relative "http"
|
|
13
13
|
require_relative "raptor_http"
|
|
14
|
+
require_relative "server"
|
|
14
15
|
require_relative "thread_locals"
|
|
15
16
|
|
|
16
17
|
module Raptor
|
|
@@ -29,29 +30,32 @@ module Raptor
|
|
|
29
30
|
|
|
30
31
|
HTTP_10 = "HTTP/1.0"
|
|
31
32
|
HTTP_11 = "HTTP/1.1"
|
|
32
|
-
STATUS_LINE_CACHE_10 = Hash.new do |
|
|
33
|
+
STATUS_LINE_CACHE_10 = Hash.new do |cache, status|
|
|
33
34
|
reason = Rack::Utils::HTTP_STATUS_CODES[status]
|
|
34
|
-
|
|
35
|
+
cache[status] = "#{HTTP_10} #{status}#{reason ? " #{reason}" : ""}\r\n".freeze
|
|
35
36
|
end
|
|
36
|
-
STATUS_LINE_CACHE_11 = Hash.new do |
|
|
37
|
+
STATUS_LINE_CACHE_11 = Hash.new do |cache, status|
|
|
37
38
|
reason = Rack::Utils::HTTP_STATUS_CODES[status]
|
|
38
|
-
|
|
39
|
+
cache[status] = "#{HTTP_11} #{status}#{reason ? " #{reason}" : ""}\r\n".freeze
|
|
39
40
|
end
|
|
40
41
|
|
|
41
42
|
CONTINUE_RESPONSE = "HTTP/1.1 100 Continue\r\n\r\n"
|
|
42
43
|
BAD_REQUEST_RESPONSE = "HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
|
|
43
44
|
CONTENT_TOO_LARGE_RESPONSE = "HTTP/1.1 413 Content Too Large\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
|
|
44
45
|
INTERNAL_SERVER_ERROR_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
|
|
46
|
+
CHUNKED_TERMINATOR = "0\r\n\r\n"
|
|
45
47
|
|
|
46
48
|
CONNECTION_CLOSE = "close"
|
|
47
49
|
CONNECTION_KEEPALIVE = "keep-alive"
|
|
48
50
|
EXPECT_100_CONTINUE = "100-continue"
|
|
49
51
|
TRANSFER_ENCODING_CHUNKED = "chunked"
|
|
50
|
-
CHUNKED_TERMINATOR = "0\r\n\r\n"
|
|
51
52
|
|
|
52
53
|
HTTP_CONNECTION = "HTTP_CONNECTION"
|
|
53
54
|
HTTP_EXPECT = "HTTP_EXPECT"
|
|
54
55
|
HTTP_TRANSFER_ENCODING = "HTTP_TRANSFER_ENCODING"
|
|
56
|
+
HTTP_X_FORWARDED_PROTO = "HTTP_X_FORWARDED_PROTO"
|
|
57
|
+
HTTP_X_FORWARDED_SCHEME = "HTTP_X_FORWARDED_SCHEME"
|
|
58
|
+
HTTP_X_FORWARDED_SSL = "HTTP_X_FORWARDED_SSL"
|
|
55
59
|
RACK_HEADER_PREFIX = "rack."
|
|
56
60
|
RACK_HIJACKED = "rack.hijacked"
|
|
57
61
|
RACK_HIJACK_IO = "rack.hijack_io"
|
|
@@ -98,6 +102,17 @@ module Raptor
|
|
|
98
102
|
false
|
|
99
103
|
end
|
|
100
104
|
|
|
105
|
+
# Returns any pipelined bytes following the request, or nil if none remain.
|
|
106
|
+
#
|
|
107
|
+
# @param buffer [String] the raw request bytes
|
|
108
|
+
# @param offset [Integer] the byte offset where the request ends
|
|
109
|
+
# @return [String, nil]
|
|
110
|
+
#
|
|
111
|
+
# @rbs (String buffer, Integer offset) -> String?
|
|
112
|
+
def self.pipelined_bytes(buffer, offset)
|
|
113
|
+
buffer.byteslice(offset..-1) if offset < buffer.bytesize
|
|
114
|
+
end
|
|
115
|
+
|
|
101
116
|
# Encodes one HTTP/1.1 response chunk.
|
|
102
117
|
#
|
|
103
118
|
# @param chunk [String] response body bytes
|
|
@@ -128,7 +143,8 @@ module Raptor
|
|
|
128
143
|
|
|
129
144
|
# Advances an HTTP/1.x request parse from the state hash's buffered
|
|
130
145
|
# bytes. A complete well-formed request returns with `:complete` set
|
|
131
|
-
# plus populated `:env` and `:body
|
|
146
|
+
# plus populated `:env` and `:body`, and records any bytes after it in
|
|
147
|
+
# the parse data's `:pipelined`; malformed or oversized input flips
|
|
132
148
|
# `:malformed` or `:too_large`; incomplete input returns the state
|
|
133
149
|
# so the reactor can wait for more.
|
|
134
150
|
#
|
|
@@ -139,11 +155,11 @@ module Raptor
|
|
|
139
155
|
#
|
|
140
156
|
# @rbs (Hash[Symbol, untyped] data, Hash[String, untyped] env_template, Integer? max_body_size) -> Hash[Symbol, untyped]
|
|
141
157
|
def self.parse(data, env_template, max_body_size)
|
|
142
|
-
parser =
|
|
158
|
+
parser = HttpParser.new
|
|
143
159
|
env = env_template.dup
|
|
144
160
|
nread = begin
|
|
145
161
|
parser.execute(env, data[:buffer], 0)
|
|
146
|
-
rescue
|
|
162
|
+
rescue HttpParserError
|
|
147
163
|
return Ractor.make_shareable(data.merge(complete: true, malformed: true))
|
|
148
164
|
end
|
|
149
165
|
parse_data = if data[:parse_data]
|
|
@@ -162,11 +178,12 @@ module Raptor
|
|
|
162
178
|
if max_body_size && parser.content_length > max_body_size
|
|
163
179
|
data.merge(env: env, body: nil, parse_data: parse_data, complete: true, too_large: true)
|
|
164
180
|
elsif parser.chunked?
|
|
165
|
-
decoded_body, chunked_state = HttpParser.decode_chunked(body_buffer, max_body_size)
|
|
181
|
+
decoded_body, chunked_state, consumed = HttpParser.decode_chunked(body_buffer, max_body_size)
|
|
166
182
|
|
|
167
183
|
case chunked_state
|
|
168
184
|
when :complete
|
|
169
185
|
env.delete(HTTP_TRANSFER_ENCODING)
|
|
186
|
+
parse_data[:pipelined] = pipelined_bytes(body_buffer, consumed)
|
|
170
187
|
data.merge(env: env, body: decoded_body, parse_data: parse_data, complete: true)
|
|
171
188
|
when :too_large
|
|
172
189
|
data.merge(env: env, body: nil, parse_data: parse_data, complete: true, too_large: true)
|
|
@@ -178,9 +195,11 @@ module Raptor
|
|
|
178
195
|
elsif parser.content_length > body_buffer.bytesize
|
|
179
196
|
data.merge(env: env, parse_data: parse_data)
|
|
180
197
|
else
|
|
181
|
-
|
|
198
|
+
parse_data[:pipelined] = pipelined_bytes(body_buffer, parser.content_length)
|
|
199
|
+
data.merge(env: env, body: body_buffer.byteslice(0, parser.content_length), parse_data: parse_data, complete: true)
|
|
182
200
|
end
|
|
183
201
|
else
|
|
202
|
+
parse_data[:pipelined] = pipelined_bytes(data[:buffer], nread)
|
|
184
203
|
data.merge(env: env, body: nil, parse_data: parse_data, complete: true)
|
|
185
204
|
end
|
|
186
205
|
else
|
|
@@ -190,7 +209,6 @@ module Raptor
|
|
|
190
209
|
end
|
|
191
210
|
|
|
192
211
|
# @rbs @app: ^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped]
|
|
193
|
-
# @rbs @server_port: Integer
|
|
194
212
|
# @rbs @server_port_string: String
|
|
195
213
|
# @rbs @write_timeout: Integer
|
|
196
214
|
# @rbs @max_body_size: Integer?
|
|
@@ -199,7 +217,7 @@ module Raptor
|
|
|
199
217
|
# @rbs @access_log_io: IO?
|
|
200
218
|
# @rbs @clean_thread_locals: bool
|
|
201
219
|
# @rbs @clean_fiber_locals: bool
|
|
202
|
-
# @rbs @on_error: ^(Hash[String, untyped]?, Exception) -> void
|
|
220
|
+
# @rbs @on_error: (^(Hash[String, untyped]?, Exception) -> void)?
|
|
203
221
|
# @rbs @running: AtomicBoolean
|
|
204
222
|
# @rbs @env_template: Hash[String, untyped]
|
|
205
223
|
|
|
@@ -219,10 +237,9 @@ module Raptor
|
|
|
219
237
|
# @param on_error [#call, nil] callback invoked with (env, exception) when the Rack app raises
|
|
220
238
|
# @return [void]
|
|
221
239
|
#
|
|
222
|
-
# @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Integer server_port, ?connection_options: Hash[Symbol, untyped], ?http1_options: Hash[Symbol, untyped], ?access_log_io: IO?, ?clean_thread_locals: bool, ?clean_fiber_locals: bool, ?on_error: ^(Hash[String, untyped]?, Exception) -> void
|
|
240
|
+
# @rbs (^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped] app, Integer server_port, ?connection_options: Hash[Symbol, untyped], ?http1_options: Hash[Symbol, untyped], ?access_log_io: IO?, ?clean_thread_locals: bool, ?clean_fiber_locals: bool, ?on_error: (^(Hash[String, untyped]?, Exception) -> void)?) -> void
|
|
223
241
|
def initialize(app, server_port, connection_options: {}, http1_options: {}, access_log_io: nil, clean_thread_locals: true, clean_fiber_locals: false, on_error: nil)
|
|
224
242
|
@app = app
|
|
225
|
-
@server_port = server_port
|
|
226
243
|
@server_port_string = server_port.to_s.freeze
|
|
227
244
|
@write_timeout = connection_options[:write_timeout] || Http::WRITE_TIMEOUT
|
|
228
245
|
@max_body_size = connection_options[:max_body_size]
|
|
@@ -242,8 +259,7 @@ module Raptor
|
|
|
242
259
|
}.freeze
|
|
243
260
|
end
|
|
244
261
|
|
|
245
|
-
#
|
|
246
|
-
# handler's env template and body-size limit into the worker proc.
|
|
262
|
+
# Returns a Ractor-safe proc that parses HTTP/1.x requests.
|
|
247
263
|
#
|
|
248
264
|
# @return [Proc]
|
|
249
265
|
#
|
|
@@ -252,32 +268,6 @@ module Raptor
|
|
|
252
268
|
Http.parser_worker(@env_template, @max_body_size)
|
|
253
269
|
end
|
|
254
270
|
|
|
255
|
-
# Instance-level wrapper around {Http.socket_write} that applies the
|
|
256
|
-
# configured `write_timeout`.
|
|
257
|
-
#
|
|
258
|
-
# @param socket [TCPSocket] the socket to write to
|
|
259
|
-
# @param string [String] the data to write
|
|
260
|
-
# @return [void]
|
|
261
|
-
# @raise [Http::WriteError] if the socket is not writable within the timeout or raises IOError
|
|
262
|
-
#
|
|
263
|
-
# @rbs (TCPSocket socket, String string) -> void
|
|
264
|
-
def socket_write(socket, string)
|
|
265
|
-
Http.socket_write(socket, string, timeout: @write_timeout)
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
# Instance-level wrapper around {Http.socket_writev} that applies the
|
|
269
|
-
# configured `write_timeout`.
|
|
270
|
-
#
|
|
271
|
-
# @param socket [TCPSocket] the socket to write to
|
|
272
|
-
# @param strings [Array<String>] the buffers to write in order
|
|
273
|
-
# @return [void]
|
|
274
|
-
# @raise [Http::WriteError] if the socket is not writable within the timeout or raises IOError
|
|
275
|
-
#
|
|
276
|
-
# @rbs (TCPSocket socket, Array[String] strings) -> void
|
|
277
|
-
def socket_writev(socket, strings)
|
|
278
|
-
Http.socket_writev(socket, strings, timeout: @write_timeout)
|
|
279
|
-
end
|
|
280
|
-
|
|
281
271
|
# Signals eager keep-alive loops to stop processing further requests on
|
|
282
272
|
# their connections. In-flight requests complete normally.
|
|
283
273
|
#
|
|
@@ -293,10 +283,10 @@ module Raptor
|
|
|
293
283
|
# when complete. Falls back to the reactor when more data is needed.
|
|
294
284
|
#
|
|
295
285
|
# @param socket [TCPSocket] the freshly accepted client socket
|
|
296
|
-
# @param id [Integer] unique
|
|
286
|
+
# @param id [Integer] unique connection identifier
|
|
297
287
|
# @param reactor [Reactor] the reactor for fallback registration
|
|
298
288
|
# @param thread_pool [AtomicThreadPool] thread pool for application processing
|
|
299
|
-
# @param remote_addr [String] client IP address
|
|
289
|
+
# @param remote_addr [String] the client IP address
|
|
300
290
|
# @param url_scheme [String] "http" or "https"
|
|
301
291
|
# @return [void]
|
|
302
292
|
#
|
|
@@ -319,18 +309,12 @@ module Raptor
|
|
|
319
309
|
return
|
|
320
310
|
end
|
|
321
311
|
|
|
322
|
-
|
|
312
|
+
unless parser.finished?
|
|
323
313
|
fallback_to_reactor(socket, id, buffer, env, parse_data, reactor, 0, remote_addr, url_scheme, persisted: false)
|
|
324
314
|
return
|
|
325
|
-
elsif Http1.invalid_host?(env) || Http1.request_smuggling?(env)
|
|
326
|
-
reject_malformed(socket)
|
|
327
|
-
return
|
|
328
|
-
elsif parser.has_body? && @max_body_size && parser.content_length > @max_body_size
|
|
329
|
-
reject_oversized(socket)
|
|
330
|
-
return
|
|
331
315
|
end
|
|
332
316
|
|
|
333
|
-
body = extract_body(buffer, env, parser, nread
|
|
317
|
+
body = extract_body(buffer, env, parse_data, parser, nread)
|
|
334
318
|
case body
|
|
335
319
|
when :incomplete
|
|
336
320
|
fallback_to_reactor(socket, id, buffer, env, parse_data, reactor, 0, remote_addr, url_scheme, persisted: false)
|
|
@@ -370,10 +354,7 @@ module Raptor
|
|
|
370
354
|
return
|
|
371
355
|
end
|
|
372
356
|
|
|
373
|
-
|
|
374
|
-
parsed_request = send_continue_if_expected(parsed_request, reactor)
|
|
375
|
-
reactor.update_state(parsed_request)
|
|
376
|
-
else
|
|
357
|
+
if parsed_request[:complete]
|
|
377
358
|
socket = reactor.remove(parsed_request[:id])
|
|
378
359
|
request_count = (parsed_request[:request_count] || 0) + 1
|
|
379
360
|
remote_addr = parsed_request[:remote_addr] || Server::DEFAULT_REMOTE_ADDR
|
|
@@ -393,6 +374,112 @@ module Raptor
|
|
|
393
374
|
url_scheme
|
|
394
375
|
)
|
|
395
376
|
end
|
|
377
|
+
else
|
|
378
|
+
parsed_request = send_continue_if_expected(parsed_request, reactor)
|
|
379
|
+
reactor.update_state(parsed_request)
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# Processes kept-alive requests on application threads, yielding to queued
|
|
384
|
+
# work and returning idle or incomplete connections to the reactor.
|
|
385
|
+
#
|
|
386
|
+
# @param socket [TCPSocket] the client socket
|
|
387
|
+
# @param id [Integer] unique connection identifier
|
|
388
|
+
# @param reactor [Reactor] the reactor for fallback registration
|
|
389
|
+
# @param thread_pool [AtomicThreadPool] thread pool for application processing
|
|
390
|
+
# @param request_count [Integer] number of requests handled on this connection
|
|
391
|
+
# @param remote_addr [String] the client IP address
|
|
392
|
+
# @param url_scheme [String] "http" or "https"
|
|
393
|
+
# @param resumed [Boolean] whether the reactor queued the connection once its next request became readable
|
|
394
|
+
# @param pipelined [String, nil] bytes the client sent after the previous request
|
|
395
|
+
# @return [void]
|
|
396
|
+
#
|
|
397
|
+
# @rbs (TCPSocket socket, Integer id, Reactor reactor, AtomicThreadPool thread_pool, Integer request_count, String remote_addr, String url_scheme, ?resumed: bool, ?pipelined: String?) -> void
|
|
398
|
+
def eager_keepalive(socket, id, reactor, thread_pool, request_count, remote_addr, url_scheme, resumed: false, pipelined: nil)
|
|
399
|
+
loop do
|
|
400
|
+
unless @running.true?
|
|
401
|
+
socket.close rescue nil
|
|
402
|
+
return
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
if pipelined
|
|
406
|
+
buffer = pipelined
|
|
407
|
+
else
|
|
408
|
+
unless resumed || socket.wait_readable(0)
|
|
409
|
+
reactor.persist(socket, id, request_count, remote_addr: remote_addr, url_scheme: url_scheme)
|
|
410
|
+
return
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
begin
|
|
414
|
+
buffer = read_into_thread_buffer(socket)
|
|
415
|
+
rescue IO::WaitReadable
|
|
416
|
+
reactor.persist(socket, id, request_count, remote_addr: remote_addr, url_scheme: url_scheme)
|
|
417
|
+
return
|
|
418
|
+
rescue EOFError, IOError, SystemCallError
|
|
419
|
+
socket.close rescue nil
|
|
420
|
+
return
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
env, parse_data, nread, parser = begin
|
|
425
|
+
parse_next_request(buffer)
|
|
426
|
+
rescue HttpParserError
|
|
427
|
+
reject_malformed(socket)
|
|
428
|
+
return
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
unless parser.finished?
|
|
432
|
+
fallback_to_reactor(socket, id, buffer, env, parse_data, reactor, request_count, remote_addr, url_scheme)
|
|
433
|
+
return
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
body = extract_body(buffer, env, parse_data, parser, nread)
|
|
437
|
+
case body
|
|
438
|
+
when :incomplete
|
|
439
|
+
fallback_to_reactor(socket, id, buffer, env, parse_data, reactor, request_count, remote_addr, url_scheme)
|
|
440
|
+
return
|
|
441
|
+
when :too_large
|
|
442
|
+
reject_oversized(socket)
|
|
443
|
+
return
|
|
444
|
+
when :malformed
|
|
445
|
+
reject_malformed(socket)
|
|
446
|
+
return
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
request_count += 1
|
|
450
|
+
|
|
451
|
+
if !resumed && thread_pool.queue_size.positive?
|
|
452
|
+
thread_pool << proc do
|
|
453
|
+
process_client(
|
|
454
|
+
socket,
|
|
455
|
+
id,
|
|
456
|
+
env,
|
|
457
|
+
parse_data,
|
|
458
|
+
body,
|
|
459
|
+
reactor,
|
|
460
|
+
thread_pool,
|
|
461
|
+
request_count,
|
|
462
|
+
remote_addr,
|
|
463
|
+
url_scheme
|
|
464
|
+
)
|
|
465
|
+
end
|
|
466
|
+
return
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
resumed = false
|
|
470
|
+
pipelined = parse_data[:pipelined]
|
|
471
|
+
keep_alive = process_request(
|
|
472
|
+
socket,
|
|
473
|
+
id,
|
|
474
|
+
env,
|
|
475
|
+
parse_data,
|
|
476
|
+
body,
|
|
477
|
+
reactor,
|
|
478
|
+
request_count,
|
|
479
|
+
remote_addr,
|
|
480
|
+
url_scheme
|
|
481
|
+
)
|
|
482
|
+
return unless keep_alive
|
|
396
483
|
end
|
|
397
484
|
end
|
|
398
485
|
|
|
@@ -400,11 +487,12 @@ module Raptor
|
|
|
400
487
|
|
|
401
488
|
# Reads pending bytes off `socket` into the thread-local read buffer,
|
|
402
489
|
# draining any additional SSL-buffered bytes so `pending` is empty on
|
|
403
|
-
# return.
|
|
404
|
-
# underlying `read_nonblock` does.
|
|
490
|
+
# return.
|
|
405
491
|
#
|
|
406
492
|
# @param socket [TCPSocket] the socket to read from
|
|
407
493
|
# @return [String] the thread-local buffer, freshly populated
|
|
494
|
+
# @raise [IO::WaitReadable] if no bytes are ready to read
|
|
495
|
+
# @raise [EOFError, IOError, SystemCallError] if the connection is closed or fails
|
|
408
496
|
#
|
|
409
497
|
# @rbs (TCPSocket socket) -> String
|
|
410
498
|
def read_into_thread_buffer(socket)
|
|
@@ -418,12 +506,11 @@ module Raptor
|
|
|
418
506
|
buffer
|
|
419
507
|
end
|
|
420
508
|
|
|
421
|
-
# Runs a fresh HTTP/1.x parse against `buffer
|
|
422
|
-
# `[env, parse_data, nread, parser]`. Raises `HttpParserError` on
|
|
423
|
-
# malformed input.
|
|
509
|
+
# Runs a fresh HTTP/1.x parse against `buffer`.
|
|
424
510
|
#
|
|
425
511
|
# @param buffer [String] the raw request bytes
|
|
426
|
-
# @return [Array(Hash, Hash, Integer, HttpParser)]
|
|
512
|
+
# @return [Array(Hash, Hash, Integer, HttpParser)] the env, parse data, bytes read, and parser
|
|
513
|
+
# @raise [HttpParserError] if the request is malformed
|
|
427
514
|
#
|
|
428
515
|
# @rbs (String buffer) -> [Hash[String, untyped], Hash[Symbol, untyped], Integer, HttpParser]
|
|
429
516
|
def parse_next_request(buffer)
|
|
@@ -435,51 +522,53 @@ module Raptor
|
|
|
435
522
|
[env, parse_data, nread, parser]
|
|
436
523
|
end
|
|
437
524
|
|
|
438
|
-
#
|
|
439
|
-
# `String` (or `nil` when the request has no body), or one of
|
|
440
|
-
# `:incomplete`, `:too_large`, `:malformed` when the caller must
|
|
441
|
-
# fall back or reject.
|
|
525
|
+
# Validates request framing and extracts the body, preserving pipelined bytes.
|
|
442
526
|
#
|
|
443
527
|
# @param buffer [String] the raw request bytes
|
|
444
528
|
# @param env [Hash] the Rack environment being built
|
|
529
|
+
# @param parse_data [Hash] metadata from the parsing pass
|
|
445
530
|
# @param parser [HttpParser] the parser holding the finished parse state
|
|
446
531
|
# @param nread [Integer] the byte offset where the body begins in `buffer`
|
|
447
|
-
# @
|
|
448
|
-
#
|
|
532
|
+
# @return [String, nil, Symbol] body, nil when absent, or `:incomplete`,
|
|
533
|
+
# `:too_large`, or `:malformed`
|
|
449
534
|
#
|
|
450
|
-
# @rbs (String buffer, Hash[String, untyped] env, HttpParser parser, Integer nread
|
|
451
|
-
def extract_body(buffer, env, parser, nread
|
|
452
|
-
return
|
|
535
|
+
# @rbs (String buffer, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, HttpParser parser, Integer nread) -> (String | Symbol)?
|
|
536
|
+
def extract_body(buffer, env, parse_data, parser, nread)
|
|
537
|
+
return :malformed if Http1.invalid_host?(env) || Http1.request_smuggling?(env)
|
|
453
538
|
|
|
454
|
-
|
|
539
|
+
unless parser.has_body?
|
|
540
|
+
parse_data[:pipelined] = Http1.pipelined_bytes(buffer, nread)
|
|
541
|
+
return
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
return :too_large if @max_body_size && parser.content_length > @max_body_size
|
|
455
545
|
|
|
456
546
|
if parser.chunked?
|
|
457
|
-
|
|
547
|
+
body, chunked_state, consumed = HttpParser.decode_chunked(buffer.byteslice(nread..-1), @max_body_size)
|
|
548
|
+
return chunked_state unless chunked_state == :complete
|
|
458
549
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
body
|
|
464
|
-
else
|
|
465
|
-
chunked_state
|
|
466
|
-
end
|
|
467
|
-
elsif parser.content_length > body.bytesize
|
|
550
|
+
env.delete(HTTP_TRANSFER_ENCODING)
|
|
551
|
+
parse_data[:pipelined] = Http1.pipelined_bytes(buffer, nread + consumed)
|
|
552
|
+
body
|
|
553
|
+
elsif nread + parser.content_length > buffer.bytesize
|
|
468
554
|
:incomplete
|
|
469
555
|
else
|
|
470
|
-
|
|
556
|
+
parse_data[:pipelined] = Http1.pipelined_bytes(buffer, nread + parser.content_length)
|
|
557
|
+
buffer.byteslice(nread, parser.content_length)
|
|
471
558
|
end
|
|
472
559
|
end
|
|
473
560
|
|
|
474
|
-
# Returns true
|
|
475
|
-
# RFC
|
|
561
|
+
# Returns true when the request expects a 100 Continue response per
|
|
562
|
+
# RFC 9110 section 10.1.1.
|
|
476
563
|
#
|
|
477
564
|
# @param env [Hash] the parsed Rack environment (possibly incomplete)
|
|
478
565
|
# @return [Boolean]
|
|
479
566
|
#
|
|
480
567
|
# @rbs (Hash[String, untyped] env) -> bool
|
|
481
568
|
def expects_100_continue?(env)
|
|
482
|
-
|
|
569
|
+
return false unless env[Rack::SERVER_PROTOCOL] == HTTP_11
|
|
570
|
+
|
|
571
|
+
env[HTTP_EXPECT]&.casecmp?(EXPECT_100_CONTINUE) || false
|
|
483
572
|
end
|
|
484
573
|
|
|
485
574
|
# Sends an HTTP 100 Continue response when the client requested
|
|
@@ -508,35 +597,35 @@ module Raptor
|
|
|
508
597
|
# if keep-alive, eagerly reading subsequent requests inline.
|
|
509
598
|
#
|
|
510
599
|
# @param socket [TCPSocket] the client socket
|
|
511
|
-
# @param id [Integer] unique
|
|
600
|
+
# @param id [Integer] unique connection identifier
|
|
512
601
|
# @param env [Hash] partial env hash from the HTTP parser
|
|
513
602
|
# @param parse_data [Hash] metadata from the parsing pass
|
|
514
603
|
# @param body [String, nil] decoded request body
|
|
515
604
|
# @param reactor [Reactor] the reactor managing the client connection
|
|
516
605
|
# @param thread_pool [AtomicThreadPool] thread pool for application processing
|
|
517
606
|
# @param request_count [Integer] number of requests handled on this connection
|
|
518
|
-
# @param remote_addr [String] client IP address
|
|
607
|
+
# @param remote_addr [String] the client IP address
|
|
519
608
|
# @param url_scheme [String] "http" or "https"
|
|
520
609
|
# @return [void]
|
|
521
610
|
#
|
|
522
611
|
# @rbs (TCPSocket socket, Integer id, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, String? body, Reactor reactor, AtomicThreadPool thread_pool, Integer request_count, String remote_addr, String url_scheme) -> void
|
|
523
612
|
def process_client(socket, id, env, parse_data, body, reactor, thread_pool, request_count, remote_addr, url_scheme)
|
|
524
613
|
keep_alive = process_request(socket, id, env, parse_data, body, reactor, request_count, remote_addr, url_scheme)
|
|
525
|
-
eager_keepalive(socket, id, reactor, thread_pool, request_count, remote_addr, url_scheme) if keep_alive
|
|
614
|
+
eager_keepalive(socket, id, reactor, thread_pool, request_count, remote_addr, url_scheme, pipelined: parse_data[:pipelined]) if keep_alive
|
|
526
615
|
end
|
|
527
616
|
|
|
528
|
-
# Processes a single request
|
|
529
|
-
#
|
|
530
|
-
#
|
|
617
|
+
# Processes a single request, isolating fiber and thread locals as
|
|
618
|
+
# configured, and returns whether the connection stays open for another
|
|
619
|
+
# request.
|
|
531
620
|
#
|
|
532
621
|
# @param socket [TCPSocket] the client socket
|
|
533
|
-
# @param id [Integer] unique
|
|
622
|
+
# @param id [Integer] unique connection identifier
|
|
534
623
|
# @param env [Hash] partial env hash from the HTTP parser
|
|
535
624
|
# @param parse_data [Hash] metadata from the parsing pass
|
|
536
625
|
# @param body [String, nil] decoded request body
|
|
537
626
|
# @param reactor [Reactor] the reactor managing the client connection
|
|
538
627
|
# @param request_count [Integer] number of requests handled on this connection
|
|
539
|
-
# @param remote_addr [String] client IP address
|
|
628
|
+
# @param remote_addr [String] the client IP address
|
|
540
629
|
# @param url_scheme [String] "http" or "https"
|
|
541
630
|
# @return [Boolean] true if the connection should be kept alive
|
|
542
631
|
#
|
|
@@ -554,13 +643,13 @@ module Raptor
|
|
|
554
643
|
# Calls the Rack app and writes its response for one request.
|
|
555
644
|
#
|
|
556
645
|
# @param socket [TCPSocket] the client socket
|
|
557
|
-
# @param id [Integer] unique
|
|
646
|
+
# @param id [Integer] unique connection identifier
|
|
558
647
|
# @param env [Hash] partial env hash from the HTTP parser
|
|
559
648
|
# @param parse_data [Hash] metadata from the parsing pass
|
|
560
649
|
# @param body [String, nil] decoded request body
|
|
561
650
|
# @param reactor [Reactor] the reactor managing the client connection
|
|
562
651
|
# @param request_count [Integer] number of requests handled on this connection
|
|
563
|
-
# @param remote_addr [String] client IP address
|
|
652
|
+
# @param remote_addr [String] the client IP address
|
|
564
653
|
# @param url_scheme [String] "http" or "https"
|
|
565
654
|
# @return [Boolean] true if the connection should be kept alive
|
|
566
655
|
#
|
|
@@ -599,7 +688,14 @@ module Raptor
|
|
|
599
688
|
socket,
|
|
600
689
|
id,
|
|
601
690
|
body,
|
|
602
|
-
{
|
|
691
|
+
{
|
|
692
|
+
chunked: chunked,
|
|
693
|
+
keep_alive: keep_alive,
|
|
694
|
+
request_count: request_count,
|
|
695
|
+
remote_addr: remote_addr,
|
|
696
|
+
url_scheme: url_scheme,
|
|
697
|
+
pipelined: parse_data[:pipelined]
|
|
698
|
+
},
|
|
603
699
|
finished
|
|
604
700
|
)
|
|
605
701
|
else
|
|
@@ -637,14 +733,14 @@ module Raptor
|
|
|
637
733
|
#
|
|
638
734
|
# @param socket [TCPSocket] the client socket
|
|
639
735
|
# @param rack_env [Hash, nil] the Rack environment, if it was built
|
|
640
|
-
# @param status [
|
|
736
|
+
# @param status [Object, nil] the status returned by the app, if any
|
|
641
737
|
# @param headers [Hash, nil] the headers returned by the app, if any
|
|
642
738
|
# @param error [Exception] the exception raised
|
|
643
739
|
# @param response_started [Boolean] whether any response bytes have been written
|
|
644
740
|
# @param hijacked [Boolean] whether the app took over the socket
|
|
645
741
|
# @return [void]
|
|
646
742
|
#
|
|
647
|
-
# @rbs (TCPSocket socket, Hash[String, untyped]? rack_env,
|
|
743
|
+
# @rbs (TCPSocket socket, Hash[String, untyped]? rack_env, untyped status, Hash[String, String | Array[String]]? headers, Exception error, response_started: bool, hijacked: bool) -> void
|
|
648
744
|
def handle_app_error(socket, rack_env, status, headers, error, response_started:, hijacked:)
|
|
649
745
|
Http.call_response_finished(rack_env, status, headers, error)
|
|
650
746
|
socket.write(INTERNAL_SERVER_ERROR_RESPONSE) rescue nil unless response_started || hijacked
|
|
@@ -656,112 +752,22 @@ module Raptor
|
|
|
656
752
|
end
|
|
657
753
|
end
|
|
658
754
|
|
|
659
|
-
# Reads and processes subsequent requests inline on a kept-alive
|
|
660
|
-
# connection. Returns the connection to the reactor when no data is ready
|
|
661
|
-
# or a request is incomplete, and queues complete requests behind work
|
|
662
|
-
# that is already waiting.
|
|
663
|
-
#
|
|
664
|
-
# @param socket [TCPSocket] the client socket
|
|
665
|
-
# @param id [Integer] unique client identifier
|
|
666
|
-
# @param reactor [Reactor] the reactor for fallback registration
|
|
667
|
-
# @param thread_pool [AtomicThreadPool] thread pool for deprioritization
|
|
668
|
-
# @param request_count [Integer] number of requests handled on this connection
|
|
669
|
-
# @param remote_addr [String] client IP address
|
|
670
|
-
# @param url_scheme [String] "http" or "https"
|
|
671
|
-
# @return [void]
|
|
672
|
-
#
|
|
673
|
-
# @rbs (TCPSocket socket, Integer id, Reactor reactor, AtomicThreadPool thread_pool, Integer request_count, String remote_addr, String url_scheme) -> void
|
|
674
|
-
def eager_keepalive(socket, id, reactor, thread_pool, request_count, remote_addr, url_scheme)
|
|
675
|
-
loop do
|
|
676
|
-
unless @running.true?
|
|
677
|
-
socket.close rescue nil
|
|
678
|
-
return
|
|
679
|
-
end
|
|
680
|
-
|
|
681
|
-
unless socket.wait_readable(0)
|
|
682
|
-
reactor.persist(socket, id, request_count, remote_addr: remote_addr, url_scheme: url_scheme)
|
|
683
|
-
return
|
|
684
|
-
end
|
|
685
|
-
|
|
686
|
-
begin
|
|
687
|
-
buffer = read_into_thread_buffer(socket)
|
|
688
|
-
rescue IO::WaitReadable
|
|
689
|
-
reactor.persist(socket, id, request_count, remote_addr: remote_addr, url_scheme: url_scheme)
|
|
690
|
-
return
|
|
691
|
-
rescue EOFError, IOError, SystemCallError
|
|
692
|
-
socket.close rescue nil
|
|
693
|
-
return
|
|
694
|
-
end
|
|
695
|
-
|
|
696
|
-
env, parse_data, nread, parser = begin
|
|
697
|
-
parse_next_request(buffer)
|
|
698
|
-
rescue HttpParserError
|
|
699
|
-
reject_malformed(socket)
|
|
700
|
-
return
|
|
701
|
-
end
|
|
702
|
-
|
|
703
|
-
if !parser.finished?
|
|
704
|
-
fallback_to_reactor(socket, id, buffer, env, parse_data, reactor, request_count, remote_addr, url_scheme)
|
|
705
|
-
return
|
|
706
|
-
end
|
|
707
|
-
|
|
708
|
-
body = extract_body(buffer, env, parser, nread, decode_chunked: false)
|
|
709
|
-
if body == :incomplete
|
|
710
|
-
fallback_to_reactor(socket, id, buffer, env, parse_data, reactor, request_count, remote_addr, url_scheme)
|
|
711
|
-
return
|
|
712
|
-
end
|
|
713
|
-
|
|
714
|
-
request_count += 1
|
|
715
|
-
|
|
716
|
-
if thread_pool.queue_size.positive?
|
|
717
|
-
thread_pool << proc do
|
|
718
|
-
process_client(
|
|
719
|
-
socket,
|
|
720
|
-
id,
|
|
721
|
-
env,
|
|
722
|
-
parse_data,
|
|
723
|
-
body,
|
|
724
|
-
reactor,
|
|
725
|
-
thread_pool,
|
|
726
|
-
request_count,
|
|
727
|
-
remote_addr,
|
|
728
|
-
url_scheme
|
|
729
|
-
)
|
|
730
|
-
end
|
|
731
|
-
return
|
|
732
|
-
end
|
|
733
|
-
|
|
734
|
-
keep_alive = process_request(
|
|
735
|
-
socket,
|
|
736
|
-
id,
|
|
737
|
-
env,
|
|
738
|
-
parse_data,
|
|
739
|
-
body,
|
|
740
|
-
reactor,
|
|
741
|
-
request_count,
|
|
742
|
-
remote_addr,
|
|
743
|
-
url_scheme
|
|
744
|
-
)
|
|
745
|
-
return unless keep_alive
|
|
746
|
-
end
|
|
747
|
-
end
|
|
748
|
-
|
|
749
755
|
# Re-registers a socket with the reactor for further processing when
|
|
750
756
|
# an incomplete request is received on the fast path.
|
|
751
757
|
#
|
|
752
758
|
# @param socket [TCPSocket] the client socket
|
|
753
|
-
# @param id [Integer] unique
|
|
759
|
+
# @param id [Integer] unique connection identifier
|
|
754
760
|
# @param buffer [String] the partial request data already read
|
|
755
761
|
# @param env [Hash] partial env hash from the HTTP parser
|
|
756
762
|
# @param parse_data [Hash] metadata from the parsing pass
|
|
757
763
|
# @param reactor [Reactor] the reactor to re-register with
|
|
758
764
|
# @param request_count [Integer] number of requests handled on this connection
|
|
759
|
-
# @param remote_addr [String] client IP address
|
|
765
|
+
# @param remote_addr [String] the client IP address
|
|
760
766
|
# @param url_scheme [String] "http" or "https"
|
|
761
767
|
# @param persisted [Boolean] whether the connection has already completed at least one request
|
|
762
768
|
# @return [void]
|
|
763
769
|
#
|
|
764
|
-
# @rbs (TCPSocket socket, Integer id, String buffer, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, Reactor reactor, Integer request_count, String remote_addr, String url_scheme, persisted: bool) -> void
|
|
770
|
+
# @rbs (TCPSocket socket, Integer id, String buffer, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, Reactor reactor, Integer request_count, String remote_addr, String url_scheme, ?persisted: bool) -> void
|
|
765
771
|
def fallback_to_reactor(socket, id, buffer, env, parse_data, reactor, request_count, remote_addr, url_scheme, persisted: true)
|
|
766
772
|
continued = expects_100_continue?(env)
|
|
767
773
|
socket_write(socket, CONTINUE_RESPONSE) rescue nil if continued
|
|
@@ -809,7 +815,7 @@ module Raptor
|
|
|
809
815
|
# @param parse_data [Hash] metadata from the parsing pass, including content_length
|
|
810
816
|
# @param body [String, nil] decoded request body, or nil if no body
|
|
811
817
|
# @param socket [TCPSocket] the client socket, used for hijack support
|
|
812
|
-
# @param remote_addr [String] client IP address
|
|
818
|
+
# @param remote_addr [String] the client IP address
|
|
813
819
|
# @param url_scheme [String] "http" or "https"
|
|
814
820
|
# @return [Hash] fully populated Rack environment hash
|
|
815
821
|
#
|
|
@@ -833,7 +839,7 @@ module Raptor
|
|
|
833
839
|
if scheme_end
|
|
834
840
|
authority_end = request_uri.index("/", scheme_end + 3) || request_uri.bytesize
|
|
835
841
|
path_and_query = request_uri.byteslice(authority_end..-1) || ""
|
|
836
|
-
if query_delim = path_and_query.index("?")
|
|
842
|
+
if (query_delim = path_and_query.index("?"))
|
|
837
843
|
env[Rack::PATH_INFO] = query_delim.zero? ? "/" : path_and_query.byteslice(0, query_delim)
|
|
838
844
|
env[Rack::QUERY_STRING] = path_and_query.byteslice(query_delim + 1..-1)
|
|
839
845
|
else
|
|
@@ -911,10 +917,10 @@ module Raptor
|
|
|
911
917
|
#
|
|
912
918
|
# @rbs (Hash[String, untyped] env) -> bool
|
|
913
919
|
def forwarded_https?(env)
|
|
914
|
-
proto = env[
|
|
920
|
+
proto = env[HTTP_X_FORWARDED_PROTO] || env[HTTP_X_FORWARDED_SCHEME]
|
|
915
921
|
return true if proto && proto.split(",").first&.strip&.casecmp?(Server::HTTPS_SCHEME)
|
|
916
922
|
|
|
917
|
-
env[
|
|
923
|
+
env[HTTP_X_FORWARDED_SSL]&.casecmp?("on") || false
|
|
918
924
|
end
|
|
919
925
|
|
|
920
926
|
# Returns true when the connection should be kept alive after the
|
|
@@ -1037,14 +1043,13 @@ module Raptor
|
|
|
1037
1043
|
# @raise [TypeError] if status is not an Integer
|
|
1038
1044
|
# @raise [ArgumentError] if status is less than 100
|
|
1039
1045
|
#
|
|
1040
|
-
# @rbs (
|
|
1046
|
+
# @rbs (untyped status) -> void
|
|
1041
1047
|
def validate_status(status)
|
|
1042
1048
|
raise TypeError, "status must be an Integer" unless status.is_a?(Integer)
|
|
1043
|
-
|
|
1044
1049
|
raise ArgumentError, "status must be >= 100" unless status >= 100
|
|
1045
1050
|
end
|
|
1046
1051
|
|
|
1047
|
-
# Returns a
|
|
1052
|
+
# Returns a normalized copy of the response headers with lowercased
|
|
1048
1053
|
# keys and illegal/`rack.*`/`status` entries dropped.
|
|
1049
1054
|
#
|
|
1050
1055
|
# @param headers [Hash] raw headers from the Rack application
|
|
@@ -1083,7 +1088,6 @@ module Raptor
|
|
|
1083
1088
|
def validate_headers(headers, status, no_entity_body)
|
|
1084
1089
|
if no_entity_body
|
|
1085
1090
|
raise ArgumentError, "content-type must not be present for status #{status}" if headers.key?(Rack::CONTENT_TYPE)
|
|
1086
|
-
|
|
1087
1091
|
raise ArgumentError, "content-length must not be present for status #{status}" if headers.key?(Rack::CONTENT_LENGTH)
|
|
1088
1092
|
end
|
|
1089
1093
|
end
|
|
@@ -1103,8 +1107,8 @@ module Raptor
|
|
|
1103
1107
|
response
|
|
1104
1108
|
end
|
|
1105
1109
|
|
|
1106
|
-
# Writes the response headers
|
|
1107
|
-
#
|
|
1110
|
+
# Writes the response headers and hands the raw socket to the hijack
|
|
1111
|
+
# callback.
|
|
1108
1112
|
#
|
|
1109
1113
|
# @param socket [TCPSocket] the client socket
|
|
1110
1114
|
# @param response [String] the status line accumulated so far
|
|
@@ -1117,7 +1121,6 @@ module Raptor
|
|
|
1117
1121
|
HttpParser.format_headers(response, headers)
|
|
1118
1122
|
response << "\r\n"
|
|
1119
1123
|
socket_write(socket, response)
|
|
1120
|
-
uncork_socket(socket)
|
|
1121
1124
|
response_hijack.call(socket)
|
|
1122
1125
|
end
|
|
1123
1126
|
|
|
@@ -1152,6 +1155,7 @@ module Raptor
|
|
|
1152
1155
|
# @param body [Object] the response body
|
|
1153
1156
|
# @param http_version [String] "HTTP/1.1" or "HTTP/1.0"
|
|
1154
1157
|
# @return [void]
|
|
1158
|
+
# @raise [TypeError] if the body responds to none of `each`, `to_ary`, or `to_path`
|
|
1155
1159
|
#
|
|
1156
1160
|
# @rbs (TCPSocket socket, String response, Hash[String, String | Array[String]] headers, untyped body, String http_version) -> void
|
|
1157
1161
|
def write_full_response(socket, response, headers, body, http_version)
|
|
@@ -1211,8 +1215,6 @@ module Raptor
|
|
|
1211
1215
|
array.sum { |chunk| chunk.is_a?(String) ? chunk.bytesize : 0 }
|
|
1212
1216
|
elsif body.respond_to?(:to_path) && (path = body.to_path) && File.readable?(path)
|
|
1213
1217
|
File.size(path)
|
|
1214
|
-
else
|
|
1215
|
-
nil
|
|
1216
1218
|
end
|
|
1217
1219
|
end
|
|
1218
1220
|
|
|
@@ -1352,8 +1354,31 @@ module Raptor
|
|
|
1352
1354
|
end
|
|
1353
1355
|
end
|
|
1354
1356
|
|
|
1355
|
-
#
|
|
1356
|
-
#
|
|
1357
|
+
# Writes response bytes within the configured write timeout.
|
|
1358
|
+
#
|
|
1359
|
+
# @param socket [TCPSocket] the socket to write to
|
|
1360
|
+
# @param string [String] the data to write
|
|
1361
|
+
# @return [void]
|
|
1362
|
+
# @raise [Http::WriteError] if the socket is not writable within the timeout or raises IOError
|
|
1363
|
+
#
|
|
1364
|
+
# @rbs (TCPSocket socket, String string) -> void
|
|
1365
|
+
def socket_write(socket, string)
|
|
1366
|
+
Http.socket_write(socket, string, timeout: @write_timeout)
|
|
1367
|
+
end
|
|
1368
|
+
|
|
1369
|
+
# Writes response buffers in order within the configured write timeout.
|
|
1370
|
+
#
|
|
1371
|
+
# @param socket [TCPSocket] the socket to write to
|
|
1372
|
+
# @param strings [Array<String>] the buffers to write in order
|
|
1373
|
+
# @return [void]
|
|
1374
|
+
# @raise [Http::WriteError] if the socket is not writable within the timeout or raises IOError
|
|
1375
|
+
#
|
|
1376
|
+
# @rbs (TCPSocket socket, Array[String] strings) -> void
|
|
1377
|
+
def socket_writev(socket, strings)
|
|
1378
|
+
Http.socket_writev(socket, strings, timeout: @write_timeout)
|
|
1379
|
+
end
|
|
1380
|
+
|
|
1381
|
+
# Writes a completed request to the access log.
|
|
1357
1382
|
#
|
|
1358
1383
|
# @param env [Hash] the Rack environment
|
|
1359
1384
|
# @param status [Integer] the response status code
|