puma 5.6.5 → 6.0.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +120 -11
- data/README.md +21 -17
- data/bin/puma-wild +1 -1
- data/docs/compile_options.md +34 -0
- data/docs/fork_worker.md +1 -3
- data/docs/nginx.md +1 -1
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/extconf.rb +11 -8
- data/ext/puma_http11/http11_parser.c +1 -1
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +2 -2
- data/ext/puma_http11/http11_parser.rl +2 -2
- data/ext/puma_http11/http11_parser_common.rl +2 -2
- data/ext/puma_http11/mini_ssl.c +36 -15
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +156 -53
- data/ext/puma_http11/puma_http11.c +17 -9
- data/lib/puma/app/status.rb +3 -3
- data/lib/puma/binder.rb +36 -42
- data/lib/puma/cli.rb +11 -17
- data/lib/puma/client.rb +26 -13
- data/lib/puma/cluster/worker.rb +13 -11
- data/lib/puma/cluster/worker_handle.rb +4 -1
- data/lib/puma/cluster.rb +28 -25
- data/lib/puma/configuration.rb +74 -58
- data/lib/puma/const.rb +14 -18
- data/lib/puma/control_cli.rb +3 -6
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +96 -52
- data/lib/puma/error_logger.rb +17 -9
- data/lib/puma/events.rb +6 -126
- data/lib/puma/io_buffer.rb +39 -4
- data/lib/puma/jruby_restart.rb +2 -1
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +96 -156
- data/lib/puma/log_writer.rb +137 -0
- data/lib/puma/minissl/context_builder.rb +23 -12
- data/lib/puma/minissl.rb +82 -11
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/rack/builder.rb +4 -4
- data/lib/puma/rack_default.rb +1 -1
- data/lib/puma/reactor.rb +4 -4
- data/lib/puma/request.rb +334 -166
- data/lib/puma/runner.rb +41 -20
- data/lib/puma/server.rb +55 -71
- data/lib/puma/single.rb +10 -10
- data/lib/puma/state_file.rb +1 -4
- data/lib/puma/systemd.rb +3 -2
- data/lib/puma/thread_pool.rb +16 -16
- data/lib/puma/util.rb +0 -11
- data/lib/puma.rb +12 -9
- data/lib/rack/handler/puma.rb +9 -9
- metadata +7 -3
- data/lib/puma/queue_close.rb +0 -26
data/lib/puma/request.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Puma
|
4
|
+
#———————————————————————— DO NOT USE — this class is for internal use only ———
|
5
|
+
|
4
6
|
|
5
7
|
# The methods here are included in Server, but are separated into this file.
|
6
8
|
# All the methods here pertain to passing the request to the app, then
|
@@ -10,7 +12,24 @@ module Puma
|
|
10
12
|
# #handle_request, which is called in Server#process_client.
|
11
13
|
# @version 5.0.3
|
12
14
|
#
|
13
|
-
module Request
|
15
|
+
module Request # :nodoc:
|
16
|
+
|
17
|
+
# Single element array body: smaller bodies are written to io_buffer first,
|
18
|
+
# then a single write from io_buffer. Larger sizes are written separately.
|
19
|
+
# Also fixes max size of chunked file body read.
|
20
|
+
BODY_LEN_MAX = 1_024 * 256
|
21
|
+
|
22
|
+
# File body: smaller bodies are combined with io_buffer, then written to
|
23
|
+
# socket. Larger bodies are written separately using `copy_stream`
|
24
|
+
IO_BODY_MAX = 1_024 * 64
|
25
|
+
|
26
|
+
# Array body: elements are collected in io_buffer. When io_buffer's size
|
27
|
+
# exceeds value, they are written to the socket.
|
28
|
+
IO_BUFFER_LEN_MAX = 1_024 * 512
|
29
|
+
|
30
|
+
SOCKET_WRITE_ERR_MSG = "Socket timeout writing data"
|
31
|
+
|
32
|
+
CUSTOM_STAT = 'CUSTOM'
|
14
33
|
|
15
34
|
include Puma::Const
|
16
35
|
|
@@ -25,40 +44,37 @@ module Puma
|
|
25
44
|
#
|
26
45
|
# Finally, it'll return +true+ on keep-alive connections.
|
27
46
|
# @param client [Puma::Client]
|
28
|
-
# @param lines [Puma::IOBuffer]
|
29
47
|
# @param requests [Integer]
|
30
48
|
# @return [Boolean,:async]
|
31
49
|
#
|
32
|
-
def handle_request(client,
|
50
|
+
def handle_request(client, requests)
|
33
51
|
env = client.env
|
34
|
-
|
52
|
+
io_buffer = client.io_buffer
|
53
|
+
socket = client.io # io may be a MiniSSL::Socket
|
54
|
+
app_body = nil
|
35
55
|
|
36
|
-
return false if closed_socket?(
|
56
|
+
return false if closed_socket?(socket)
|
37
57
|
|
38
58
|
normalize_env env, client
|
39
59
|
|
40
|
-
env[PUMA_SOCKET] =
|
60
|
+
env[PUMA_SOCKET] = socket
|
41
61
|
|
42
|
-
if env[HTTPS_KEY] &&
|
43
|
-
env[PUMA_PEERCERT] =
|
62
|
+
if env[HTTPS_KEY] && socket.peercert
|
63
|
+
env[PUMA_PEERCERT] = socket.peercert
|
44
64
|
end
|
45
65
|
|
46
66
|
env[HIJACK_P] = true
|
47
67
|
env[HIJACK] = client
|
48
68
|
|
49
|
-
|
50
|
-
|
51
|
-
head = env[REQUEST_METHOD] == HEAD
|
52
|
-
|
53
|
-
env[RACK_INPUT] = body
|
69
|
+
env[RACK_INPUT] = client.body
|
54
70
|
env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP
|
55
71
|
|
56
72
|
if @early_hints
|
57
73
|
env[EARLY_HINTS] = lambda { |headers|
|
58
74
|
begin
|
59
|
-
|
75
|
+
fast_write_str socket, str_early_hints(headers)
|
60
76
|
rescue ConnectionError => e
|
61
|
-
@
|
77
|
+
@log_writer.debug_error e
|
62
78
|
# noop, if we lost the socket we just won't send the early hints
|
63
79
|
end
|
64
80
|
}
|
@@ -69,123 +85,164 @@ module Puma
|
|
69
85
|
# A rack extension. If the app writes #call'ables to this
|
70
86
|
# array, we will invoke them when the request is done.
|
71
87
|
#
|
72
|
-
|
88
|
+
env[RACK_AFTER_REPLY] ||= []
|
73
89
|
|
74
90
|
begin
|
75
|
-
|
76
|
-
status, headers,
|
91
|
+
if SUPPORTED_HTTP_METHODS.include?(env[REQUEST_METHOD])
|
92
|
+
status, headers, app_body = @thread_pool.with_force_shutdown do
|
77
93
|
@app.call(env)
|
78
94
|
end
|
95
|
+
else
|
96
|
+
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
|
97
|
+
status, headers, app_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
|
98
|
+
end
|
79
99
|
|
80
|
-
|
100
|
+
# app_body needs to always be closed, hold value in case lowlevel_error
|
101
|
+
# is called
|
102
|
+
res_body = app_body
|
81
103
|
|
82
|
-
|
104
|
+
return :async if client.hijacked
|
83
105
|
|
84
|
-
|
85
|
-
unless headers.empty? and res_body == []
|
86
|
-
raise "async response must have empty headers and body"
|
87
|
-
end
|
106
|
+
status = status.to_i
|
88
107
|
|
89
|
-
|
108
|
+
if status == -1
|
109
|
+
unless headers.empty? and res_body == []
|
110
|
+
raise "async response must have empty headers and body"
|
90
111
|
end
|
91
|
-
rescue ThreadPool::ForceShutdown => e
|
92
|
-
@events.unknown_error e, client, "Rack app"
|
93
|
-
@events.log "Detected force shutdown of a thread"
|
94
|
-
|
95
|
-
status, headers, res_body = lowlevel_error(e, env, 503)
|
96
|
-
rescue Exception => e
|
97
|
-
@events.unknown_error e, client, "Rack app"
|
98
|
-
|
99
|
-
status, headers, res_body = lowlevel_error(e, env, 500)
|
100
|
-
end
|
101
112
|
|
102
|
-
|
103
|
-
res_info[:content_length] = nil
|
104
|
-
res_info[:no_body] = head
|
105
|
-
|
106
|
-
res_info[:content_length] = if res_body.kind_of? Array and res_body.size == 1
|
107
|
-
res_body[0].bytesize
|
108
|
-
else
|
109
|
-
nil
|
113
|
+
return :async
|
110
114
|
end
|
115
|
+
rescue ThreadPool::ForceShutdown => e
|
116
|
+
@log_writer.unknown_error e, client, "Rack app"
|
117
|
+
@log_writer.log "Detected force shutdown of a thread"
|
111
118
|
|
112
|
-
|
119
|
+
status, headers, res_body = lowlevel_error(e, env, 503)
|
120
|
+
rescue Exception => e
|
121
|
+
@log_writer.unknown_error e, client, "Rack app"
|
113
122
|
|
114
|
-
|
123
|
+
status, headers, res_body = lowlevel_error(e, env, 500)
|
124
|
+
end
|
125
|
+
prepare_response(status, headers, res_body, requests, client)
|
126
|
+
ensure
|
127
|
+
io_buffer.reset
|
128
|
+
uncork_socket client.io
|
129
|
+
app_body.close if app_body.respond_to? :close
|
130
|
+
client.tempfile&.unlink
|
131
|
+
after_reply = env[RACK_AFTER_REPLY] || []
|
132
|
+
begin
|
133
|
+
after_reply.each { |o| o.call }
|
134
|
+
rescue StandardError => e
|
135
|
+
@log_writer.debug_error e
|
136
|
+
end unless after_reply.empty?
|
137
|
+
end
|
115
138
|
|
116
|
-
|
139
|
+
# Assembles the headers and prepares the body for actually sending the
|
140
|
+
# response via `#fast_write_response`.
|
141
|
+
#
|
142
|
+
# @param status [Integer] the status returned by the Rack application
|
143
|
+
# @param headers [Hash] the headers returned by the Rack application
|
144
|
+
# @param res_body [Array] the body returned by the Rack application or
|
145
|
+
# a call to `Server#lowlevel_error`
|
146
|
+
# @param requests [Integer] number of inline requests handled
|
147
|
+
# @param client [Puma::Client]
|
148
|
+
# @return [Boolean,:async] keep-alive status or `:async`
|
149
|
+
def prepare_response(status, headers, res_body, requests, client)
|
150
|
+
env = client.env
|
151
|
+
socket = client.io
|
152
|
+
io_buffer = client.io_buffer
|
117
153
|
|
118
|
-
|
119
|
-
response_hijack = res_info[:response_hijack]
|
154
|
+
return false if closed_socket?(socket)
|
120
155
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
156
|
+
# Close the connection after a reasonable number of inline requests
|
157
|
+
# if the server is at capacity and the listener has a new connection ready.
|
158
|
+
# This allows Puma to service connections fairly when the number
|
159
|
+
# of concurrent connections exceeds the size of the threadpool.
|
160
|
+
force_keep_alive = requests < @max_fast_inline ||
|
161
|
+
@thread_pool.busy_threads < @max_threads ||
|
162
|
+
!client.listener.to_io.wait_readable(0)
|
125
163
|
|
126
|
-
|
127
|
-
|
128
|
-
|
164
|
+
resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
|
165
|
+
|
166
|
+
close_body = false
|
167
|
+
|
168
|
+
# below converts app_body into body, dependent on app_body's characteristics, and
|
169
|
+
# resp_info[:content_length] will be set if it can be determined
|
170
|
+
if !resp_info[:content_length] && !resp_info[:transfer_encoding] && status != 204
|
171
|
+
if res_body.respond_to?(:to_ary) && (array_body = res_body.to_ary)
|
172
|
+
body = array_body
|
173
|
+
resp_info[:content_length] = body.sum(&:bytesize)
|
174
|
+
elsif res_body.is_a?(File) && res_body.respond_to?(:size)
|
175
|
+
body = res_body
|
176
|
+
resp_info[:content_length] = body.size
|
177
|
+
elsif res_body.respond_to?(:to_path) && res_body.respond_to?(:each) &&
|
178
|
+
File.readable?(fn = res_body.to_path)
|
179
|
+
body = File.open fn, 'rb'
|
180
|
+
resp_info[:content_length] = body.size
|
181
|
+
close_body = true
|
182
|
+
else
|
183
|
+
body = res_body
|
129
184
|
end
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
185
|
+
elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) && res_body.respond_to?(:each) &&
|
186
|
+
File.readable?(fn = res_body.to_path)
|
187
|
+
body = File.open fn, 'rb'
|
188
|
+
resp_info[:content_length] = body.size
|
189
|
+
close_body = true
|
190
|
+
elsif !res_body.is_a?(::File) && res_body.respond_to?(:filename) && res_body.respond_to?(:each) &&
|
191
|
+
res_body.respond_to?(:bytesize) && File.readable?(fn = res_body.filename)
|
192
|
+
# Sprockets::Asset
|
193
|
+
resp_info[:content_length] = res_body.bytesize unless resp_info[:content_length]
|
194
|
+
if res_body.to_hash[:source] # use each to return @source
|
195
|
+
body = res_body
|
196
|
+
else # avoid each and use a File object
|
197
|
+
body = File.open fn, 'rb'
|
198
|
+
close_body = true
|
137
199
|
end
|
200
|
+
else
|
201
|
+
body = res_body
|
202
|
+
end
|
138
203
|
|
139
|
-
|
204
|
+
line_ending = LINE_END
|
140
205
|
|
141
|
-
|
206
|
+
content_length = resp_info[:content_length]
|
207
|
+
keep_alive = resp_info[:keep_alive]
|
142
208
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
209
|
+
if res_body && !res_body.respond_to?(:each)
|
210
|
+
response_hijack = res_body
|
211
|
+
else
|
212
|
+
response_hijack = resp_info[:response_hijack]
|
213
|
+
end
|
147
214
|
|
148
|
-
|
149
|
-
res_body.each do |part|
|
150
|
-
next if part.bytesize.zero?
|
151
|
-
if chunked
|
152
|
-
fast_write io, (part.bytesize.to_s(16) << line_ending)
|
153
|
-
fast_write io, part # part may have different encoding
|
154
|
-
fast_write io, line_ending
|
155
|
-
else
|
156
|
-
fast_write io, part
|
157
|
-
end
|
158
|
-
io.flush
|
159
|
-
end
|
215
|
+
cork_socket socket
|
160
216
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
end
|
165
|
-
rescue SystemCallError, IOError
|
166
|
-
raise ConnectionError, "Connection error detected during write"
|
217
|
+
if resp_info[:no_body]
|
218
|
+
if content_length and status != 204
|
219
|
+
io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
167
220
|
end
|
168
221
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
222
|
+
io_buffer << LINE_END
|
223
|
+
fast_write_str socket, io_buffer.read_and_reset
|
224
|
+
socket.flush
|
225
|
+
return keep_alive
|
226
|
+
end
|
227
|
+
if content_length
|
228
|
+
io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
229
|
+
chunked = false
|
230
|
+
elsif !response_hijack and resp_info[:allow_chunked]
|
231
|
+
io_buffer << TRANSFER_ENCODING_CHUNKED
|
232
|
+
chunked = true
|
233
|
+
end
|
180
234
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
235
|
+
io_buffer << line_ending
|
236
|
+
|
237
|
+
if response_hijack
|
238
|
+
fast_write_str socket, io_buffer.read_and_reset
|
239
|
+
response_hijack.call socket
|
240
|
+
return :async
|
186
241
|
end
|
187
242
|
|
188
|
-
|
243
|
+
fast_write_response socket, body, io_buffer, chunked, content_length.to_i
|
244
|
+
body.close if close_body
|
245
|
+
keep_alive
|
189
246
|
end
|
190
247
|
|
191
248
|
# @param env [Hash] see Puma::Client#env, from request
|
@@ -199,45 +256,126 @@ module Puma
|
|
199
256
|
end
|
200
257
|
end
|
201
258
|
|
202
|
-
#
|
203
|
-
#
|
259
|
+
# Used to write 'early hints', 'no body' responses, 'hijacked' responses,
|
260
|
+
# and body segments (called by `fast_write_response`).
|
261
|
+
# Writes a string to a socket (normally `Client#io`) using `write_nonblock`.
|
262
|
+
# Large strings may not be written in one pass, especially if `io` is a
|
263
|
+
# `MiniSSL::Socket`.
|
264
|
+
# @param socket [#write_nonblock] the request/response socket
|
204
265
|
# @param str [String] the string written to the io
|
205
266
|
# @raise [ConnectionError]
|
206
267
|
#
|
207
|
-
def
|
268
|
+
def fast_write_str(socket, str)
|
208
269
|
n = 0
|
209
|
-
|
270
|
+
byte_size = str.bytesize
|
271
|
+
while n < byte_size
|
210
272
|
begin
|
211
|
-
n
|
273
|
+
n += socket.write_nonblock(n.zero? ? str : str.byteslice(n..-1))
|
212
274
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
213
|
-
unless
|
214
|
-
raise ConnectionError,
|
275
|
+
unless socket.wait_writable WRITE_TIMEOUT
|
276
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
215
277
|
end
|
216
|
-
|
217
278
|
retry
|
218
279
|
rescue Errno::EPIPE, SystemCallError, IOError
|
219
|
-
raise ConnectionError,
|
280
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
220
281
|
end
|
221
|
-
|
222
|
-
return if n == str.bytesize
|
223
|
-
str = str.byteslice(n..-1)
|
224
282
|
end
|
225
283
|
end
|
226
|
-
private :fast_write
|
227
284
|
|
228
|
-
#
|
229
|
-
#
|
285
|
+
# Used to write headers and body.
|
286
|
+
# Writes to a socket (normally `Client#io`) using `#fast_write_str`.
|
287
|
+
# Accumulates `body` items into `io_buffer`, then writes to socket.
|
288
|
+
# @param socket [#write] the response socket
|
289
|
+
# @param body [Enumerable, File] the body object
|
290
|
+
# @param io_buffer [Puma::IOBuffer] contains headers
|
291
|
+
# @param chunked [Boolean]
|
292
|
+
# @paramn content_length [Integer
|
293
|
+
# @raise [ConnectionError]
|
230
294
|
#
|
231
|
-
def
|
232
|
-
|
295
|
+
def fast_write_response(socket, body, io_buffer, chunked, content_length)
|
296
|
+
if body.is_a?(::File) && body.respond_to?(:read)
|
297
|
+
if chunked # would this ever happen?
|
298
|
+
while part = body.read(BODY_LEN_MAX)
|
299
|
+
io_buffer.append part.bytesize.to_s(16), LINE_END, part, LINE_END
|
300
|
+
end
|
301
|
+
fast_write_str socket, CLOSE_CHUNKED
|
302
|
+
else
|
303
|
+
if content_length <= IO_BODY_MAX
|
304
|
+
io_buffer.write body.read(content_length)
|
305
|
+
fast_write_str socket, io_buffer.read_and_reset
|
306
|
+
else
|
307
|
+
fast_write_str socket, io_buffer.read_and_reset
|
308
|
+
IO.copy_stream body, socket
|
309
|
+
end
|
310
|
+
end
|
311
|
+
elsif body.is_a?(::Array) && body.length == 1
|
312
|
+
body_first = nil
|
313
|
+
# using body_first = body.first causes issues?
|
314
|
+
body.each { |str| body_first ||= str }
|
315
|
+
|
316
|
+
if body_first.is_a?(::String) && body_first.bytesize < BODY_LEN_MAX
|
317
|
+
# smaller body, write to io_buffer first
|
318
|
+
io_buffer.write body_first
|
319
|
+
fast_write_str socket, io_buffer.read_and_reset
|
320
|
+
else
|
321
|
+
# large body, write both header & body to socket
|
322
|
+
fast_write_str socket, io_buffer.read_and_reset
|
323
|
+
fast_write_str socket, body_first
|
324
|
+
end
|
325
|
+
elsif body.is_a?(::Array)
|
326
|
+
# for array bodies, flush io_buffer to socket when size is greater than
|
327
|
+
# IO_BUFFER_LEN_MAX
|
328
|
+
if chunked
|
329
|
+
body.each do |part|
|
330
|
+
next if (byte_size = part.bytesize).zero?
|
331
|
+
io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
|
332
|
+
if io_buffer.length > IO_BUFFER_LEN_MAX
|
333
|
+
fast_write_str socket, io_buffer.read_and_reset
|
334
|
+
end
|
335
|
+
end
|
336
|
+
io_buffer.write CLOSE_CHUNKED
|
337
|
+
else
|
338
|
+
body.each do |part|
|
339
|
+
next if part.bytesize.zero?
|
340
|
+
io_buffer.write part
|
341
|
+
if io_buffer.length > IO_BUFFER_LEN_MAX
|
342
|
+
fast_write_str socket, io_buffer.read_and_reset
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
# may write last body part for non-chunked, also headers if array is empty
|
347
|
+
fast_write_str(socket, io_buffer.read_and_reset) unless io_buffer.length.zero?
|
348
|
+
else
|
349
|
+
# for enum bodies
|
350
|
+
fast_write_str socket, io_buffer.read_and_reset
|
351
|
+
if chunked
|
352
|
+
body.each do |part|
|
353
|
+
next if (byte_size = part.bytesize).zero?
|
354
|
+
fast_write_str socket, (byte_size.to_s(16) << LINE_END)
|
355
|
+
fast_write_str socket, part
|
356
|
+
fast_write_str socket, LINE_END
|
357
|
+
end
|
358
|
+
fast_write_str socket, CLOSE_CHUNKED
|
359
|
+
else
|
360
|
+
body.each do |part|
|
361
|
+
next if part.bytesize.zero?
|
362
|
+
fast_write_str socket, part
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
socket.flush
|
367
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
368
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
369
|
+
rescue Errno::EPIPE, SystemCallError, IOError
|
370
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
233
371
|
end
|
234
|
-
|
372
|
+
|
373
|
+
private :fast_write_str, :fast_write_response
|
235
374
|
|
236
375
|
# Given a Hash +env+ for the request read from +client+, add
|
237
376
|
# and fixup keys to comply with Rack's env guidelines.
|
238
377
|
# @param env [Hash] see Puma::Client#env, from request
|
239
378
|
# @param client [Puma::Client] only needed for Client#peerip
|
240
|
-
# @todo make private in 6.0.0
|
241
379
|
#
|
242
380
|
def normalize_env(env, client)
|
243
381
|
if host = env[HTTP_HOST]
|
@@ -262,14 +400,12 @@ module Puma
|
|
262
400
|
uri = URI.parse(env[REQUEST_URI])
|
263
401
|
env[REQUEST_PATH] = uri.path
|
264
402
|
|
265
|
-
raise "No REQUEST PATH" unless env[REQUEST_PATH]
|
266
|
-
|
267
403
|
# A nil env value will cause a LintError (and fatal errors elsewhere),
|
268
404
|
# so only set the env value if there actually is a value.
|
269
405
|
env[QUERY_STRING] = uri.query if uri.query
|
270
406
|
end
|
271
407
|
|
272
|
-
env[PATH_INFO] = env[REQUEST_PATH]
|
408
|
+
env[PATH_INFO] = env[REQUEST_PATH].to_s # #to_s in case it's nil
|
273
409
|
|
274
410
|
# From https://www.ietf.org/rfc/rfc3875 :
|
275
411
|
# "Script authors should be aware that the REMOTE_ADDR and
|
@@ -285,17 +421,31 @@ module Puma
|
|
285
421
|
addr = client.peerip
|
286
422
|
rescue Errno::ENOTCONN
|
287
423
|
# Client disconnects can result in an inability to get the
|
288
|
-
# peeraddr from the socket; default to
|
289
|
-
|
424
|
+
# peeraddr from the socket; default to unspec.
|
425
|
+
if client.peer_family == Socket::AF_INET6
|
426
|
+
addr = UNSPECIFIED_IPV6
|
427
|
+
else
|
428
|
+
addr = UNSPECIFIED_IPV4
|
429
|
+
end
|
290
430
|
end
|
291
431
|
|
292
432
|
# Set unix socket addrs to localhost
|
293
|
-
|
433
|
+
if addr.empty?
|
434
|
+
if client.peer_family == Socket::AF_INET6
|
435
|
+
addr = LOCALHOST_IPV6
|
436
|
+
else
|
437
|
+
addr = LOCALHOST_IPV4
|
438
|
+
end
|
439
|
+
end
|
294
440
|
|
295
441
|
env[REMOTE_ADDR] = addr
|
296
442
|
end
|
443
|
+
|
444
|
+
# The legacy HTTP_VERSION header can be sent as a client header.
|
445
|
+
# Rack v4 may remove using HTTP_VERSION. If so, remove this line.
|
446
|
+
env[HTTP_VERSION] = env[SERVER_PROTOCOL]
|
297
447
|
end
|
298
|
-
|
448
|
+
private :normalize_env
|
299
449
|
|
300
450
|
# @param header_key [#to_s]
|
301
451
|
# @return [Boolean]
|
@@ -354,7 +504,7 @@ module Puma
|
|
354
504
|
# @version 5.0.3
|
355
505
|
#
|
356
506
|
def str_early_hints(headers)
|
357
|
-
eh_str = "HTTP/1.1 103 Early Hints\r\n"
|
507
|
+
eh_str = +"HTTP/1.1 103 Early Hints\r\n"
|
358
508
|
headers.each_pair do |k, vs|
|
359
509
|
next if illegal_header_key?(k)
|
360
510
|
|
@@ -371,66 +521,74 @@ module Puma
|
|
371
521
|
end
|
372
522
|
private :str_early_hints
|
373
523
|
|
524
|
+
# @param status [Integer] status from the app
|
525
|
+
# @return [String] the text description from Puma::HTTP_STATUS_CODES
|
526
|
+
#
|
527
|
+
def fetch_status_code(status)
|
528
|
+
HTTP_STATUS_CODES.fetch(status) { CUSTOM_STAT }
|
529
|
+
end
|
530
|
+
private :fetch_status_code
|
531
|
+
|
374
532
|
# Processes and write headers to the IOBuffer.
|
375
533
|
# @param env [Hash] see Puma::Client#env, from request
|
376
534
|
# @param status [Integer] the status returned by the Rack application
|
377
535
|
# @param headers [Hash] the headers returned by the Rack application
|
378
|
-
# @param
|
379
|
-
#
|
380
|
-
# @param
|
381
|
-
# @param
|
536
|
+
# @param content_length [Integer,nil] content length if it can be determined from the
|
537
|
+
# response body
|
538
|
+
# @param io_buffer [Puma::IOBuffer] modified inn place
|
539
|
+
# @param force_keep_alive [Boolean] 'anded' with keep_alive, based on system
|
540
|
+
# status and `@max_fast_inline`
|
541
|
+
# @return [Hash] resp_info
|
382
542
|
# @version 5.0.3
|
383
543
|
#
|
384
|
-
def str_headers(env, status, headers,
|
544
|
+
def str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
|
545
|
+
|
385
546
|
line_ending = LINE_END
|
386
547
|
colon = COLON
|
387
548
|
|
388
|
-
|
549
|
+
resp_info = {}
|
550
|
+
resp_info[:no_body] = env[REQUEST_METHOD] == HEAD
|
551
|
+
|
552
|
+
http_11 = env[SERVER_PROTOCOL] == HTTP_11
|
389
553
|
if http_11
|
390
|
-
|
391
|
-
|
554
|
+
resp_info[:allow_chunked] = true
|
555
|
+
resp_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
|
392
556
|
|
393
557
|
# An optimization. The most common response is 200, so we can
|
394
558
|
# reply with the proper 200 status without having to compute
|
395
559
|
# the response header.
|
396
560
|
#
|
397
561
|
if status == 200
|
398
|
-
|
562
|
+
io_buffer << HTTP_11_200
|
399
563
|
else
|
400
|
-
|
401
|
-
fetch_status_code(status), line_ending
|
564
|
+
io_buffer.append "#{HTTP_11} #{status} ", fetch_status_code(status), line_ending
|
402
565
|
|
403
|
-
|
566
|
+
resp_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
404
567
|
end
|
405
568
|
else
|
406
|
-
|
407
|
-
|
569
|
+
resp_info[:allow_chunked] = false
|
570
|
+
resp_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
|
408
571
|
|
409
572
|
# Same optimization as above for HTTP/1.1
|
410
573
|
#
|
411
574
|
if status == 200
|
412
|
-
|
575
|
+
io_buffer << HTTP_10_200
|
413
576
|
else
|
414
|
-
|
577
|
+
io_buffer.append "HTTP/1.0 #{status} ",
|
415
578
|
fetch_status_code(status), line_ending
|
416
579
|
|
417
|
-
|
580
|
+
resp_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
418
581
|
end
|
419
582
|
end
|
420
583
|
|
421
584
|
# regardless of what the client wants, we always close the connection
|
422
585
|
# if running without request queueing
|
423
|
-
|
586
|
+
resp_info[:keep_alive] &&= @queue_requests
|
424
587
|
|
425
|
-
#
|
426
|
-
|
427
|
-
# This allows Puma to service connections fairly when the number
|
428
|
-
# of concurrent connections exceeds the size of the threadpool.
|
429
|
-
res_info[:keep_alive] &&= requests < @max_fast_inline ||
|
430
|
-
@thread_pool.busy_threads < @max_threads ||
|
431
|
-
!client.listener.to_io.wait_readable(0)
|
588
|
+
# see prepare_response
|
589
|
+
resp_info[:keep_alive] &&= force_keep_alive
|
432
590
|
|
433
|
-
|
591
|
+
resp_info[:response_hijack] = nil
|
434
592
|
|
435
593
|
headers.each do |k, vs|
|
436
594
|
next if illegal_header_key?(k)
|
@@ -438,25 +596,34 @@ module Puma
|
|
438
596
|
case k.downcase
|
439
597
|
when CONTENT_LENGTH2
|
440
598
|
next if illegal_header_value?(vs)
|
441
|
-
|
599
|
+
# nil.to_i is 0, nil&.to_i is nil
|
600
|
+
resp_info[:content_length] = vs&.to_i
|
442
601
|
next
|
443
602
|
when TRANSFER_ENCODING
|
444
|
-
|
445
|
-
|
603
|
+
resp_info[:allow_chunked] = false
|
604
|
+
resp_info[:content_length] = nil
|
605
|
+
resp_info[:transfer_encoding] = vs
|
446
606
|
when HIJACK
|
447
|
-
|
607
|
+
resp_info[:response_hijack] = vs
|
448
608
|
next
|
449
609
|
when BANNED_HEADER_KEY
|
450
610
|
next
|
451
611
|
end
|
452
612
|
|
453
|
-
if vs.
|
454
|
-
vs
|
613
|
+
ary = if vs.is_a?(::Array) && !vs.empty?
|
614
|
+
vs
|
615
|
+
elsif vs.respond_to?(:to_s) && !vs.to_s.empty?
|
616
|
+
vs.to_s.split NEWLINE
|
617
|
+
else
|
618
|
+
nil
|
619
|
+
end
|
620
|
+
if ary
|
621
|
+
ary.each do |v|
|
455
622
|
next if illegal_header_value?(v)
|
456
|
-
|
623
|
+
io_buffer.append k, colon, v, line_ending
|
457
624
|
end
|
458
625
|
else
|
459
|
-
|
626
|
+
io_buffer.append k, colon, line_ending
|
460
627
|
end
|
461
628
|
end
|
462
629
|
|
@@ -466,10 +633,11 @@ module Puma
|
|
466
633
|
# Only set the header if we're doing something which is not the default
|
467
634
|
# for this protocol version
|
468
635
|
if http_11
|
469
|
-
|
636
|
+
io_buffer << CONNECTION_CLOSE if !resp_info[:keep_alive]
|
470
637
|
else
|
471
|
-
|
638
|
+
io_buffer << CONNECTION_KEEP_ALIVE if resp_info[:keep_alive]
|
472
639
|
end
|
640
|
+
resp_info
|
473
641
|
end
|
474
642
|
private :str_headers
|
475
643
|
end
|