raptor 0.20.2 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +41 -64
- data/docs/raptor-vs-puma.md +22 -20
- data/ext/raptor_http2/raptor_http2.c +12 -0
- data/lib/raptor/binder.rb +39 -13
- data/lib/raptor/cli.rb +10 -0
- data/lib/raptor/cluster.rb +6 -3
- data/lib/raptor/detached_body.rb +293 -0
- data/lib/raptor/http.rb +18 -0
- data/lib/raptor/http1.rb +87 -35
- data/lib/raptor/http2.rb +501 -112
- data/lib/raptor/reactor.rb +992 -50
- data/lib/raptor/server.rb +12 -0
- data/lib/raptor/version.rb +1 -1
- data/lib/raptor.rb +1 -0
- data/sig/generated/raptor/binder.rbs +26 -13
- data/sig/generated/raptor/detached_body.rbs +127 -0
- data/sig/generated/raptor/http.rbs +12 -0
- data/sig/generated/raptor/http1.rbs +22 -16
- data/sig/generated/raptor/http2.rbs +240 -25
- data/sig/generated/raptor/reactor.rbs +388 -19
- metadata +4 -2
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# rbs_inline: enabled
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "atomic-ruby/atom"
|
|
5
|
+
|
|
6
|
+
module Raptor
|
|
7
|
+
# A bounded response body that may be written after its application thread
|
|
8
|
+
# has returned.
|
|
9
|
+
#
|
|
10
|
+
class DetachedBody
|
|
11
|
+
DEFAULT_MAX_BUFFER_SIZE = 64 * 1024
|
|
12
|
+
|
|
13
|
+
# Tracks buffered bytes shared by a group of detached bodies.
|
|
14
|
+
#
|
|
15
|
+
class Budget
|
|
16
|
+
# @rbs @max_size: Integer
|
|
17
|
+
# @rbs @size: Atom
|
|
18
|
+
|
|
19
|
+
# @rbs (Integer max_size) -> void
|
|
20
|
+
def initialize(max_size)
|
|
21
|
+
@max_size = max_size
|
|
22
|
+
@size = Atom.new(0)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @rbs (Integer bytes) -> bool
|
|
26
|
+
def reserve(bytes)
|
|
27
|
+
reserved = false
|
|
28
|
+
@size.swap do |size|
|
|
29
|
+
reserved = size + bytes <= @max_size
|
|
30
|
+
reserved ? size + bytes : size
|
|
31
|
+
end
|
|
32
|
+
reserved
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @rbs (Integer bytes) -> void
|
|
36
|
+
def release(bytes)
|
|
37
|
+
@size.swap { |size| size - bytes }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @rbs @max_buffer_size: Integer
|
|
42
|
+
# @rbs @state: Atom
|
|
43
|
+
# @rbs @budgets: Array[Budget]
|
|
44
|
+
# @rbs @wake: ^() -> void
|
|
45
|
+
# @rbs @dispatch: ^(Proc) -> void
|
|
46
|
+
# @rbs @finished: ^(Symbol) -> void
|
|
47
|
+
# @rbs @on_open: (^(DetachedBody) -> void)?
|
|
48
|
+
# @rbs @on_close: (^(Symbol) -> void)?
|
|
49
|
+
|
|
50
|
+
# Creates a detached body with a bounded byte buffer.
|
|
51
|
+
#
|
|
52
|
+
# @param max_buffer_size [Integer] maximum body bytes waiting to be written
|
|
53
|
+
# @return [void]
|
|
54
|
+
#
|
|
55
|
+
# @rbs (?max_buffer_size: Integer) -> void
|
|
56
|
+
def initialize(max_buffer_size: DEFAULT_MAX_BUFFER_SIZE)
|
|
57
|
+
raise ArgumentError, "max_buffer_size must be positive" unless max_buffer_size.positive?
|
|
58
|
+
|
|
59
|
+
@max_buffer_size = max_buffer_size
|
|
60
|
+
@state = Atom.new({chunks: [], size: 0, closing: false, closed: false, notified: false, trailers: {}})
|
|
61
|
+
@budgets = []
|
|
62
|
+
@wake = proc {}
|
|
63
|
+
@dispatch = proc { |callback| callback.call }
|
|
64
|
+
@finished = proc {}
|
|
65
|
+
@on_open = nil
|
|
66
|
+
@on_close = nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Registers a callback for when the server accepts the response stream.
|
|
70
|
+
#
|
|
71
|
+
# @yieldparam stream [DetachedBody] the opened response stream
|
|
72
|
+
# @return [DetachedBody]
|
|
73
|
+
#
|
|
74
|
+
# @rbs () { (DetachedBody stream) -> void } -> DetachedBody
|
|
75
|
+
def on_open(&block)
|
|
76
|
+
@on_open = block
|
|
77
|
+
self
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Registers a callback for when the response stream closes.
|
|
81
|
+
#
|
|
82
|
+
# @yieldparam reason [Symbol] why the stream closed
|
|
83
|
+
# @return [DetachedBody]
|
|
84
|
+
#
|
|
85
|
+
# @rbs () { (Symbol reason) -> void } -> DetachedBody
|
|
86
|
+
def on_close(&block)
|
|
87
|
+
@on_close = block
|
|
88
|
+
self
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Adds body bytes without waiting for socket or flow-control capacity.
|
|
92
|
+
#
|
|
93
|
+
# @param chunk [String] response body bytes
|
|
94
|
+
# @return [Symbol] `:accepted`, `:full`, or `:closed`
|
|
95
|
+
#
|
|
96
|
+
# @rbs (String chunk) -> Symbol
|
|
97
|
+
def try_write(chunk)
|
|
98
|
+
raise TypeError, "body must yield String values" unless chunk.is_a?(String)
|
|
99
|
+
|
|
100
|
+
chunk = chunk.dup.freeze
|
|
101
|
+
if chunk.empty?
|
|
102
|
+
state = @state.value
|
|
103
|
+
return state[:closed] || state[:closing] ? :closed : :accepted
|
|
104
|
+
end
|
|
105
|
+
return :full unless reserve(chunk.bytesize)
|
|
106
|
+
|
|
107
|
+
result = :closed
|
|
108
|
+
wake = false
|
|
109
|
+
@state.swap do |state|
|
|
110
|
+
if state[:closed] || state[:closing]
|
|
111
|
+
result = :closed
|
|
112
|
+
wake = false
|
|
113
|
+
state
|
|
114
|
+
elsif state[:size] + chunk.bytesize > @max_buffer_size
|
|
115
|
+
result = :full
|
|
116
|
+
wake = false
|
|
117
|
+
state
|
|
118
|
+
else
|
|
119
|
+
result = :accepted
|
|
120
|
+
wake = !state[:notified]
|
|
121
|
+
state.merge(
|
|
122
|
+
chunks: state[:chunks] + [chunk],
|
|
123
|
+
size: state[:size] + chunk.bytesize,
|
|
124
|
+
notified: true
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
if result == :accepted
|
|
130
|
+
@wake.call if wake
|
|
131
|
+
else
|
|
132
|
+
release(chunk.bytesize)
|
|
133
|
+
end
|
|
134
|
+
result
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Finishes the body after all accepted bytes have been written.
|
|
138
|
+
#
|
|
139
|
+
# @param trailers [Hash] trailing response headers
|
|
140
|
+
# @return [void]
|
|
141
|
+
#
|
|
142
|
+
# @rbs (?trailers: Hash[String, String | Array[String]]) -> void
|
|
143
|
+
def close(trailers: {})
|
|
144
|
+
trailers = trailers.to_h do |name, value|
|
|
145
|
+
value = value.is_a?(Array) ? value.map { _1.dup.freeze }.freeze : value.dup.freeze
|
|
146
|
+
[name.dup.freeze, value]
|
|
147
|
+
end.freeze
|
|
148
|
+
wake = false
|
|
149
|
+
@state.swap do |state|
|
|
150
|
+
if state[:closed] || state[:closing]
|
|
151
|
+
wake = false
|
|
152
|
+
next state
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
wake = !state[:notified]
|
|
156
|
+
state.merge(closing: true, notified: true, trailers: trailers)
|
|
157
|
+
end
|
|
158
|
+
@wake.call if wake
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Returns whether the stream has closed.
|
|
162
|
+
#
|
|
163
|
+
# @return [Boolean]
|
|
164
|
+
#
|
|
165
|
+
# @rbs () -> bool
|
|
166
|
+
def closed?
|
|
167
|
+
@state.value[:closed]
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Connects the body to its reactor-owned response stream.
|
|
171
|
+
#
|
|
172
|
+
# @rbs (Array[Budget] budgets, ^() -> void wake, ^(Proc) -> void dispatch, ^(Symbol) -> void finished) -> bool
|
|
173
|
+
def attach(budgets, wake, dispatch, finished)
|
|
174
|
+
@wake = wake
|
|
175
|
+
@dispatch = dispatch
|
|
176
|
+
@finished = finished
|
|
177
|
+
size = @state.value[:size]
|
|
178
|
+
reserved = []
|
|
179
|
+
budgets.each do |budget|
|
|
180
|
+
unless budget.reserve(size)
|
|
181
|
+
reserved.each { |held| held.release(size) }
|
|
182
|
+
finish(:full)
|
|
183
|
+
return false
|
|
184
|
+
end
|
|
185
|
+
reserved << budget
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
@budgets = budgets
|
|
189
|
+
true
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Notifies the application that its response stream is ready.
|
|
193
|
+
#
|
|
194
|
+
# @rbs () -> void
|
|
195
|
+
def open
|
|
196
|
+
callback = @on_open
|
|
197
|
+
@dispatch.call(proc { callback.call(self) }) if callback
|
|
198
|
+
@wake.call if @state.value[:notified]
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Returns the size of the next buffered chunk, 0 when closing, or nil
|
|
202
|
+
# while waiting for more data.
|
|
203
|
+
#
|
|
204
|
+
# @rbs () -> Integer?
|
|
205
|
+
def next_size
|
|
206
|
+
state = @state.value
|
|
207
|
+
state[:chunks].first&.bytesize || (0 if state[:closing])
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Removes up to `max_bytes` from the next buffered chunk.
|
|
211
|
+
#
|
|
212
|
+
# @rbs (Integer max_bytes) -> String?
|
|
213
|
+
def shift(max_bytes)
|
|
214
|
+
chunk = nil
|
|
215
|
+
@state.swap do |state|
|
|
216
|
+
chunk = nil
|
|
217
|
+
first = state[:chunks].first
|
|
218
|
+
next state unless first
|
|
219
|
+
|
|
220
|
+
size = max_bytes < first.bytesize ? max_bytes : first.bytesize
|
|
221
|
+
chunk = first.byteslice(0, size)
|
|
222
|
+
chunks = if size == first.bytesize
|
|
223
|
+
state[:chunks].drop(1)
|
|
224
|
+
else
|
|
225
|
+
[first.byteslice(size..-1).freeze] + state[:chunks].drop(1)
|
|
226
|
+
end
|
|
227
|
+
state.merge(
|
|
228
|
+
chunks: chunks,
|
|
229
|
+
size: state[:size] - size,
|
|
230
|
+
notified: !chunks.empty? || state[:closing]
|
|
231
|
+
)
|
|
232
|
+
end
|
|
233
|
+
release(chunk.bytesize) if chunk
|
|
234
|
+
chunk
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Returns the response trailers supplied when the body closed.
|
|
238
|
+
#
|
|
239
|
+
# @rbs () -> Hash[String, String | Array[String]]
|
|
240
|
+
def trailers
|
|
241
|
+
@state.value[:trailers]
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Closes the stream and invokes its callback exactly once.
|
|
245
|
+
#
|
|
246
|
+
# @rbs (Symbol reason) -> void
|
|
247
|
+
def finish(reason)
|
|
248
|
+
callback = false
|
|
249
|
+
remaining = 0
|
|
250
|
+
@state.swap do |state|
|
|
251
|
+
if state[:closed]
|
|
252
|
+
callback = false
|
|
253
|
+
remaining = 0
|
|
254
|
+
state
|
|
255
|
+
else
|
|
256
|
+
callback = true
|
|
257
|
+
remaining = state[:size]
|
|
258
|
+
state.merge(chunks: [], size: 0, closed: true, notified: false)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
return unless callback
|
|
262
|
+
|
|
263
|
+
release(remaining)
|
|
264
|
+
@dispatch.call(proc do
|
|
265
|
+
begin
|
|
266
|
+
@on_close&.call(reason)
|
|
267
|
+
ensure
|
|
268
|
+
@finished.call(reason)
|
|
269
|
+
end
|
|
270
|
+
end)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
private
|
|
274
|
+
|
|
275
|
+
# @rbs (Integer bytes) -> bool
|
|
276
|
+
def reserve(bytes)
|
|
277
|
+
reserved = []
|
|
278
|
+
@budgets.each do |budget|
|
|
279
|
+
unless budget.reserve(bytes)
|
|
280
|
+
reserved.each { |held| held.release(bytes) }
|
|
281
|
+
return false
|
|
282
|
+
end
|
|
283
|
+
reserved << budget
|
|
284
|
+
end
|
|
285
|
+
true
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# @rbs (Integer bytes) -> void
|
|
289
|
+
def release(bytes)
|
|
290
|
+
@budgets.each { |budget| budget.release(bytes) }
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
end
|
data/lib/raptor/http.rb
CHANGED
|
@@ -107,6 +107,24 @@ module Raptor
|
|
|
107
107
|
end
|
|
108
108
|
end
|
|
109
109
|
|
|
110
|
+
# Calls every `rack.response_finished` callback in reverse
|
|
111
|
+
# registration order, rescuing any that raise.
|
|
112
|
+
#
|
|
113
|
+
# @param env [Hash, nil] the Rack environment
|
|
114
|
+
# @param status [Integer, nil] the response status code
|
|
115
|
+
# @param headers [Hash, nil] the response headers
|
|
116
|
+
# @param error [Exception, nil] any error raised during processing, or nil on success
|
|
117
|
+
# @return [void]
|
|
118
|
+
#
|
|
119
|
+
# @rbs (Hash[String, untyped]? env, Integer? status, Hash[String, String | Array[String]]? headers, Exception? error) -> void
|
|
120
|
+
def self.call_response_finished(env, status, headers, error)
|
|
121
|
+
return unless env && env[Rack::RACK_RESPONSE_FINISHED].is_a?(Array)
|
|
122
|
+
|
|
123
|
+
env[Rack::RACK_RESPONSE_FINISHED].reverse_each do |callable|
|
|
124
|
+
callable.call(env, status, headers, error) rescue nil
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
110
128
|
# Writes a Common Log Format entry to `io`. Write failures are silently
|
|
111
129
|
# ignored.
|
|
112
130
|
#
|
data/lib/raptor/http1.rb
CHANGED
|
@@ -8,6 +8,7 @@ require "tempfile"
|
|
|
8
8
|
require "atomic-ruby/atomic_boolean"
|
|
9
9
|
require "rack"
|
|
10
10
|
|
|
11
|
+
require_relative "detached_body"
|
|
11
12
|
require_relative "http"
|
|
12
13
|
require_relative "raptor_http"
|
|
13
14
|
require_relative "thread_locals"
|
|
@@ -97,6 +98,28 @@ module Raptor
|
|
|
97
98
|
false
|
|
98
99
|
end
|
|
99
100
|
|
|
101
|
+
# Encodes one HTTP/1.1 response chunk.
|
|
102
|
+
#
|
|
103
|
+
# @rbs (String chunk) -> String
|
|
104
|
+
def self.encode_chunk(chunk)
|
|
105
|
+
HttpParser.chunked_encode(String.new, chunk)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Encodes the final HTTP/1.1 response chunk and its trailers.
|
|
109
|
+
#
|
|
110
|
+
# @rbs (Hash[String, String | Array[String]] trailers) -> String
|
|
111
|
+
def self.encode_trailers(trailers)
|
|
112
|
+
normalized = trailers.each_with_object({}) do |(key, value), headers|
|
|
113
|
+
name = key.match?(/[A-Z]/) ? key.downcase : key
|
|
114
|
+
next if name.start_with?(RACK_HEADER_PREFIX) || name == "status"
|
|
115
|
+
|
|
116
|
+
headers[name] = value
|
|
117
|
+
end
|
|
118
|
+
response = +"0\r\n"
|
|
119
|
+
HttpParser.format_headers(response, normalized)
|
|
120
|
+
response << "\r\n"
|
|
121
|
+
end
|
|
122
|
+
|
|
100
123
|
# Advances an HTTP/1.x request parse from the state hash's buffered
|
|
101
124
|
# bytes. A complete well-formed request returns with `:complete` set
|
|
102
125
|
# plus populated `:env` and `:body`; malformed or oversized input flips
|
|
@@ -492,7 +515,7 @@ module Raptor
|
|
|
492
515
|
#
|
|
493
516
|
# @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
|
|
494
517
|
def process_client(socket, id, env, parse_data, body, reactor, thread_pool, request_count, remote_addr, url_scheme)
|
|
495
|
-
keep_alive = process_request(socket, env, parse_data, body, request_count, remote_addr, url_scheme)
|
|
518
|
+
keep_alive = process_request(socket, id, env, parse_data, body, reactor, request_count, remote_addr, url_scheme)
|
|
496
519
|
eager_keepalive(socket, id, reactor, thread_pool, request_count, remote_addr, url_scheme) if keep_alive
|
|
497
520
|
end
|
|
498
521
|
|
|
@@ -501,20 +524,22 @@ module Raptor
|
|
|
501
524
|
# for another request.
|
|
502
525
|
#
|
|
503
526
|
# @param socket [TCPSocket] the client socket
|
|
527
|
+
# @param id [Integer] unique client identifier
|
|
504
528
|
# @param env [Hash] partial env hash from the HTTP parser
|
|
505
529
|
# @param parse_data [Hash] metadata from the parsing pass
|
|
506
530
|
# @param body [String, nil] decoded request body
|
|
531
|
+
# @param reactor [Reactor] the reactor managing the client connection
|
|
507
532
|
# @param request_count [Integer] number of requests handled on this connection
|
|
508
533
|
# @param remote_addr [String] client IP address
|
|
509
534
|
# @param url_scheme [String] "http" or "https"
|
|
510
535
|
# @return [Boolean] true if the connection should be kept alive
|
|
511
536
|
#
|
|
512
|
-
# @rbs (TCPSocket socket, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, String? body, Integer request_count, String remote_addr, String url_scheme) -> bool
|
|
513
|
-
def process_request(socket, env, parse_data, body, request_count, remote_addr, url_scheme)
|
|
537
|
+
# @rbs (TCPSocket socket, Integer id, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, String? body, Reactor reactor, Integer request_count, String remote_addr, String url_scheme) -> bool
|
|
538
|
+
def process_request(socket, id, env, parse_data, body, reactor, request_count, remote_addr, url_scheme)
|
|
514
539
|
if @clean_fiber_locals
|
|
515
|
-
Fiber.new { perform_request(socket, env, parse_data, body, request_count, remote_addr, url_scheme) }.resume
|
|
540
|
+
Fiber.new { perform_request(socket, id, env, parse_data, body, reactor, request_count, remote_addr, url_scheme) }.resume
|
|
516
541
|
else
|
|
517
|
-
perform_request(socket, env, parse_data, body, request_count, remote_addr, url_scheme)
|
|
542
|
+
perform_request(socket, id, env, parse_data, body, reactor, request_count, remote_addr, url_scheme)
|
|
518
543
|
end
|
|
519
544
|
ensure
|
|
520
545
|
ThreadLocals.clear if @clean_thread_locals
|
|
@@ -522,13 +547,14 @@ module Raptor
|
|
|
522
547
|
|
|
523
548
|
# Calls the Rack app and writes its response for one request.
|
|
524
549
|
#
|
|
525
|
-
# @rbs (TCPSocket socket, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, String? body, Integer request_count, String remote_addr, String url_scheme) -> bool
|
|
526
|
-
def perform_request(socket, env, parse_data, body, request_count, remote_addr, url_scheme)
|
|
550
|
+
# @rbs (TCPSocket socket, Integer id, Hash[String, untyped] env, Hash[Symbol, untyped] parse_data, String? body, Reactor reactor, Integer request_count, String remote_addr, String url_scheme) -> bool
|
|
551
|
+
def perform_request(socket, id, env, parse_data, body, reactor, request_count, remote_addr, url_scheme)
|
|
527
552
|
rack_env = nil
|
|
528
553
|
status = nil
|
|
529
554
|
headers = nil
|
|
530
555
|
hijacked = false
|
|
531
556
|
keep_alive = false
|
|
557
|
+
detached = false
|
|
532
558
|
response_started = false
|
|
533
559
|
|
|
534
560
|
begin
|
|
@@ -540,16 +566,40 @@ module Raptor
|
|
|
540
566
|
body.close if body.respond_to?(:close)
|
|
541
567
|
else
|
|
542
568
|
hijacked = headers.is_a?(Hash) && !!headers[Rack::RACK_HIJACK]
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
569
|
+
no_body = rack_env[Rack::REQUEST_METHOD] == "HEAD" || (status >= 100 && status < 200) || status == 204 || status == 304
|
|
570
|
+
detached = body.is_a?(DetachedBody) && !no_body
|
|
571
|
+
if detached
|
|
572
|
+
chunked = rack_env[Rack::SERVER_PROTOCOL] == HTTP_11
|
|
573
|
+
keep_alive = chunked && keep_alive?(rack_env, request_count)
|
|
574
|
+
response_started = true
|
|
575
|
+
write_detached_response(socket, status, headers, chunked: chunked, keep_alive: keep_alive)
|
|
576
|
+
finished = proc do |reason|
|
|
577
|
+
error = IOError.new("detached response closed") unless reason == :closed
|
|
578
|
+
write_access_log(rack_env, status, "-", remote_addr) if @access_log_io
|
|
579
|
+
Http.call_response_finished(rack_env, status, headers, error)
|
|
580
|
+
end
|
|
581
|
+
reactor.attach_http1_body(
|
|
582
|
+
socket,
|
|
583
|
+
id,
|
|
584
|
+
body,
|
|
585
|
+
{chunked: chunked, keep_alive: keep_alive, request_count: request_count, remote_addr: remote_addr, url_scheme: url_scheme},
|
|
586
|
+
finished
|
|
587
|
+
)
|
|
588
|
+
else
|
|
589
|
+
streaming = body.respond_to?(:call) && !body.respond_to?(:each)
|
|
590
|
+
keep_alive = (hijacked || streaming) ? false : keep_alive?(rack_env, request_count)
|
|
591
|
+
response_size = response_size(headers, body) if @access_log_io && !hijacked
|
|
592
|
+
response_started = true
|
|
593
|
+
write_response(socket, rack_env, status, headers, body, keep_alive: keep_alive)
|
|
594
|
+
body.finish(:closed) if body.is_a?(DetachedBody)
|
|
595
|
+
end
|
|
548
596
|
end
|
|
549
597
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
598
|
+
unless detached
|
|
599
|
+
write_access_log(rack_env, status, response_size, remote_addr) if @access_log_io && !hijacked
|
|
600
|
+
Http.call_response_finished(rack_env, status, headers, nil)
|
|
601
|
+
end
|
|
602
|
+
keep_alive && !hijacked && !detached
|
|
553
603
|
rescue => error
|
|
554
604
|
keep_alive = false
|
|
555
605
|
handle_app_error(socket, rack_env, status, headers, error, response_started: response_started, hijacked: hijacked)
|
|
@@ -557,7 +607,7 @@ module Raptor
|
|
|
557
607
|
rack_input = rack_env && rack_env[Rack::RACK_INPUT]
|
|
558
608
|
rack_input.close! rescue nil if rack_input.respond_to?(:close!)
|
|
559
609
|
|
|
560
|
-
unless hijacked || keep_alive
|
|
610
|
+
unless hijacked || keep_alive || detached
|
|
561
611
|
socket.close rescue nil
|
|
562
612
|
end
|
|
563
613
|
end
|
|
@@ -579,7 +629,7 @@ module Raptor
|
|
|
579
629
|
#
|
|
580
630
|
# @rbs (TCPSocket socket, Hash[String, untyped]? rack_env, Integer? status, Hash[String, String | Array[String]]? headers, Exception error, response_started: bool, hijacked: bool) -> void
|
|
581
631
|
def handle_app_error(socket, rack_env, status, headers, error, response_started:, hijacked:)
|
|
582
|
-
call_response_finished(rack_env, status, headers, error)
|
|
632
|
+
Http.call_response_finished(rack_env, status, headers, error)
|
|
583
633
|
socket.write(INTERNAL_SERVER_ERROR_RESPONSE) rescue nil unless response_started || hijacked
|
|
584
634
|
|
|
585
635
|
if @on_error
|
|
@@ -666,9 +716,11 @@ module Raptor
|
|
|
666
716
|
|
|
667
717
|
keep_alive = process_request(
|
|
668
718
|
socket,
|
|
719
|
+
id,
|
|
669
720
|
env,
|
|
670
721
|
parse_data,
|
|
671
722
|
body,
|
|
723
|
+
reactor,
|
|
672
724
|
request_count,
|
|
673
725
|
remote_addr,
|
|
674
726
|
url_scheme
|
|
@@ -895,6 +947,24 @@ module Raptor
|
|
|
895
947
|
socket_write(socket, response)
|
|
896
948
|
end
|
|
897
949
|
|
|
950
|
+
# Starts a detached response, chunked on HTTP/1.1 and ended by closing
|
|
951
|
+
# the connection on HTTP/1.0.
|
|
952
|
+
#
|
|
953
|
+
# @rbs (TCPSocket socket, Integer status, Hash[String, String | Array[String]] headers, chunked: bool, keep_alive: bool) -> void
|
|
954
|
+
def write_detached_response(socket, status, headers, chunked:, keep_alive:)
|
|
955
|
+
validate_status(status)
|
|
956
|
+
headers = normalize_headers(headers)
|
|
957
|
+
validate_headers(headers, status, false)
|
|
958
|
+
headers.delete(Rack::CONTENT_LENGTH)
|
|
959
|
+
headers[Rack::TRANSFER_ENCODING] = TRANSFER_ENCODING_CHUNKED if chunked
|
|
960
|
+
headers["connection"] = keep_alive ? CONNECTION_KEEPALIVE : CONNECTION_CLOSE
|
|
961
|
+
|
|
962
|
+
response = build_status_line(chunked ? HTTP_11 : HTTP_10, status)
|
|
963
|
+
HttpParser.format_headers(response, headers)
|
|
964
|
+
response << "\r\n"
|
|
965
|
+
socket_write(socket, response)
|
|
966
|
+
end
|
|
967
|
+
|
|
898
968
|
# Writes a complete HTTP response to the socket.
|
|
899
969
|
#
|
|
900
970
|
# @param socket [TCPSocket] the client socket to write to
|
|
@@ -1258,24 +1328,6 @@ module Raptor
|
|
|
1258
1328
|
end
|
|
1259
1329
|
end
|
|
1260
1330
|
|
|
1261
|
-
# Calls every `rack.response_finished` callback in reverse
|
|
1262
|
-
# registration order, rescuing any that raise.
|
|
1263
|
-
#
|
|
1264
|
-
# @param env [Hash, nil] the Rack environment
|
|
1265
|
-
# @param status [Integer, nil] the response status code
|
|
1266
|
-
# @param headers [Hash, nil] the response headers
|
|
1267
|
-
# @param error [Exception, nil] any error raised during processing, or nil on success
|
|
1268
|
-
# @return [void]
|
|
1269
|
-
#
|
|
1270
|
-
# @rbs (Hash[String, untyped] env, Integer? status, Hash[String, String | Array[String]]? headers, Exception? error) -> void
|
|
1271
|
-
def call_response_finished(env, status, headers, error)
|
|
1272
|
-
return unless env && env[Rack::RACK_RESPONSE_FINISHED].is_a?(Array)
|
|
1273
|
-
|
|
1274
|
-
env[Rack::RACK_RESPONSE_FINISHED].reverse_each do |callable|
|
|
1275
|
-
callable.call(env, status, headers, error) rescue nil
|
|
1276
|
-
end
|
|
1277
|
-
end
|
|
1278
|
-
|
|
1279
1331
|
# Instance-level wrapper around {Http.write_access_log} that routes to
|
|
1280
1332
|
# the configured `@access_log_io`.
|
|
1281
1333
|
#
|