puma 5.6.4 → 6.0.2
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 +163 -3
- 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 +18 -10
- 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 +63 -24
- 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 +166 -65
- data/ext/puma_http11/puma_http11.c +17 -9
- data/lib/puma/app/status.rb +6 -3
- data/lib/puma/binder.rb +37 -43
- 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 +31 -30
- data/lib/puma/configuration.rb +74 -58
- data/lib/puma/const.rb +76 -88
- data/lib/puma/control_cli.rb +21 -18
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +97 -49
- 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 +107 -156
- data/lib/puma/log_writer.rb +137 -0
- data/lib/puma/minissl/context_builder.rb +23 -12
- data/lib/puma/minissl.rb +91 -15
- data/lib/puma/null_io.rb +5 -0
- 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 -162
- data/lib/puma/runner.rb +45 -20
- data/lib/puma/server.rb +55 -69
- data/lib/puma/single.rb +11 -11
- data/lib/puma/state_file.rb +2 -4
- data/lib/puma/systemd.rb +3 -2
- data/lib/puma/thread_pool.rb +16 -16
- data/lib/puma/util.rb +12 -14
- data/lib/puma.rb +12 -11
- 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,119 +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
|
-
|
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
|
182
241
|
end
|
183
242
|
|
184
|
-
|
243
|
+
fast_write_response socket, body, io_buffer, chunked, content_length.to_i
|
244
|
+
body.close if close_body
|
245
|
+
keep_alive
|
185
246
|
end
|
186
247
|
|
187
248
|
# @param env [Hash] see Puma::Client#env, from request
|
@@ -195,45 +256,126 @@ module Puma
|
|
195
256
|
end
|
196
257
|
end
|
197
258
|
|
198
|
-
#
|
199
|
-
#
|
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
|
200
265
|
# @param str [String] the string written to the io
|
201
266
|
# @raise [ConnectionError]
|
202
267
|
#
|
203
|
-
def
|
268
|
+
def fast_write_str(socket, str)
|
204
269
|
n = 0
|
205
|
-
|
270
|
+
byte_size = str.bytesize
|
271
|
+
while n < byte_size
|
206
272
|
begin
|
207
|
-
n
|
273
|
+
n += socket.write_nonblock(n.zero? ? str : str.byteslice(n..-1))
|
208
274
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
209
|
-
unless
|
210
|
-
raise ConnectionError,
|
275
|
+
unless socket.wait_writable WRITE_TIMEOUT
|
276
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
211
277
|
end
|
212
|
-
|
213
278
|
retry
|
214
279
|
rescue Errno::EPIPE, SystemCallError, IOError
|
215
|
-
raise ConnectionError,
|
280
|
+
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
216
281
|
end
|
217
|
-
|
218
|
-
return if n == str.bytesize
|
219
|
-
str = str.byteslice(n..-1)
|
220
282
|
end
|
221
283
|
end
|
222
|
-
private :fast_write
|
223
284
|
|
224
|
-
#
|
225
|
-
#
|
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]
|
226
294
|
#
|
227
|
-
def
|
228
|
-
|
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
|
229
371
|
end
|
230
|
-
|
372
|
+
|
373
|
+
private :fast_write_str, :fast_write_response
|
231
374
|
|
232
375
|
# Given a Hash +env+ for the request read from +client+, add
|
233
376
|
# and fixup keys to comply with Rack's env guidelines.
|
234
377
|
# @param env [Hash] see Puma::Client#env, from request
|
235
378
|
# @param client [Puma::Client] only needed for Client#peerip
|
236
|
-
# @todo make private in 6.0.0
|
237
379
|
#
|
238
380
|
def normalize_env(env, client)
|
239
381
|
if host = env[HTTP_HOST]
|
@@ -258,14 +400,12 @@ module Puma
|
|
258
400
|
uri = URI.parse(env[REQUEST_URI])
|
259
401
|
env[REQUEST_PATH] = uri.path
|
260
402
|
|
261
|
-
raise "No REQUEST PATH" unless env[REQUEST_PATH]
|
262
|
-
|
263
403
|
# A nil env value will cause a LintError (and fatal errors elsewhere),
|
264
404
|
# so only set the env value if there actually is a value.
|
265
405
|
env[QUERY_STRING] = uri.query if uri.query
|
266
406
|
end
|
267
407
|
|
268
|
-
env[PATH_INFO] = env[REQUEST_PATH]
|
408
|
+
env[PATH_INFO] = env[REQUEST_PATH].to_s # #to_s in case it's nil
|
269
409
|
|
270
410
|
# From https://www.ietf.org/rfc/rfc3875 :
|
271
411
|
# "Script authors should be aware that the REMOTE_ADDR and
|
@@ -281,17 +421,31 @@ module Puma
|
|
281
421
|
addr = client.peerip
|
282
422
|
rescue Errno::ENOTCONN
|
283
423
|
# Client disconnects can result in an inability to get the
|
284
|
-
# peeraddr from the socket; default to
|
285
|
-
|
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
|
286
430
|
end
|
287
431
|
|
288
432
|
# Set unix socket addrs to localhost
|
289
|
-
|
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
|
290
440
|
|
291
441
|
env[REMOTE_ADDR] = addr
|
292
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]
|
293
447
|
end
|
294
|
-
|
448
|
+
private :normalize_env
|
295
449
|
|
296
450
|
# @param header_key [#to_s]
|
297
451
|
# @return [Boolean]
|
@@ -350,7 +504,7 @@ module Puma
|
|
350
504
|
# @version 5.0.3
|
351
505
|
#
|
352
506
|
def str_early_hints(headers)
|
353
|
-
eh_str = "HTTP/1.1 103 Early Hints\r\n"
|
507
|
+
eh_str = +"HTTP/1.1 103 Early Hints\r\n"
|
354
508
|
headers.each_pair do |k, vs|
|
355
509
|
next if illegal_header_key?(k)
|
356
510
|
|
@@ -367,66 +521,74 @@ module Puma
|
|
367
521
|
end
|
368
522
|
private :str_early_hints
|
369
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
|
+
|
370
532
|
# Processes and write headers to the IOBuffer.
|
371
533
|
# @param env [Hash] see Puma::Client#env, from request
|
372
534
|
# @param status [Integer] the status returned by the Rack application
|
373
535
|
# @param headers [Hash] the headers returned by the Rack application
|
374
|
-
# @param
|
375
|
-
#
|
376
|
-
# @param
|
377
|
-
# @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
|
378
542
|
# @version 5.0.3
|
379
543
|
#
|
380
|
-
def str_headers(env, status, headers,
|
544
|
+
def str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
|
545
|
+
|
381
546
|
line_ending = LINE_END
|
382
547
|
colon = COLON
|
383
548
|
|
384
|
-
|
549
|
+
resp_info = {}
|
550
|
+
resp_info[:no_body] = env[REQUEST_METHOD] == HEAD
|
551
|
+
|
552
|
+
http_11 = env[SERVER_PROTOCOL] == HTTP_11
|
385
553
|
if http_11
|
386
|
-
|
387
|
-
|
554
|
+
resp_info[:allow_chunked] = true
|
555
|
+
resp_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
|
388
556
|
|
389
557
|
# An optimization. The most common response is 200, so we can
|
390
558
|
# reply with the proper 200 status without having to compute
|
391
559
|
# the response header.
|
392
560
|
#
|
393
561
|
if status == 200
|
394
|
-
|
562
|
+
io_buffer << HTTP_11_200
|
395
563
|
else
|
396
|
-
|
397
|
-
fetch_status_code(status), line_ending
|
564
|
+
io_buffer.append "#{HTTP_11} #{status} ", fetch_status_code(status), line_ending
|
398
565
|
|
399
|
-
|
566
|
+
resp_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
400
567
|
end
|
401
568
|
else
|
402
|
-
|
403
|
-
|
569
|
+
resp_info[:allow_chunked] = false
|
570
|
+
resp_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
|
404
571
|
|
405
572
|
# Same optimization as above for HTTP/1.1
|
406
573
|
#
|
407
574
|
if status == 200
|
408
|
-
|
575
|
+
io_buffer << HTTP_10_200
|
409
576
|
else
|
410
|
-
|
577
|
+
io_buffer.append "HTTP/1.0 #{status} ",
|
411
578
|
fetch_status_code(status), line_ending
|
412
579
|
|
413
|
-
|
580
|
+
resp_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
414
581
|
end
|
415
582
|
end
|
416
583
|
|
417
584
|
# regardless of what the client wants, we always close the connection
|
418
585
|
# if running without request queueing
|
419
|
-
|
586
|
+
resp_info[:keep_alive] &&= @queue_requests
|
420
587
|
|
421
|
-
#
|
422
|
-
|
423
|
-
# This allows Puma to service connections fairly when the number
|
424
|
-
# of concurrent connections exceeds the size of the threadpool.
|
425
|
-
res_info[:keep_alive] &&= requests < @max_fast_inline ||
|
426
|
-
@thread_pool.busy_threads < @max_threads ||
|
427
|
-
!client.listener.to_io.wait_readable(0)
|
588
|
+
# see prepare_response
|
589
|
+
resp_info[:keep_alive] &&= force_keep_alive
|
428
590
|
|
429
|
-
|
591
|
+
resp_info[:response_hijack] = nil
|
430
592
|
|
431
593
|
headers.each do |k, vs|
|
432
594
|
next if illegal_header_key?(k)
|
@@ -434,25 +596,34 @@ module Puma
|
|
434
596
|
case k.downcase
|
435
597
|
when CONTENT_LENGTH2
|
436
598
|
next if illegal_header_value?(vs)
|
437
|
-
|
599
|
+
# nil.to_i is 0, nil&.to_i is nil
|
600
|
+
resp_info[:content_length] = vs&.to_i
|
438
601
|
next
|
439
602
|
when TRANSFER_ENCODING
|
440
|
-
|
441
|
-
|
603
|
+
resp_info[:allow_chunked] = false
|
604
|
+
resp_info[:content_length] = nil
|
605
|
+
resp_info[:transfer_encoding] = vs
|
442
606
|
when HIJACK
|
443
|
-
|
607
|
+
resp_info[:response_hijack] = vs
|
444
608
|
next
|
445
609
|
when BANNED_HEADER_KEY
|
446
610
|
next
|
447
611
|
end
|
448
612
|
|
449
|
-
if vs.
|
450
|
-
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|
|
451
622
|
next if illegal_header_value?(v)
|
452
|
-
|
623
|
+
io_buffer.append k, colon, v, line_ending
|
453
624
|
end
|
454
625
|
else
|
455
|
-
|
626
|
+
io_buffer.append k, colon, line_ending
|
456
627
|
end
|
457
628
|
end
|
458
629
|
|
@@ -462,10 +633,11 @@ module Puma
|
|
462
633
|
# Only set the header if we're doing something which is not the default
|
463
634
|
# for this protocol version
|
464
635
|
if http_11
|
465
|
-
|
636
|
+
io_buffer << CONNECTION_CLOSE if !resp_info[:keep_alive]
|
466
637
|
else
|
467
|
-
|
638
|
+
io_buffer << CONNECTION_KEEP_ALIVE if resp_info[:keep_alive]
|
468
639
|
end
|
640
|
+
resp_info
|
469
641
|
end
|
470
642
|
private :str_headers
|
471
643
|
end
|