puma 6.0.0 → 6.4.0
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 +184 -6
- data/LICENSE +0 -0
- data/README.md +58 -13
- data/bin/puma-wild +0 -0
- data/docs/architecture.md +0 -0
- data/docs/compile_options.md +0 -0
- data/docs/deployment.md +0 -0
- data/docs/fork_worker.md +0 -0
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/jungle/README.md +0 -0
- data/docs/jungle/rc.d/README.md +0 -0
- data/docs/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +12 -0
- data/docs/nginx.md +1 -1
- data/docs/plugins.md +0 -0
- data/docs/rails_dev_mode.md +0 -0
- data/docs/restart.md +0 -0
- data/docs/signals.md +0 -0
- data/docs/stats.md +0 -0
- data/docs/systemd.md +1 -2
- data/docs/testing_benchmarks_local_files.md +0 -0
- data/docs/testing_test_rackup_ci_files.md +0 -0
- data/ext/puma_http11/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/ext_help.h +0 -0
- data/ext/puma_http11/extconf.rb +0 -0
- data/ext/puma_http11/http11_parser.c +0 -0
- data/ext/puma_http11/http11_parser.h +0 -0
- data/ext/puma_http11/http11_parser.java.rl +0 -0
- data/ext/puma_http11/http11_parser.rl +0 -0
- data/ext/puma_http11/http11_parser_common.rl +0 -0
- data/ext/puma_http11/mini_ssl.c +91 -8
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +0 -0
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +0 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +0 -0
- data/ext/puma_http11/puma_http11.c +0 -0
- data/lib/puma/app/status.rb +1 -1
- data/lib/puma/binder.rb +14 -11
- data/lib/puma/cli.rb +5 -1
- data/lib/puma/client.rb +53 -16
- data/lib/puma/cluster/worker.rb +5 -0
- data/lib/puma/cluster/worker_handle.rb +0 -0
- data/lib/puma/cluster.rb +5 -5
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +5 -1
- data/lib/puma/const.rb +129 -88
- data/lib/puma/control_cli.rb +12 -5
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +147 -7
- data/lib/puma/error_logger.rb +2 -1
- data/lib/puma/events.rb +0 -0
- data/lib/puma/io_buffer.rb +10 -0
- data/lib/puma/jruby_restart.rb +0 -0
- data/lib/puma/json_serialization.rb +0 -0
- data/lib/puma/launcher/bundle_pruner.rb +0 -0
- data/lib/puma/launcher.rb +9 -22
- data/lib/puma/log_writer.rb +14 -4
- data/lib/puma/minissl/context_builder.rb +1 -0
- data/lib/puma/minissl.rb +17 -0
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +0 -0
- data/lib/puma/plugin.rb +0 -0
- data/lib/puma/rack/builder.rb +2 -2
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/rack_default.rb +18 -3
- data/lib/puma/reactor.rb +17 -8
- data/lib/puma/request.rb +189 -125
- data/lib/puma/runner.rb +16 -1
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +74 -34
- data/lib/puma/single.rb +3 -1
- data/lib/puma/state_file.rb +0 -0
- data/lib/puma/thread_pool.rb +42 -7
- data/lib/puma/util.rb +0 -0
- data/lib/puma.rb +1 -3
- data/lib/rack/handler/puma.rb +113 -86
- data/tools/Dockerfile +0 -0
- data/tools/trickletest.rb +0 -0
- metadata +4 -3
- data/lib/puma/systemd.rb +0 -47
data/lib/puma/request.rb
CHANGED
@@ -14,15 +14,18 @@ module Puma
|
|
14
14
|
#
|
15
15
|
module Request # :nodoc:
|
16
16
|
|
17
|
-
#
|
18
|
-
#
|
19
|
-
|
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
|
20
21
|
|
21
|
-
#
|
22
|
+
# File body: smaller bodies are combined with io_buffer, then written to
|
23
|
+
# socket. Larger bodies are written separately using `copy_stream`
|
22
24
|
IO_BODY_MAX = 1_024 * 64
|
23
25
|
|
24
|
-
#
|
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
|
26
29
|
|
27
30
|
SOCKET_WRITE_ERR_MSG = "Socket timeout writing data"
|
28
31
|
|
@@ -41,16 +44,22 @@ module Puma
|
|
41
44
|
#
|
42
45
|
# Finally, it'll return +true+ on keep-alive connections.
|
43
46
|
# @param client [Puma::Client]
|
44
|
-
# @param io_buffer [Puma::IOBuffer]
|
45
47
|
# @param requests [Integer]
|
46
48
|
# @return [Boolean,:async]
|
47
49
|
#
|
48
|
-
def handle_request(client,
|
50
|
+
def handle_request(client, requests)
|
49
51
|
env = client.env
|
52
|
+
io_buffer = client.io_buffer
|
50
53
|
socket = client.io # io may be a MiniSSL::Socket
|
54
|
+
app_body = nil
|
55
|
+
|
51
56
|
|
52
57
|
return false if closed_socket?(socket)
|
53
58
|
|
59
|
+
if client.http_content_length_limit_exceeded
|
60
|
+
return prepare_response(413, {}, ["Payload Too Large"], requests, client)
|
61
|
+
end
|
62
|
+
|
54
63
|
normalize_env env, client
|
55
64
|
|
56
65
|
env[PUMA_SOCKET] = socket
|
@@ -68,7 +77,9 @@ module Puma
|
|
68
77
|
if @early_hints
|
69
78
|
env[EARLY_HINTS] = lambda { |headers|
|
70
79
|
begin
|
71
|
-
|
80
|
+
unless (str = str_early_hints headers).empty?
|
81
|
+
fast_write_str socket, "HTTP/1.1 103 Early Hints\r\n#{str}\r\n"
|
82
|
+
end
|
72
83
|
rescue ConnectionError => e
|
73
84
|
@log_writer.debug_error e
|
74
85
|
# noop, if we lost the socket we just won't send the early hints
|
@@ -84,15 +95,20 @@ module Puma
|
|
84
95
|
env[RACK_AFTER_REPLY] ||= []
|
85
96
|
|
86
97
|
begin
|
87
|
-
if
|
88
|
-
status, headers,
|
98
|
+
if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
|
99
|
+
status, headers, app_body = @thread_pool.with_force_shutdown do
|
89
100
|
@app.call(env)
|
90
101
|
end
|
91
102
|
else
|
92
103
|
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
|
93
|
-
status, headers,
|
104
|
+
status, headers, app_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
|
94
105
|
end
|
95
106
|
|
107
|
+
# app_body needs to always be closed, hold value in case lowlevel_error
|
108
|
+
# is called
|
109
|
+
res_body = app_body
|
110
|
+
|
111
|
+
# full hijack, app called env['rack.hijack']
|
96
112
|
return :async if client.hijacked
|
97
113
|
|
98
114
|
status = status.to_i
|
@@ -114,114 +130,136 @@ module Puma
|
|
114
130
|
|
115
131
|
status, headers, res_body = lowlevel_error(e, env, 500)
|
116
132
|
end
|
117
|
-
prepare_response(status, headers, res_body,
|
133
|
+
prepare_response(status, headers, res_body, requests, client)
|
134
|
+
ensure
|
135
|
+
io_buffer.reset
|
136
|
+
uncork_socket client.io
|
137
|
+
app_body.close if app_body.respond_to? :close
|
138
|
+
client.tempfile&.unlink
|
139
|
+
after_reply = env[RACK_AFTER_REPLY] || []
|
140
|
+
begin
|
141
|
+
after_reply.each { |o| o.call }
|
142
|
+
rescue StandardError => e
|
143
|
+
@log_writer.debug_error e
|
144
|
+
end unless after_reply.empty?
|
118
145
|
end
|
119
146
|
|
120
147
|
# Assembles the headers and prepares the body for actually sending the
|
121
|
-
# response via
|
148
|
+
# response via `#fast_write_response`.
|
122
149
|
#
|
123
150
|
# @param status [Integer] the status returned by the Rack application
|
124
151
|
# @param headers [Hash] the headers returned by the Rack application
|
125
|
-
# @param
|
126
|
-
# a call to `lowlevel_error`
|
127
|
-
# @param io_buffer [Puma::IOBuffer] modified in place
|
152
|
+
# @param res_body [Array] the body returned by the Rack application or
|
153
|
+
# a call to `Server#lowlevel_error`
|
128
154
|
# @param requests [Integer] number of inline requests handled
|
129
155
|
# @param client [Puma::Client]
|
130
|
-
# @return [Boolean,:async]
|
131
|
-
def prepare_response(status, headers,
|
156
|
+
# @return [Boolean,:async] keep-alive status or `:async`
|
157
|
+
def prepare_response(status, headers, res_body, requests, client)
|
132
158
|
env = client.env
|
133
|
-
socket
|
134
|
-
|
135
|
-
after_reply = env[RACK_AFTER_REPLY] || []
|
159
|
+
socket = client.io
|
160
|
+
io_buffer = client.io_buffer
|
136
161
|
|
137
162
|
return false if closed_socket?(socket)
|
138
163
|
|
139
|
-
|
140
|
-
|
141
|
-
#
|
142
|
-
#
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
164
|
+
# Close the connection after a reasonable number of inline requests
|
165
|
+
# if the server is at capacity and the listener has a new connection ready.
|
166
|
+
# This allows Puma to service connections fairly when the number
|
167
|
+
# of concurrent connections exceeds the size of the threadpool.
|
168
|
+
force_keep_alive = requests < @max_fast_inline ||
|
169
|
+
@thread_pool.busy_threads < @max_threads ||
|
170
|
+
!client.listener.to_io.wait_readable(0)
|
171
|
+
|
172
|
+
resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
|
173
|
+
|
174
|
+
close_body = false
|
175
|
+
response_hijack = nil
|
176
|
+
content_length = resp_info[:content_length]
|
177
|
+
keep_alive = resp_info[:keep_alive]
|
178
|
+
|
179
|
+
if res_body.respond_to?(:each) && !resp_info[:response_hijack]
|
180
|
+
# below converts app_body into body, dependent on app_body's characteristics, and
|
181
|
+
# content_length will be set if it can be determined
|
182
|
+
if !content_length && !resp_info[:transfer_encoding] && status != 204
|
183
|
+
if res_body.respond_to?(:to_ary) && (array_body = res_body.to_ary) &&
|
184
|
+
array_body.is_a?(Array)
|
185
|
+
body = array_body.compact
|
186
|
+
content_length = body.sum(&:bytesize)
|
187
|
+
elsif res_body.is_a?(File) && res_body.respond_to?(:size)
|
188
|
+
body = res_body
|
189
|
+
content_length = body.size
|
190
|
+
elsif res_body.respond_to?(:to_path) && File.readable?(fn = res_body.to_path)
|
191
|
+
body = File.open fn, 'rb'
|
192
|
+
content_length = body.size
|
193
|
+
close_body = true
|
194
|
+
else
|
195
|
+
body = res_body
|
153
196
|
end
|
154
|
-
|
155
|
-
|
156
|
-
resp_info[:content_length] = app_body.size
|
157
|
-
body = app_body
|
158
|
-
elsif app_body.respond_to?(:to_path) && app_body.respond_to?(:each) &&
|
159
|
-
File.readable?(fn = app_body.to_path)
|
197
|
+
elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) &&
|
198
|
+
File.readable?(fn = res_body.to_path)
|
160
199
|
body = File.open fn, 'rb'
|
161
|
-
|
200
|
+
content_length = body.size
|
201
|
+
close_body = true
|
202
|
+
elsif !res_body.is_a?(::File) && res_body.respond_to?(:filename) &&
|
203
|
+
res_body.respond_to?(:bytesize) && File.readable?(fn = res_body.filename)
|
204
|
+
# Sprockets::Asset
|
205
|
+
content_length = res_body.bytesize unless content_length
|
206
|
+
if (body_str = res_body.to_hash[:source])
|
207
|
+
body = [body_str]
|
208
|
+
else # avoid each and use a File object
|
209
|
+
body = File.open fn, 'rb'
|
210
|
+
close_body = true
|
211
|
+
end
|
162
212
|
else
|
163
|
-
body =
|
213
|
+
body = res_body
|
164
214
|
end
|
165
|
-
elsif !app_body.is_a?(::File) && app_body.respond_to?(:to_path) && app_body.respond_to?(:each) &&
|
166
|
-
File.readable?(fn = app_body.to_path)
|
167
|
-
body = File.open fn, 'rb'
|
168
|
-
resp_info[:content_length] = body.size
|
169
215
|
else
|
170
|
-
|
216
|
+
# partial hijack, from Rack spec:
|
217
|
+
# Servers must ignore the body part of the response tuple when the
|
218
|
+
# rack.hijack response header is present.
|
219
|
+
response_hijack = resp_info[:response_hijack] || res_body
|
171
220
|
end
|
172
221
|
|
173
222
|
line_ending = LINE_END
|
174
223
|
|
175
|
-
content_length = resp_info[:content_length]
|
176
|
-
keep_alive = resp_info[:keep_alive]
|
177
|
-
|
178
|
-
if app_body && !app_body.respond_to?(:each)
|
179
|
-
response_hijack = app_body
|
180
|
-
else
|
181
|
-
response_hijack = resp_info[:response_hijack]
|
182
|
-
end
|
183
|
-
|
184
224
|
cork_socket socket
|
185
225
|
|
186
226
|
if resp_info[:no_body]
|
187
|
-
|
227
|
+
# 101 (Switching Protocols) doesn't return here or have content_length,
|
228
|
+
# it should be using `response_hijack`
|
229
|
+
unless status == 101
|
230
|
+
if content_length && status != 204
|
231
|
+
io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
232
|
+
end
|
233
|
+
|
234
|
+
io_buffer << LINE_END
|
235
|
+
fast_write_str socket, io_buffer.read_and_reset
|
236
|
+
socket.flush
|
237
|
+
return keep_alive
|
238
|
+
end
|
239
|
+
else
|
240
|
+
if content_length
|
188
241
|
io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
242
|
+
chunked = false
|
243
|
+
elsif !response_hijack && resp_info[:allow_chunked]
|
244
|
+
io_buffer << TRANSFER_ENCODING_CHUNKED
|
245
|
+
chunked = true
|
189
246
|
end
|
190
|
-
|
191
|
-
io_buffer << LINE_END
|
192
|
-
fast_write_str socket, io_buffer.to_s
|
193
|
-
return keep_alive
|
194
|
-
end
|
195
|
-
if content_length
|
196
|
-
io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
197
|
-
chunked = false
|
198
|
-
elsif !response_hijack and resp_info[:allow_chunked]
|
199
|
-
io_buffer << TRANSFER_ENCODING_CHUNKED
|
200
|
-
chunked = true
|
201
247
|
end
|
202
248
|
|
203
249
|
io_buffer << line_ending
|
204
250
|
|
251
|
+
# partial hijack, we write headers, then hand the socket to the app via
|
252
|
+
# response_hijack.call
|
205
253
|
if response_hijack
|
206
|
-
fast_write_str socket, io_buffer.
|
254
|
+
fast_write_str socket, io_buffer.read_and_reset
|
255
|
+
uncork_socket socket
|
207
256
|
response_hijack.call socket
|
208
257
|
return :async
|
209
258
|
end
|
210
259
|
|
211
260
|
fast_write_response socket, body, io_buffer, chunked, content_length.to_i
|
261
|
+
body.close if close_body
|
212
262
|
keep_alive
|
213
|
-
ensure
|
214
|
-
io_buffer.reset
|
215
|
-
resp_info = nil
|
216
|
-
uncork_socket socket
|
217
|
-
app_body.close if app_body.respond_to? :close
|
218
|
-
client.tempfile&.unlink
|
219
|
-
|
220
|
-
begin
|
221
|
-
after_reply.each { |o| o.call }
|
222
|
-
rescue StandardError => e
|
223
|
-
@log_writer.debug_error e
|
224
|
-
end unless after_reply.empty?
|
225
263
|
end
|
226
264
|
|
227
265
|
# @param env [Hash] see Puma::Client#env, from request
|
@@ -237,10 +275,10 @@ module Puma
|
|
237
275
|
|
238
276
|
# Used to write 'early hints', 'no body' responses, 'hijacked' responses,
|
239
277
|
# and body segments (called by `fast_write_response`).
|
240
|
-
# Writes a string to
|
278
|
+
# Writes a string to a socket (normally `Client#io`) using `write_nonblock`.
|
241
279
|
# Large strings may not be written in one pass, especially if `io` is a
|
242
280
|
# `MiniSSL::Socket`.
|
243
|
-
# @param
|
281
|
+
# @param socket [#write_nonblock] the request/response socket
|
244
282
|
# @param str [String] the string written to the io
|
245
283
|
# @raise [ConnectionError]
|
246
284
|
#
|
@@ -249,7 +287,7 @@ module Puma
|
|
249
287
|
byte_size = str.bytesize
|
250
288
|
while n < byte_size
|
251
289
|
begin
|
252
|
-
n += socket.
|
290
|
+
n += socket.write_nonblock(n.zero? ? str : str.byteslice(n..-1))
|
253
291
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
254
292
|
unless socket.wait_writable WRITE_TIMEOUT
|
255
293
|
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
@@ -267,39 +305,41 @@ module Puma
|
|
267
305
|
# @param socket [#write] the response socket
|
268
306
|
# @param body [Enumerable, File] the body object
|
269
307
|
# @param io_buffer [Puma::IOBuffer] contains headers
|
270
|
-
# @param
|
308
|
+
# @param chunked [Boolean]
|
309
|
+
# @paramn content_length [Integer
|
271
310
|
# @raise [ConnectionError]
|
272
311
|
#
|
273
312
|
def fast_write_response(socket, body, io_buffer, chunked, content_length)
|
274
|
-
if body.is_a?(::File)
|
313
|
+
if body.is_a?(::File) && body.respond_to?(:read)
|
275
314
|
if chunked # would this ever happen?
|
276
|
-
while
|
277
|
-
io_buffer.append
|
315
|
+
while chunk = body.read(BODY_LEN_MAX)
|
316
|
+
io_buffer.append chunk.bytesize.to_s(16), LINE_END, chunk, LINE_END
|
278
317
|
end
|
279
|
-
|
280
|
-
fast_write_str socket, io_buffer.to_s
|
318
|
+
fast_write_str socket, CLOSE_CHUNKED
|
281
319
|
else
|
282
320
|
if content_length <= IO_BODY_MAX
|
283
|
-
io_buffer.write body.
|
284
|
-
fast_write_str socket, io_buffer.
|
321
|
+
io_buffer.write body.read(content_length)
|
322
|
+
fast_write_str socket, io_buffer.read_and_reset
|
285
323
|
else
|
286
|
-
fast_write_str socket, io_buffer.
|
324
|
+
fast_write_str socket, io_buffer.read_and_reset
|
287
325
|
IO.copy_stream body, socket
|
288
326
|
end
|
289
327
|
end
|
290
|
-
body.close
|
291
328
|
elsif body.is_a?(::Array) && body.length == 1
|
292
|
-
body_first =
|
293
|
-
|
329
|
+
body_first = nil
|
330
|
+
# using body_first = body.first causes issues?
|
331
|
+
body.each { |str| body_first ||= str }
|
332
|
+
|
333
|
+
if body_first.is_a?(::String) && body_first.bytesize < BODY_LEN_MAX
|
334
|
+
# smaller body, write to io_buffer first
|
335
|
+
io_buffer.write body_first
|
336
|
+
fast_write_str socket, io_buffer.read_and_reset
|
337
|
+
else
|
294
338
|
# large body, write both header & body to socket
|
295
|
-
fast_write_str socket, io_buffer.
|
339
|
+
fast_write_str socket, io_buffer.read_and_reset
|
296
340
|
fast_write_str socket, body_first
|
297
|
-
else
|
298
|
-
# smaller body, write to stream first
|
299
|
-
io_buffer.write body_first
|
300
|
-
fast_write_str socket, io_buffer.to_s
|
301
341
|
end
|
302
|
-
|
342
|
+
elsif body.is_a?(::Array)
|
303
343
|
# for array bodies, flush io_buffer to socket when size is greater than
|
304
344
|
# IO_BUFFER_LEN_MAX
|
305
345
|
if chunked
|
@@ -307,8 +347,7 @@ module Puma
|
|
307
347
|
next if (byte_size = part.bytesize).zero?
|
308
348
|
io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
|
309
349
|
if io_buffer.length > IO_BUFFER_LEN_MAX
|
310
|
-
fast_write_str socket, io_buffer.
|
311
|
-
io_buffer.reset
|
350
|
+
fast_write_str socket, io_buffer.read_and_reset
|
312
351
|
end
|
313
352
|
end
|
314
353
|
io_buffer.write CLOSE_CHUNKED
|
@@ -317,13 +356,37 @@ module Puma
|
|
317
356
|
next if part.bytesize.zero?
|
318
357
|
io_buffer.write part
|
319
358
|
if io_buffer.length > IO_BUFFER_LEN_MAX
|
320
|
-
fast_write_str socket, io_buffer.
|
321
|
-
io_buffer.reset
|
359
|
+
fast_write_str socket, io_buffer.read_and_reset
|
322
360
|
end
|
323
361
|
end
|
324
362
|
end
|
325
|
-
|
363
|
+
# may write last body part for non-chunked, also headers if array is empty
|
364
|
+
fast_write_str(socket, io_buffer.read_and_reset) unless io_buffer.length.zero?
|
365
|
+
else
|
366
|
+
# for enum bodies
|
367
|
+
if chunked
|
368
|
+
empty_body = true
|
369
|
+
body.each do |part|
|
370
|
+
next if part.nil? || (byte_size = part.bytesize).zero?
|
371
|
+
empty_body = false
|
372
|
+
io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
|
373
|
+
fast_write_str socket, io_buffer.read_and_reset
|
374
|
+
end
|
375
|
+
if empty_body
|
376
|
+
io_buffer << CLOSE_CHUNKED
|
377
|
+
fast_write_str socket, io_buffer.read_and_reset
|
378
|
+
else
|
379
|
+
fast_write_str socket, CLOSE_CHUNKED
|
380
|
+
end
|
381
|
+
else
|
382
|
+
fast_write_str socket, io_buffer.read_and_reset
|
383
|
+
body.each do |part|
|
384
|
+
next if part.bytesize.zero?
|
385
|
+
fast_write_str socket, part
|
386
|
+
end
|
387
|
+
end
|
326
388
|
end
|
389
|
+
socket.flush
|
327
390
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
328
391
|
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
329
392
|
rescue Errno::EPIPE, SystemCallError, IOError
|
@@ -357,7 +420,11 @@ module Puma
|
|
357
420
|
|
358
421
|
unless env[REQUEST_PATH]
|
359
422
|
# it might be a dumbass full host request header
|
360
|
-
uri =
|
423
|
+
uri = begin
|
424
|
+
URI.parse(env[REQUEST_URI])
|
425
|
+
rescue URI::InvalidURIError
|
426
|
+
raise Puma::HttpParserError
|
427
|
+
end
|
361
428
|
env[REQUEST_PATH] = uri.path
|
362
429
|
|
363
430
|
# A nil env value will cause a LintError (and fatal errors elsewhere),
|
@@ -436,7 +503,7 @@ module Puma
|
|
436
503
|
to_add = nil
|
437
504
|
|
438
505
|
env.each do |k,v|
|
439
|
-
if k.start_with?("HTTP_")
|
506
|
+
if k.start_with?("HTTP_") && k.include?(",") && k != "HTTP_TRANSFER,ENCODING"
|
440
507
|
if to_delete
|
441
508
|
to_delete << k
|
442
509
|
else
|
@@ -464,7 +531,7 @@ module Puma
|
|
464
531
|
# @version 5.0.3
|
465
532
|
#
|
466
533
|
def str_early_hints(headers)
|
467
|
-
eh_str = +"
|
534
|
+
eh_str = +""
|
468
535
|
headers.each_pair do |k, vs|
|
469
536
|
next if illegal_header_key?(k)
|
470
537
|
|
@@ -473,11 +540,11 @@ module Puma
|
|
473
540
|
next if illegal_header_value?(v)
|
474
541
|
eh_str << "#{k}: #{v}\r\n"
|
475
542
|
end
|
476
|
-
|
543
|
+
elsif !(vs.to_s.empty? || !illegal_header_value?(vs))
|
477
544
|
eh_str << "#{k}: #{vs}\r\n"
|
478
545
|
end
|
479
546
|
end
|
480
|
-
|
547
|
+
eh_str.freeze
|
481
548
|
end
|
482
549
|
private :str_early_hints
|
483
550
|
|
@@ -496,12 +563,13 @@ module Puma
|
|
496
563
|
# @param content_length [Integer,nil] content length if it can be determined from the
|
497
564
|
# response body
|
498
565
|
# @param io_buffer [Puma::IOBuffer] modified inn place
|
499
|
-
# @param
|
500
|
-
#
|
566
|
+
# @param force_keep_alive [Boolean] 'anded' with keep_alive, based on system
|
567
|
+
# status and `@max_fast_inline`
|
501
568
|
# @return [Hash] resp_info
|
502
569
|
# @version 5.0.3
|
503
570
|
#
|
504
|
-
def str_headers(env, status, headers, res_body, io_buffer,
|
571
|
+
def str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
|
572
|
+
|
505
573
|
line_ending = LINE_END
|
506
574
|
colon = COLON
|
507
575
|
|
@@ -544,13 +612,8 @@ module Puma
|
|
544
612
|
# if running without request queueing
|
545
613
|
resp_info[:keep_alive] &&= @queue_requests
|
546
614
|
|
547
|
-
#
|
548
|
-
|
549
|
-
# This allows Puma to service connections fairly when the number
|
550
|
-
# of concurrent connections exceeds the size of the threadpool.
|
551
|
-
resp_info[:keep_alive] &&= requests < @max_fast_inline ||
|
552
|
-
@thread_pool.busy_threads < @max_threads ||
|
553
|
-
!client.listener.to_io.wait_readable(0)
|
615
|
+
# see prepare_response
|
616
|
+
resp_info[:keep_alive] &&= force_keep_alive
|
554
617
|
|
555
618
|
resp_info[:response_hijack] = nil
|
556
619
|
|
@@ -560,7 +623,8 @@ module Puma
|
|
560
623
|
case k.downcase
|
561
624
|
when CONTENT_LENGTH2
|
562
625
|
next if illegal_header_value?(vs)
|
563
|
-
|
626
|
+
# nil.to_i is 0, nil&.to_i is nil
|
627
|
+
resp_info[:content_length] = vs&.to_i
|
564
628
|
next
|
565
629
|
when TRANSFER_ENCODING
|
566
630
|
resp_info[:allow_chunked] = false
|
data/lib/puma/runner.rb
CHANGED
@@ -75,7 +75,11 @@ module Puma
|
|
75
75
|
control = Puma::Server.new app, nil,
|
76
76
|
{ min_threads: 0, max_threads: 1, queue_requests: false, log_writer: @log_writer }
|
77
77
|
|
78
|
-
|
78
|
+
begin
|
79
|
+
control.binder.parse [str], nil, 'Starting control server'
|
80
|
+
rescue Errno::EADDRINUSE, Errno::EACCES => e
|
81
|
+
raise e, "Error: Control server address '#{str}' is already in use. Original error: #{e.message}"
|
82
|
+
end
|
79
83
|
|
80
84
|
control.run thread_name: 'ctl'
|
81
85
|
@control = control
|
@@ -182,6 +186,10 @@ module Puma
|
|
182
186
|
end
|
183
187
|
end
|
184
188
|
|
189
|
+
def utc_iso8601(val)
|
190
|
+
"#{val.utc.strftime '%FT%T'}Z"
|
191
|
+
end
|
192
|
+
|
185
193
|
def stats
|
186
194
|
{
|
187
195
|
versions: {
|
@@ -194,5 +202,12 @@ module Puma
|
|
194
202
|
}
|
195
203
|
}
|
196
204
|
end
|
205
|
+
|
206
|
+
# this method call should always be guarded by `@log_writer.debug?`
|
207
|
+
def debug_loaded_extensions(str)
|
208
|
+
@log_writer.debug "────────────────────────────────── #{str}"
|
209
|
+
re_ext = /\.#{RbConfig::CONFIG['DLEXT']}\z/i
|
210
|
+
$LOADED_FEATURES.grep(re_ext).each { |f| @log_writer.debug(" #{f}") }
|
211
|
+
end
|
197
212
|
end
|
198
213
|
end
|