raptor 0.3.0 → 0.5.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/.mise.toml +2 -0
- data/Brewfile +2 -0
- data/CHANGELOG.md +21 -0
- data/README.md +28 -25
- data/ext/raptor_http2/raptor_http2.c +1 -0
- data/lib/rackup/handler/raptor.rb +20 -21
- data/lib/raptor/cli.rb +46 -14
- data/lib/raptor/cluster.rb +142 -64
- data/lib/raptor/http2.rb +324 -42
- data/lib/raptor/log.rb +55 -0
- data/lib/raptor/reactor.rb +89 -53
- data/lib/raptor/request.rb +106 -61
- data/lib/raptor/server.rb +125 -51
- data/lib/raptor/stats.rb +30 -26
- data/lib/raptor/version.rb +1 -1
- data/sig/generated/raptor/cli.rbs +15 -1
- data/sig/generated/raptor/cluster.rbs +70 -38
- data/sig/generated/raptor/http2.rbs +126 -6
- data/sig/generated/raptor/log.rbs +41 -0
- data/sig/generated/raptor/reactor.rbs +44 -25
- data/sig/generated/raptor/request.rbs +36 -22
- data/sig/generated/raptor/server.rbs +63 -26
- data/sig/generated/raptor/stats.rbs +24 -20
- metadata +5 -1
data/lib/raptor/reactor.rb
CHANGED
|
@@ -9,12 +9,12 @@ module Raptor
|
|
|
9
9
|
#
|
|
10
10
|
# Reactor uses NIO selectors for efficient I/O multiplexing and implements
|
|
11
11
|
# client timeouts using a red-black tree for O(log n) timeout management.
|
|
12
|
-
# It coordinates between
|
|
13
|
-
# pools for
|
|
14
|
-
#
|
|
12
|
+
# It coordinates between ractor pools for CPU-intensive HTTP parsing and
|
|
13
|
+
# thread pools for blocking operations, and provides backlog metrics that
|
|
14
|
+
# the server uses for backpressure control to prevent overload.
|
|
15
15
|
#
|
|
16
16
|
# @example
|
|
17
|
-
# reactor = Reactor.new(
|
|
17
|
+
# reactor = Reactor.new(ractor_pool, thread_pool, client_options: {
|
|
18
18
|
# first_data_timeout: 30,
|
|
19
19
|
# chunk_data_timeout: 10
|
|
20
20
|
# })
|
|
@@ -24,38 +24,38 @@ module Raptor
|
|
|
24
24
|
# reactor.shutdown
|
|
25
25
|
#
|
|
26
26
|
class Reactor
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
# TimeoutClient extends RedBlackTree::Node to enable efficient timeout
|
|
30
|
-
# management using the tree's ordering properties.
|
|
27
|
+
# A client connection node ordered by absolute expiry time so the
|
|
28
|
+
# soonest-to-expire is always at the tree's minimum.
|
|
31
29
|
#
|
|
32
30
|
class TimeoutClient < RedBlackTree::Node
|
|
33
31
|
# @rbs attr_accessor timeout_at: Float
|
|
34
32
|
attr_accessor :timeout_at
|
|
35
33
|
|
|
36
|
-
#
|
|
34
|
+
# Semantic alias for the inherited `data` slot.
|
|
37
35
|
#
|
|
38
|
-
# @return [Hash] the client connection state
|
|
36
|
+
# @return [Hash] the client connection state
|
|
39
37
|
#
|
|
40
38
|
# @rbs () -> Hash[Symbol, untyped]
|
|
41
39
|
def client_data
|
|
42
40
|
data
|
|
43
41
|
end
|
|
44
42
|
|
|
45
|
-
#
|
|
43
|
+
# Returns seconds until expiry, clamped to 0 so an already-expired
|
|
44
|
+
# client doesn't push the next selector wait into the future.
|
|
46
45
|
#
|
|
47
46
|
# @param now [Float] current monotonic timestamp
|
|
48
|
-
# @return [Float]
|
|
47
|
+
# @return [Float] seconds until expiry, never negative
|
|
49
48
|
#
|
|
50
49
|
# @rbs (Float now) -> Float
|
|
51
50
|
def timeout(now)
|
|
52
51
|
[timeout_at - now, 0].max
|
|
53
52
|
end
|
|
54
53
|
|
|
55
|
-
#
|
|
54
|
+
# Orders nodes by `timeout_at` so the tree minimum is the next
|
|
55
|
+
# client to expire.
|
|
56
56
|
#
|
|
57
57
|
# @param other [TimeoutClient] another timeout client to compare
|
|
58
|
-
# @return [Integer] -1, 0, or 1
|
|
58
|
+
# @return [Integer] -1, 0, or 1
|
|
59
59
|
#
|
|
60
60
|
# @rbs (TimeoutClient other) -> Integer
|
|
61
61
|
def <=>(other)
|
|
@@ -76,21 +76,22 @@ 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
|
#
|
|
82
|
-
# @param thread_pool [AtomicThreadPool] thread pool for application processing
|
|
83
83
|
# @param ractor_pool [RactorPool] ractor pool for HTTP parsing
|
|
84
|
+
# @param thread_pool [AtomicThreadPool] thread pool for application processing
|
|
84
85
|
# @param client_options [Hash] timeout configuration options
|
|
85
86
|
# @option client_options [Integer] :first_data_timeout timeout for initial data
|
|
86
87
|
# @option client_options [Integer] :chunk_data_timeout timeout for subsequent chunks
|
|
87
88
|
# @option client_options [Integer] :persistent_data_timeout timeout for keep-alive connections
|
|
88
89
|
# @return [void]
|
|
89
90
|
#
|
|
90
|
-
# @rbs (untyped
|
|
91
|
-
def initialize(
|
|
92
|
-
@thread_pool = thread_pool
|
|
91
|
+
# @rbs (untyped ractor_pool, untyped thread_pool, client_options: Hash[Symbol, Integer]) -> void
|
|
92
|
+
def initialize(ractor_pool, thread_pool, client_options:)
|
|
93
93
|
@ractor_pool = ractor_pool
|
|
94
|
+
@thread_pool = thread_pool
|
|
94
95
|
@client_options = client_options
|
|
95
96
|
|
|
96
97
|
@selector = NIO::Selector.new
|
|
@@ -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,37 @@ 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
|
+
Log.rescued_error(error)
|
|
147
153
|
end
|
|
148
154
|
end
|
|
149
155
|
|
|
@@ -163,9 +169,11 @@ module Raptor
|
|
|
163
169
|
socket = state[:socket]
|
|
164
170
|
state.delete(:socket)
|
|
165
171
|
writer = state.delete(:writer)
|
|
172
|
+
flow_control = state.delete(:flow_control)
|
|
166
173
|
@id_to_socket[state[:id]] = socket
|
|
167
174
|
@socket_to_state[socket] = state
|
|
168
175
|
@id_to_writer[state[:id]] = writer if writer
|
|
176
|
+
@id_to_flow_control[state[:id]] = flow_control if flow_control
|
|
169
177
|
|
|
170
178
|
read_and_queue_for_parse(socket, state)
|
|
171
179
|
end
|
|
@@ -191,13 +199,12 @@ module Raptor
|
|
|
191
199
|
socket.close
|
|
192
200
|
end
|
|
193
201
|
|
|
194
|
-
#
|
|
195
|
-
#
|
|
196
|
-
#
|
|
197
|
-
# processing. Triggers server accept re-enabling if system capacity allows.
|
|
202
|
+
# Drops the reactor's references to a client whose parsed request
|
|
203
|
+
# has been handed off to the thread pool. The socket itself is kept
|
|
204
|
+
# open so the worker can write the response.
|
|
198
205
|
#
|
|
199
206
|
# @param id [Integer] unique client identifier
|
|
200
|
-
# @return [TCPSocket, nil] the
|
|
207
|
+
# @return [TCPSocket, nil] the socket associated with `id`, if any
|
|
201
208
|
#
|
|
202
209
|
# @rbs (Integer id) -> TCPSocket?
|
|
203
210
|
def remove(id)
|
|
@@ -262,6 +269,18 @@ module Raptor
|
|
|
262
269
|
@id_to_writer[id]
|
|
263
270
|
end
|
|
264
271
|
|
|
272
|
+
# Returns the flow controller associated with a given connection, if one
|
|
273
|
+
# was supplied when the connection was added. Used by HTTP/2 stream
|
|
274
|
+
# dispatchers to honour the peer's flow-control windows.
|
|
275
|
+
#
|
|
276
|
+
# @param id [Integer] unique client identifier
|
|
277
|
+
# @return [Object, nil] the flow controller, if found
|
|
278
|
+
#
|
|
279
|
+
# @rbs (Integer id) -> untyped?
|
|
280
|
+
def flow_control_for(id)
|
|
281
|
+
@id_to_flow_control[id]
|
|
282
|
+
end
|
|
283
|
+
|
|
265
284
|
# Updates connection state for an HTTP/2 connection after frame processing.
|
|
266
285
|
#
|
|
267
286
|
# Re-registers the socket with the selector for further reads and stores
|
|
@@ -282,10 +301,26 @@ module Raptor
|
|
|
282
301
|
socket.close
|
|
283
302
|
end
|
|
284
303
|
|
|
285
|
-
#
|
|
304
|
+
# Closes the socket for the given connection and drops all reactor state
|
|
305
|
+
# associated with it. Used to terminate HTTP/2 connections after sending
|
|
306
|
+
# a GOAWAY frame.
|
|
286
307
|
#
|
|
287
|
-
#
|
|
288
|
-
#
|
|
308
|
+
# @param id [Integer] unique client identifier
|
|
309
|
+
# @return [void]
|
|
310
|
+
#
|
|
311
|
+
# @rbs (Integer id) -> void
|
|
312
|
+
def close_connection(id)
|
|
313
|
+
socket = @id_to_socket.delete(id)
|
|
314
|
+
return unless socket
|
|
315
|
+
|
|
316
|
+
@socket_to_state.delete(socket)
|
|
317
|
+
@id_to_writer.delete(id)
|
|
318
|
+
@id_to_flow_control.delete(id)
|
|
319
|
+
socket.close rescue nil
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Closes the registration queue and wakes the selector so the
|
|
323
|
+
# event loop drains pending work and exits.
|
|
289
324
|
#
|
|
290
325
|
# @return [void]
|
|
291
326
|
#
|
|
@@ -385,6 +420,7 @@ module Raptor
|
|
|
385
420
|
state = @socket_to_state.delete(socket)
|
|
386
421
|
@id_to_socket.delete(state[:id])
|
|
387
422
|
@id_to_writer.delete(state[:id])
|
|
423
|
+
@id_to_flow_control.delete(state[:id])
|
|
388
424
|
socket.close
|
|
389
425
|
end
|
|
390
426
|
|
data/lib/raptor/request.rb
CHANGED
|
@@ -5,18 +5,16 @@ require "socket"
|
|
|
5
5
|
require "stringio"
|
|
6
6
|
require "tempfile"
|
|
7
7
|
|
|
8
|
+
require "atomic-ruby/atomic_boolean"
|
|
8
9
|
require "rack"
|
|
9
10
|
|
|
10
11
|
require_relative "raptor_http"
|
|
11
12
|
|
|
12
13
|
module Raptor
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# low-level HTTP parsing and high-level Rack application interface, handling
|
|
18
|
-
# both incomplete requests (that need more data) and complete requests
|
|
19
|
-
# (ready for application processing).
|
|
14
|
+
# Parses HTTP/1.x requests and dispatches them to the Rack
|
|
15
|
+
# application. Coordinates with the Ractor pool for parsing and
|
|
16
|
+
# with the reactor for requests that need more data before they
|
|
17
|
+
# can be handled.
|
|
20
18
|
#
|
|
21
19
|
class Request
|
|
22
20
|
BODY_BUFFER_THRESHOLD = 256 * 1024
|
|
@@ -26,7 +24,6 @@ module Raptor
|
|
|
26
24
|
KEEPALIVE_READ_TIMEOUT = 0.001
|
|
27
25
|
MAX_KEEPALIVE_REQUESTS = 100
|
|
28
26
|
|
|
29
|
-
HTTP_SCHEME = "http"
|
|
30
27
|
HTTP_10 = "HTTP/1.0"
|
|
31
28
|
HTTP_11 = "HTTP/1.1"
|
|
32
29
|
STATUS_LINE_CACHE_10 = Hash.new do |h, status|
|
|
@@ -39,6 +36,7 @@ module Raptor
|
|
|
39
36
|
end
|
|
40
37
|
|
|
41
38
|
STATUS_WITH_NO_ENTITY_BODY = Set.new([204, 304, *100..199]).freeze
|
|
39
|
+
BAD_REQUEST_RESPONSE = "HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
|
|
42
40
|
INTERNAL_SERVER_ERROR_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
|
|
43
41
|
CONTENT_TOO_LARGE_RESPONSE = "HTTP/1.1 413 Content Too Large\r\nContent-Length: 0\r\nConnection: close\r\n\r\n"
|
|
44
42
|
|
|
@@ -92,11 +90,37 @@ module Raptor
|
|
|
92
90
|
[decoded, :incomplete]
|
|
93
91
|
end
|
|
94
92
|
|
|
93
|
+
# Writes `string` in full, retrying on partial writes. Bounded by
|
|
94
|
+
# `WRITE_TIMEOUT` so a slow client can't pin the writing thread.
|
|
95
|
+
#
|
|
96
|
+
# @param socket [TCPSocket] the socket to write to
|
|
97
|
+
# @param string [String] the data to write
|
|
98
|
+
# @return [void]
|
|
99
|
+
# @raise [WriteError] if the socket is not writable within the timeout or raises IOError
|
|
100
|
+
#
|
|
101
|
+
# @rbs (TCPSocket socket, String string) -> void
|
|
102
|
+
def self.socket_write(socket, string)
|
|
103
|
+
bytes = 0
|
|
104
|
+
byte_size = string.bytesize
|
|
105
|
+
|
|
106
|
+
while bytes < byte_size
|
|
107
|
+
begin
|
|
108
|
+
bytes += socket.write_nonblock(bytes.zero? ? string : string.byteslice(bytes..-1))
|
|
109
|
+
rescue IO::WaitWritable
|
|
110
|
+
raise WriteError unless socket.wait_writable(WRITE_TIMEOUT)
|
|
111
|
+
retry
|
|
112
|
+
rescue IOError
|
|
113
|
+
raise WriteError
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
95
118
|
# @rbs @app: ^(Hash[String, untyped]) -> [Integer, Hash[String, String | Array[String]], untyped]
|
|
96
119
|
# @rbs @server_port: Integer
|
|
97
120
|
# @rbs @max_body_size: Integer?
|
|
98
121
|
# @rbs @body_spool_threshold: Integer?
|
|
99
122
|
# @rbs @on_error: ^(Hash[String, untyped]?, Exception) -> void | nil
|
|
123
|
+
# @rbs @running: AtomicBoolean
|
|
100
124
|
|
|
101
125
|
# Creates a new Request handler.
|
|
102
126
|
#
|
|
@@ -115,6 +139,17 @@ module Raptor
|
|
|
115
139
|
@max_body_size = client_options[:max_body_size]
|
|
116
140
|
@body_spool_threshold = client_options[:body_spool_threshold]
|
|
117
141
|
@on_error = on_error
|
|
142
|
+
@running = AtomicBoolean.new(true)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Signals eager keep-alive loops to stop processing further requests on
|
|
146
|
+
# their connections. In-flight requests complete normally.
|
|
147
|
+
#
|
|
148
|
+
# @return [void]
|
|
149
|
+
#
|
|
150
|
+
# @rbs () -> void
|
|
151
|
+
def shutdown
|
|
152
|
+
@running.make_false
|
|
118
153
|
end
|
|
119
154
|
|
|
120
155
|
# Eagerly reads and parses the first request on a freshly accepted
|
|
@@ -155,7 +190,12 @@ module Raptor
|
|
|
155
190
|
|
|
156
191
|
parser = HttpParser.new
|
|
157
192
|
env = {}
|
|
158
|
-
nread =
|
|
193
|
+
nread = begin
|
|
194
|
+
parser.execute(env, buffer, 0)
|
|
195
|
+
rescue HttpParserError
|
|
196
|
+
reject_malformed(socket)
|
|
197
|
+
return
|
|
198
|
+
end
|
|
159
199
|
parse_data = { parse_count: 1, content_length: parser.content_length }
|
|
160
200
|
|
|
161
201
|
body = nil
|
|
@@ -210,7 +250,11 @@ module Raptor
|
|
|
210
250
|
|
|
211
251
|
parser = Raptor::HttpParser.new
|
|
212
252
|
env = {}
|
|
213
|
-
nread =
|
|
253
|
+
nread = begin
|
|
254
|
+
parser.execute(env, data[:buffer], 0)
|
|
255
|
+
rescue Raptor::HttpParserError
|
|
256
|
+
next Ractor.make_shareable(data.merge(complete: true, malformed: true))
|
|
257
|
+
end
|
|
214
258
|
parse_data = if data[:parse_data]
|
|
215
259
|
data[:parse_data].dup
|
|
216
260
|
else
|
|
@@ -269,13 +313,19 @@ module Raptor
|
|
|
269
313
|
return
|
|
270
314
|
end
|
|
271
315
|
|
|
316
|
+
if parsed_request[:malformed]
|
|
317
|
+
socket = reactor.remove(parsed_request[:id])
|
|
318
|
+
reject_malformed(socket) if socket
|
|
319
|
+
return
|
|
320
|
+
end
|
|
321
|
+
|
|
272
322
|
unless parsed_request[:complete]
|
|
273
323
|
reactor.update_state(parsed_request)
|
|
274
324
|
else
|
|
275
325
|
socket = reactor.remove(parsed_request[:id])
|
|
276
326
|
request_count = (parsed_request[:request_count] || 0) + 1
|
|
277
|
-
remote_addr = parsed_request[:remote_addr] ||
|
|
278
|
-
url_scheme = parsed_request[:url_scheme] || HTTP_SCHEME
|
|
327
|
+
remote_addr = parsed_request[:remote_addr] || Server::DEFAULT_REMOTE_ADDR
|
|
328
|
+
url_scheme = parsed_request[:url_scheme] || Server::HTTP_SCHEME
|
|
279
329
|
|
|
280
330
|
thread_pool << proc do
|
|
281
331
|
process_client(
|
|
@@ -394,6 +444,11 @@ module Raptor
|
|
|
394
444
|
# @rbs (TCPSocket socket, Integer id, Reactor reactor, AtomicThreadPool thread_pool, Integer request_count, String remote_addr, String url_scheme) -> void
|
|
395
445
|
def eager_keepalive(socket, id, reactor, thread_pool, request_count, remote_addr, url_scheme)
|
|
396
446
|
loop do
|
|
447
|
+
unless @running.true?
|
|
448
|
+
socket.close rescue nil
|
|
449
|
+
return
|
|
450
|
+
end
|
|
451
|
+
|
|
397
452
|
unless socket.wait_readable(KEEPALIVE_READ_TIMEOUT)
|
|
398
453
|
reactor.persist(socket, id, request_count, remote_addr: remote_addr, url_scheme: url_scheme)
|
|
399
454
|
return
|
|
@@ -418,7 +473,12 @@ module Raptor
|
|
|
418
473
|
|
|
419
474
|
parser = HttpParser.new
|
|
420
475
|
env = {}
|
|
421
|
-
nread =
|
|
476
|
+
nread = begin
|
|
477
|
+
parser.execute(env, buffer, 0)
|
|
478
|
+
rescue HttpParserError
|
|
479
|
+
reject_malformed(socket)
|
|
480
|
+
return
|
|
481
|
+
end
|
|
422
482
|
parse_data = { parse_count: 1, content_length: parser.content_length }
|
|
423
483
|
|
|
424
484
|
body = nil
|
|
@@ -515,6 +575,18 @@ module Raptor
|
|
|
515
575
|
socket.close rescue nil
|
|
516
576
|
end
|
|
517
577
|
|
|
578
|
+
# Writes a 400 response and closes the socket. Used when the HTTP parser
|
|
579
|
+
# rejects the request line or headers.
|
|
580
|
+
#
|
|
581
|
+
# @param socket [TCPSocket] the client socket
|
|
582
|
+
# @return [void]
|
|
583
|
+
#
|
|
584
|
+
# @rbs (TCPSocket socket) -> void
|
|
585
|
+
def reject_malformed(socket)
|
|
586
|
+
socket.write(BAD_REQUEST_RESPONSE) rescue nil
|
|
587
|
+
socket.close rescue nil
|
|
588
|
+
end
|
|
589
|
+
|
|
518
590
|
# Builds a Rack environment hash from parsed HTTP request data.
|
|
519
591
|
#
|
|
520
592
|
# Populates all required Rack env keys including rack.* keys, REMOTE_ADDR,
|
|
@@ -529,7 +601,7 @@ module Raptor
|
|
|
529
601
|
# @return [Hash] fully populated Rack environment hash
|
|
530
602
|
#
|
|
531
603
|
# @rbs (Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, String? body, TCPSocket socket, ?remote_addr: String, ?url_scheme: String) -> Hash[String, untyped]
|
|
532
|
-
def build_rack_env(env, parse_data, body, socket, remote_addr:
|
|
604
|
+
def build_rack_env(env, parse_data, body, socket, remote_addr: Server::DEFAULT_REMOTE_ADDR, url_scheme: Server::HTTP_SCHEME)
|
|
533
605
|
env[Rack::RACK_VERSION] = Rack::VERSION
|
|
534
606
|
env[Rack::RACK_URL_SCHEME] = url_scheme
|
|
535
607
|
env[Rack::RACK_INPUT] = build_rack_input(body)
|
|
@@ -569,7 +641,7 @@ module Raptor
|
|
|
569
641
|
env[Rack::SERVER_NAME] ||= host
|
|
570
642
|
env[Rack::SERVER_PORT] ||= port || @server_port.to_s
|
|
571
643
|
else
|
|
572
|
-
env[Rack::SERVER_NAME] ||=
|
|
644
|
+
env[Rack::SERVER_NAME] ||= Server::DEFAULT_SERVER_NAME
|
|
573
645
|
env[Rack::SERVER_PORT] ||= @server_port.to_s
|
|
574
646
|
end
|
|
575
647
|
|
|
@@ -644,7 +716,7 @@ module Raptor
|
|
|
644
716
|
end
|
|
645
717
|
response << "\r\n"
|
|
646
718
|
|
|
647
|
-
socket_write(socket, response)
|
|
719
|
+
Request.socket_write(socket, response)
|
|
648
720
|
end
|
|
649
721
|
|
|
650
722
|
# Writes a complete HTTP response to the socket.
|
|
@@ -777,7 +849,7 @@ module Raptor
|
|
|
777
849
|
def write_hijacked_response(socket, response, headers, response_hijack)
|
|
778
850
|
response << format_headers(headers)
|
|
779
851
|
response << "\r\n"
|
|
780
|
-
socket_write(socket, response)
|
|
852
|
+
Request.socket_write(socket, response)
|
|
781
853
|
uncork_socket(socket)
|
|
782
854
|
response_hijack.call(socket)
|
|
783
855
|
end
|
|
@@ -802,7 +874,7 @@ module Raptor
|
|
|
802
874
|
|
|
803
875
|
response << format_headers(headers)
|
|
804
876
|
response << "\r\n"
|
|
805
|
-
socket_write(socket, response)
|
|
877
|
+
Request.socket_write(socket, response)
|
|
806
878
|
end
|
|
807
879
|
|
|
808
880
|
# Writes a complete response with a body.
|
|
@@ -824,7 +896,7 @@ module Raptor
|
|
|
824
896
|
if body.respond_to?(:call)
|
|
825
897
|
response << format_headers(headers)
|
|
826
898
|
response << "\r\n"
|
|
827
|
-
socket_write(socket, response)
|
|
899
|
+
Request.socket_write(socket, response)
|
|
828
900
|
uncork_socket(socket)
|
|
829
901
|
body.call(socket)
|
|
830
902
|
return
|
|
@@ -861,7 +933,7 @@ module Raptor
|
|
|
861
933
|
raise TypeError, "body must respond to each, to_ary, or to_path"
|
|
862
934
|
end
|
|
863
935
|
|
|
864
|
-
socket_write(socket, "0\r\n\r\n") if use_chunked
|
|
936
|
+
Request.socket_write(socket, "0\r\n\r\n") if use_chunked
|
|
865
937
|
end
|
|
866
938
|
|
|
867
939
|
# Calculates content length from an array or file body without consuming it.
|
|
@@ -901,15 +973,15 @@ module Raptor
|
|
|
901
973
|
def write_file_body(socket, response, path, content_length, use_chunked)
|
|
902
974
|
File.open(path, "rb") do |file|
|
|
903
975
|
if use_chunked
|
|
904
|
-
socket_write(socket, response)
|
|
976
|
+
Request.socket_write(socket, response)
|
|
905
977
|
while (chunk = file.read(FILE_CHUNK_SIZE))
|
|
906
|
-
socket_write(socket, "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n")
|
|
978
|
+
Request.socket_write(socket, "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n")
|
|
907
979
|
end
|
|
908
980
|
elsif content_length && content_length < BODY_BUFFER_THRESHOLD
|
|
909
981
|
response << file.read(content_length)
|
|
910
|
-
socket_write(socket, response)
|
|
982
|
+
Request.socket_write(socket, response)
|
|
911
983
|
else
|
|
912
|
-
socket_write(socket, response)
|
|
984
|
+
Request.socket_write(socket, response)
|
|
913
985
|
IO.copy_stream(file, socket)
|
|
914
986
|
end
|
|
915
987
|
end
|
|
@@ -952,12 +1024,12 @@ module Raptor
|
|
|
952
1024
|
|
|
953
1025
|
if use_chunked
|
|
954
1026
|
response << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n"
|
|
955
|
-
socket_write(socket, response)
|
|
1027
|
+
Request.socket_write(socket, response)
|
|
956
1028
|
elsif chunk.bytesize < BODY_BUFFER_THRESHOLD
|
|
957
|
-
socket_write(socket, response << chunk)
|
|
1029
|
+
Request.socket_write(socket, response << chunk)
|
|
958
1030
|
else
|
|
959
|
-
socket_write(socket, response)
|
|
960
|
-
socket_write(socket, chunk)
|
|
1031
|
+
Request.socket_write(socket, response)
|
|
1032
|
+
Request.socket_write(socket, chunk)
|
|
961
1033
|
end
|
|
962
1034
|
end
|
|
963
1035
|
|
|
@@ -973,13 +1045,13 @@ module Raptor
|
|
|
973
1045
|
# @rbs (TCPSocket socket, String response, Array[String] body_array, bool use_chunked) -> void
|
|
974
1046
|
def write_multiple_chunks(socket, response, body_array, use_chunked)
|
|
975
1047
|
if use_chunked
|
|
976
|
-
socket_write(socket, response)
|
|
1048
|
+
Request.socket_write(socket, response)
|
|
977
1049
|
body_array.each do |chunk|
|
|
978
1050
|
raise TypeError, "body must yield String values" unless chunk.is_a?(String)
|
|
979
1051
|
|
|
980
1052
|
next if chunk.empty?
|
|
981
1053
|
|
|
982
|
-
socket_write(socket, "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n")
|
|
1054
|
+
Request.socket_write(socket, "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n")
|
|
983
1055
|
end
|
|
984
1056
|
else
|
|
985
1057
|
body_array.each do |chunk|
|
|
@@ -987,7 +1059,7 @@ module Raptor
|
|
|
987
1059
|
|
|
988
1060
|
response << chunk
|
|
989
1061
|
end
|
|
990
|
-
socket_write(socket, response)
|
|
1062
|
+
Request.socket_write(socket, response)
|
|
991
1063
|
end
|
|
992
1064
|
end
|
|
993
1065
|
|
|
@@ -1003,13 +1075,13 @@ module Raptor
|
|
|
1003
1075
|
# @rbs (TCPSocket socket, String response, untyped body, bool use_chunked) -> void
|
|
1004
1076
|
def write_enumerable_body(socket, response, body, use_chunked)
|
|
1005
1077
|
if use_chunked
|
|
1006
|
-
socket_write(socket, response)
|
|
1078
|
+
Request.socket_write(socket, response)
|
|
1007
1079
|
body.each do |chunk|
|
|
1008
1080
|
raise TypeError, "body must yield String values" unless chunk.is_a?(String)
|
|
1009
1081
|
|
|
1010
1082
|
next if chunk.empty?
|
|
1011
1083
|
|
|
1012
|
-
socket_write(socket, "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n")
|
|
1084
|
+
Request.socket_write(socket, "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n")
|
|
1013
1085
|
end
|
|
1014
1086
|
else
|
|
1015
1087
|
body.each do |chunk|
|
|
@@ -1017,7 +1089,7 @@ module Raptor
|
|
|
1017
1089
|
|
|
1018
1090
|
response << chunk
|
|
1019
1091
|
end
|
|
1020
|
-
socket_write(socket, response)
|
|
1092
|
+
Request.socket_write(socket, response)
|
|
1021
1093
|
end
|
|
1022
1094
|
end
|
|
1023
1095
|
|
|
@@ -1090,33 +1162,6 @@ module Raptor
|
|
|
1090
1162
|
end
|
|
1091
1163
|
end
|
|
1092
1164
|
|
|
1093
|
-
# Writes a string to the socket, retrying on partial writes and flow control blocks.
|
|
1094
|
-
#
|
|
1095
|
-
# Uses write_nonblock with a 5-second writable timeout to avoid blocking the
|
|
1096
|
-
# thread indefinitely on slow clients.
|
|
1097
|
-
#
|
|
1098
|
-
# @param socket [TCPSocket] the socket to write to
|
|
1099
|
-
# @param string [String] the data to write
|
|
1100
|
-
# @return [void]
|
|
1101
|
-
# @raise [WriteError] if the socket is not writable within the timeout or raises IOError
|
|
1102
|
-
#
|
|
1103
|
-
# @rbs (TCPSocket socket, String string) -> void
|
|
1104
|
-
def socket_write(socket, string)
|
|
1105
|
-
bytes = 0
|
|
1106
|
-
byte_size = string.bytesize
|
|
1107
|
-
|
|
1108
|
-
while bytes < byte_size
|
|
1109
|
-
begin
|
|
1110
|
-
bytes += socket.write_nonblock(bytes.zero? ? string : string.byteslice(bytes..-1))
|
|
1111
|
-
rescue IO::WaitWritable
|
|
1112
|
-
raise WriteError unless socket.wait_writable(WRITE_TIMEOUT)
|
|
1113
|
-
retry
|
|
1114
|
-
rescue IOError
|
|
1115
|
-
raise WriteError
|
|
1116
|
-
end
|
|
1117
|
-
end
|
|
1118
|
-
end
|
|
1119
|
-
|
|
1120
1165
|
if Socket.const_defined?(:TCP_CORK)
|
|
1121
1166
|
# Enables TCP_CORK on the socket to batch outgoing packets into fewer segments.
|
|
1122
1167
|
#
|