puma 6.0.0 → 6.6.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/History.md +392 -13
- data/LICENSE +0 -0
- data/README.md +135 -29
- 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 +11 -1
- 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/java_options.md +54 -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 +4 -0
- data/docs/rails_dev_mode.md +0 -0
- data/docs/restart.md +1 -0
- data/docs/signals.md +2 -2
- data/docs/stats.md +8 -3
- data/docs/systemd.md +13 -7
- 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 +21 -14
- 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 +107 -10
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +30 -7
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +0 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +2 -1
- data/ext/puma_http11/puma_http11.c +4 -1
- data/lib/puma/app/status.rb +1 -1
- data/lib/puma/binder.rb +26 -15
- data/lib/puma/cli.rb +13 -5
- data/lib/puma/client.rb +113 -26
- data/lib/puma/cluster/worker.rb +14 -6
- data/lib/puma/cluster/worker_handle.rb +4 -5
- data/lib/puma/cluster.rb +93 -22
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +42 -22
- data/lib/puma/const.rb +149 -89
- data/lib/puma/control_cli.rb +16 -9
- data/lib/puma/detect.rb +5 -4
- data/lib/puma/dsl.rb +432 -40
- data/lib/puma/error_logger.rb +6 -5
- data/lib/puma/events.rb +0 -0
- data/lib/puma/io_buffer.rb +10 -0
- data/lib/puma/jruby_restart.rb +0 -16
- data/lib/puma/json_serialization.rb +0 -0
- data/lib/puma/launcher/bundle_pruner.rb +0 -0
- data/lib/puma/launcher.rb +29 -29
- data/lib/puma/log_writer.rb +23 -13
- data/lib/puma/minissl/context_builder.rb +4 -0
- data/lib/puma/minissl.rb +23 -0
- data/lib/puma/null_io.rb +42 -2
- 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 +207 -126
- data/lib/puma/runner.rb +26 -4
- data/lib/puma/sd_notify.rb +146 -0
- data/lib/puma/server.rb +121 -49
- data/lib/puma/single.rb +3 -1
- data/lib/puma/state_file.rb +2 -2
- data/lib/puma/thread_pool.rb +56 -9
- data/lib/puma/util.rb +1 -1
- data/lib/puma.rb +1 -3
- data/lib/rack/handler/puma.rb +116 -86
- data/tools/Dockerfile +2 -2
- data/tools/trickletest.rb +0 -0
- metadata +12 -13
- 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,21 @@ 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
|
51
55
|
|
52
56
|
return false if closed_socket?(socket)
|
53
57
|
|
58
|
+
if client.http_content_length_limit_exceeded
|
59
|
+
return prepare_response(413, {}, ["Payload Too Large"], requests, client)
|
60
|
+
end
|
61
|
+
|
54
62
|
normalize_env env, client
|
55
63
|
|
56
64
|
env[PUMA_SOCKET] = socket
|
@@ -68,7 +76,9 @@ module Puma
|
|
68
76
|
if @early_hints
|
69
77
|
env[EARLY_HINTS] = lambda { |headers|
|
70
78
|
begin
|
71
|
-
|
79
|
+
unless (str = str_early_hints headers).empty?
|
80
|
+
fast_write_str socket, "HTTP/1.1 103 Early Hints\r\n#{str}\r\n"
|
81
|
+
end
|
72
82
|
rescue ConnectionError => e
|
73
83
|
@log_writer.debug_error e
|
74
84
|
# noop, if we lost the socket we just won't send the early hints
|
@@ -84,15 +94,20 @@ module Puma
|
|
84
94
|
env[RACK_AFTER_REPLY] ||= []
|
85
95
|
|
86
96
|
begin
|
87
|
-
if
|
88
|
-
status, headers,
|
97
|
+
if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
|
98
|
+
status, headers, app_body = @thread_pool.with_force_shutdown do
|
89
99
|
@app.call(env)
|
90
100
|
end
|
91
101
|
else
|
92
102
|
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
|
93
|
-
status, headers,
|
103
|
+
status, headers, app_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
|
94
104
|
end
|
95
105
|
|
106
|
+
# app_body needs to always be closed, hold value in case lowlevel_error
|
107
|
+
# is called
|
108
|
+
res_body = app_body
|
109
|
+
|
110
|
+
# full hijack, app called env['rack.hijack']
|
96
111
|
return :async if client.hijacked
|
97
112
|
|
98
113
|
status = status.to_i
|
@@ -114,114 +129,141 @@ module Puma
|
|
114
129
|
|
115
130
|
status, headers, res_body = lowlevel_error(e, env, 500)
|
116
131
|
end
|
117
|
-
prepare_response(status, headers, res_body,
|
132
|
+
prepare_response(status, headers, res_body, requests, client)
|
133
|
+
ensure
|
134
|
+
io_buffer.reset
|
135
|
+
uncork_socket client.io
|
136
|
+
app_body.close if app_body.respond_to? :close
|
137
|
+
client&.tempfile_close
|
138
|
+
after_reply = env[RACK_AFTER_REPLY] || []
|
139
|
+
begin
|
140
|
+
after_reply.each { |o| o.call }
|
141
|
+
rescue StandardError => e
|
142
|
+
@log_writer.debug_error e
|
143
|
+
end unless after_reply.empty?
|
118
144
|
end
|
119
145
|
|
120
146
|
# Assembles the headers and prepares the body for actually sending the
|
121
|
-
# response via
|
147
|
+
# response via `#fast_write_response`.
|
122
148
|
#
|
123
149
|
# @param status [Integer] the status returned by the Rack application
|
124
150
|
# @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
|
151
|
+
# @param res_body [Array] the body returned by the Rack application or
|
152
|
+
# a call to `Server#lowlevel_error`
|
128
153
|
# @param requests [Integer] number of inline requests handled
|
129
154
|
# @param client [Puma::Client]
|
130
|
-
# @return [Boolean,:async]
|
131
|
-
def prepare_response(status, headers,
|
155
|
+
# @return [Boolean,:async] keep-alive status or `:async`
|
156
|
+
def prepare_response(status, headers, res_body, requests, client)
|
132
157
|
env = client.env
|
133
|
-
socket
|
134
|
-
|
135
|
-
after_reply = env[RACK_AFTER_REPLY] || []
|
158
|
+
socket = client.io
|
159
|
+
io_buffer = client.io_buffer
|
136
160
|
|
137
161
|
return false if closed_socket?(socket)
|
138
162
|
|
139
|
-
|
140
|
-
|
141
|
-
#
|
142
|
-
#
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
body = array_body.map { |part| length += part.bytesize; part }
|
148
|
-
elsif app_body.is_a?(::File) && app_body.respond_to?(:size)
|
149
|
-
length = app_body.size
|
150
|
-
elsif app_body.respond_to?(:each)
|
151
|
-
body = []
|
152
|
-
app_body.each { |part| length += part.bytesize; body << part }
|
153
|
-
end
|
154
|
-
resp_info[:content_length] = length
|
155
|
-
elsif app_body.is_a?(File) && app_body.respond_to?(:size)
|
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)
|
160
|
-
body = File.open fn, 'rb'
|
161
|
-
resp_info[:content_length] = body.size
|
162
|
-
else
|
163
|
-
body = app_body
|
164
|
-
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
|
163
|
+
# Close the connection after a reasonable number of inline requests
|
164
|
+
# if the server is at capacity and the listener has a new connection ready.
|
165
|
+
# This allows Puma to service connections fairly when the number
|
166
|
+
# of concurrent connections exceeds the size of the threadpool.
|
167
|
+
force_keep_alive = if @enable_keep_alives
|
168
|
+
requests < @max_fast_inline ||
|
169
|
+
@thread_pool.busy_threads < @max_threads ||
|
170
|
+
!client.listener.to_io.wait_readable(0)
|
169
171
|
else
|
170
|
-
|
172
|
+
# Always set force_keep_alive to false if the server has keep-alives not enabled.
|
173
|
+
false
|
171
174
|
end
|
172
175
|
|
173
|
-
|
176
|
+
resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
|
174
177
|
|
178
|
+
close_body = false
|
179
|
+
response_hijack = nil
|
175
180
|
content_length = resp_info[:content_length]
|
176
181
|
keep_alive = resp_info[:keep_alive]
|
177
182
|
|
178
|
-
if
|
179
|
-
|
183
|
+
if res_body.respond_to?(:each) && !resp_info[:response_hijack]
|
184
|
+
# below converts app_body into body, dependent on app_body's characteristics, and
|
185
|
+
# content_length will be set if it can be determined
|
186
|
+
if !content_length && !resp_info[:transfer_encoding] && status != 204
|
187
|
+
if res_body.respond_to?(:to_ary) && (array_body = res_body.to_ary) &&
|
188
|
+
array_body.is_a?(Array)
|
189
|
+
body = array_body.compact
|
190
|
+
content_length = body.sum(&:bytesize)
|
191
|
+
elsif res_body.is_a?(File) && res_body.respond_to?(:size)
|
192
|
+
body = res_body
|
193
|
+
content_length = body.size
|
194
|
+
elsif res_body.respond_to?(:to_path) && File.readable?(fn = res_body.to_path)
|
195
|
+
body = File.open fn, 'rb'
|
196
|
+
content_length = body.size
|
197
|
+
close_body = true
|
198
|
+
else
|
199
|
+
body = res_body
|
200
|
+
end
|
201
|
+
elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) &&
|
202
|
+
File.readable?(fn = res_body.to_path)
|
203
|
+
body = File.open fn, 'rb'
|
204
|
+
content_length = body.size
|
205
|
+
close_body = true
|
206
|
+
elsif !res_body.is_a?(::File) && res_body.respond_to?(:filename) &&
|
207
|
+
res_body.respond_to?(:bytesize) && File.readable?(fn = res_body.filename)
|
208
|
+
# Sprockets::Asset
|
209
|
+
content_length = res_body.bytesize unless content_length
|
210
|
+
if (body_str = res_body.to_hash[:source])
|
211
|
+
body = [body_str]
|
212
|
+
else # avoid each and use a File object
|
213
|
+
body = File.open fn, 'rb'
|
214
|
+
close_body = true
|
215
|
+
end
|
216
|
+
else
|
217
|
+
body = res_body
|
218
|
+
end
|
180
219
|
else
|
181
|
-
|
220
|
+
# partial hijack, from Rack spec:
|
221
|
+
# Servers must ignore the body part of the response tuple when the
|
222
|
+
# rack.hijack response header is present.
|
223
|
+
response_hijack = resp_info[:response_hijack] || res_body
|
182
224
|
end
|
183
225
|
|
226
|
+
line_ending = LINE_END
|
227
|
+
|
184
228
|
cork_socket socket
|
185
229
|
|
186
230
|
if resp_info[:no_body]
|
187
|
-
|
231
|
+
# 101 (Switching Protocols) doesn't return here or have content_length,
|
232
|
+
# it should be using `response_hijack`
|
233
|
+
unless status == 101
|
234
|
+
if content_length && status != 204
|
235
|
+
io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
236
|
+
end
|
237
|
+
|
238
|
+
io_buffer << LINE_END
|
239
|
+
fast_write_str socket, io_buffer.read_and_reset
|
240
|
+
socket.flush
|
241
|
+
return keep_alive
|
242
|
+
end
|
243
|
+
else
|
244
|
+
if content_length
|
188
245
|
io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
246
|
+
chunked = false
|
247
|
+
elsif !response_hijack && resp_info[:allow_chunked]
|
248
|
+
io_buffer << TRANSFER_ENCODING_CHUNKED
|
249
|
+
chunked = true
|
189
250
|
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
251
|
end
|
202
252
|
|
203
253
|
io_buffer << line_ending
|
204
254
|
|
255
|
+
# partial hijack, we write headers, then hand the socket to the app via
|
256
|
+
# response_hijack.call
|
205
257
|
if response_hijack
|
206
|
-
fast_write_str socket, io_buffer.
|
258
|
+
fast_write_str socket, io_buffer.read_and_reset
|
259
|
+
uncork_socket socket
|
207
260
|
response_hijack.call socket
|
208
261
|
return :async
|
209
262
|
end
|
210
263
|
|
211
264
|
fast_write_response socket, body, io_buffer, chunked, content_length.to_i
|
265
|
+
body.close if close_body
|
212
266
|
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
267
|
end
|
226
268
|
|
227
269
|
# @param env [Hash] see Puma::Client#env, from request
|
@@ -237,10 +279,10 @@ module Puma
|
|
237
279
|
|
238
280
|
# Used to write 'early hints', 'no body' responses, 'hijacked' responses,
|
239
281
|
# and body segments (called by `fast_write_response`).
|
240
|
-
# Writes a string to
|
282
|
+
# Writes a string to a socket (normally `Client#io`) using `write_nonblock`.
|
241
283
|
# Large strings may not be written in one pass, especially if `io` is a
|
242
284
|
# `MiniSSL::Socket`.
|
243
|
-
# @param
|
285
|
+
# @param socket [#write_nonblock] the request/response socket
|
244
286
|
# @param str [String] the string written to the io
|
245
287
|
# @raise [ConnectionError]
|
246
288
|
#
|
@@ -249,7 +291,7 @@ module Puma
|
|
249
291
|
byte_size = str.bytesize
|
250
292
|
while n < byte_size
|
251
293
|
begin
|
252
|
-
n += socket.
|
294
|
+
n += socket.write_nonblock(n.zero? ? str : str.byteslice(n..-1))
|
253
295
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
254
296
|
unless socket.wait_writable WRITE_TIMEOUT
|
255
297
|
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
@@ -267,39 +309,41 @@ module Puma
|
|
267
309
|
# @param socket [#write] the response socket
|
268
310
|
# @param body [Enumerable, File] the body object
|
269
311
|
# @param io_buffer [Puma::IOBuffer] contains headers
|
270
|
-
# @param
|
312
|
+
# @param chunked [Boolean]
|
313
|
+
# @paramn content_length [Integer
|
271
314
|
# @raise [ConnectionError]
|
272
315
|
#
|
273
316
|
def fast_write_response(socket, body, io_buffer, chunked, content_length)
|
274
|
-
if body.is_a?(::File)
|
317
|
+
if body.is_a?(::File) && body.respond_to?(:read)
|
275
318
|
if chunked # would this ever happen?
|
276
|
-
while
|
277
|
-
io_buffer.append
|
319
|
+
while chunk = body.read(BODY_LEN_MAX)
|
320
|
+
io_buffer.append chunk.bytesize.to_s(16), LINE_END, chunk, LINE_END
|
278
321
|
end
|
279
|
-
|
280
|
-
fast_write_str socket, io_buffer.to_s
|
322
|
+
fast_write_str socket, CLOSE_CHUNKED
|
281
323
|
else
|
282
324
|
if content_length <= IO_BODY_MAX
|
283
|
-
io_buffer.write body.
|
284
|
-
fast_write_str socket, io_buffer.
|
325
|
+
io_buffer.write body.read(content_length)
|
326
|
+
fast_write_str socket, io_buffer.read_and_reset
|
285
327
|
else
|
286
|
-
fast_write_str socket, io_buffer.
|
328
|
+
fast_write_str socket, io_buffer.read_and_reset
|
287
329
|
IO.copy_stream body, socket
|
288
330
|
end
|
289
331
|
end
|
290
|
-
body.close
|
291
332
|
elsif body.is_a?(::Array) && body.length == 1
|
292
|
-
body_first =
|
293
|
-
|
333
|
+
body_first = nil
|
334
|
+
# using body_first = body.first causes issues?
|
335
|
+
body.each { |str| body_first ||= str }
|
336
|
+
|
337
|
+
if body_first.is_a?(::String) && body_first.bytesize < BODY_LEN_MAX
|
338
|
+
# smaller body, write to io_buffer first
|
339
|
+
io_buffer.write body_first
|
340
|
+
fast_write_str socket, io_buffer.read_and_reset
|
341
|
+
else
|
294
342
|
# large body, write both header & body to socket
|
295
|
-
fast_write_str socket, io_buffer.
|
343
|
+
fast_write_str socket, io_buffer.read_and_reset
|
296
344
|
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
345
|
end
|
302
|
-
|
346
|
+
elsif body.is_a?(::Array)
|
303
347
|
# for array bodies, flush io_buffer to socket when size is greater than
|
304
348
|
# IO_BUFFER_LEN_MAX
|
305
349
|
if chunked
|
@@ -307,8 +351,7 @@ module Puma
|
|
307
351
|
next if (byte_size = part.bytesize).zero?
|
308
352
|
io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
|
309
353
|
if io_buffer.length > IO_BUFFER_LEN_MAX
|
310
|
-
fast_write_str socket, io_buffer.
|
311
|
-
io_buffer.reset
|
354
|
+
fast_write_str socket, io_buffer.read_and_reset
|
312
355
|
end
|
313
356
|
end
|
314
357
|
io_buffer.write CLOSE_CHUNKED
|
@@ -317,13 +360,37 @@ module Puma
|
|
317
360
|
next if part.bytesize.zero?
|
318
361
|
io_buffer.write part
|
319
362
|
if io_buffer.length > IO_BUFFER_LEN_MAX
|
320
|
-
fast_write_str socket, io_buffer.
|
321
|
-
io_buffer.reset
|
363
|
+
fast_write_str socket, io_buffer.read_and_reset
|
322
364
|
end
|
323
365
|
end
|
324
366
|
end
|
325
|
-
|
367
|
+
# may write last body part for non-chunked, also headers if array is empty
|
368
|
+
fast_write_str(socket, io_buffer.read_and_reset) unless io_buffer.length.zero?
|
369
|
+
else
|
370
|
+
# for enum bodies
|
371
|
+
if chunked
|
372
|
+
empty_body = true
|
373
|
+
body.each do |part|
|
374
|
+
next if part.nil? || (byte_size = part.bytesize).zero?
|
375
|
+
empty_body = false
|
376
|
+
io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
|
377
|
+
fast_write_str socket, io_buffer.read_and_reset
|
378
|
+
end
|
379
|
+
if empty_body
|
380
|
+
io_buffer << CLOSE_CHUNKED
|
381
|
+
fast_write_str socket, io_buffer.read_and_reset
|
382
|
+
else
|
383
|
+
fast_write_str socket, CLOSE_CHUNKED
|
384
|
+
end
|
385
|
+
else
|
386
|
+
fast_write_str socket, io_buffer.read_and_reset
|
387
|
+
body.each do |part|
|
388
|
+
next if part.bytesize.zero?
|
389
|
+
fast_write_str socket, part
|
390
|
+
end
|
391
|
+
end
|
326
392
|
end
|
393
|
+
socket.flush
|
327
394
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
328
395
|
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
329
396
|
rescue Errno::EPIPE, SystemCallError, IOError
|
@@ -357,7 +424,11 @@ module Puma
|
|
357
424
|
|
358
425
|
unless env[REQUEST_PATH]
|
359
426
|
# it might be a dumbass full host request header
|
360
|
-
uri =
|
427
|
+
uri = begin
|
428
|
+
URI.parse(env[REQUEST_URI])
|
429
|
+
rescue URI::InvalidURIError
|
430
|
+
raise Puma::HttpParserError
|
431
|
+
end
|
361
432
|
env[REQUEST_PATH] = uri.path
|
362
433
|
|
363
434
|
# A nil env value will cause a LintError (and fatal errors elsewhere),
|
@@ -428,6 +499,11 @@ module Puma
|
|
428
499
|
# compatibility, we'll convert them back. This code is written to
|
429
500
|
# avoid allocation in the common case (ie there are no headers
|
430
501
|
# with `,` in their names), that's why it has the extra conditionals.
|
502
|
+
#
|
503
|
+
# @note If a normalized version of a `,` header already exists, we ignore
|
504
|
+
# the `,` version. This prevents clobbering headers managed by proxies
|
505
|
+
# but not by clients (Like X-Forwarded-For).
|
506
|
+
#
|
431
507
|
# @param env [Hash] see Puma::Client#env, from request, modifies in place
|
432
508
|
# @version 5.0.3
|
433
509
|
#
|
@@ -436,23 +512,31 @@ module Puma
|
|
436
512
|
to_add = nil
|
437
513
|
|
438
514
|
env.each do |k,v|
|
439
|
-
if k.start_with?("HTTP_")
|
515
|
+
if k.start_with?("HTTP_") && k.include?(",") && !UNMASKABLE_HEADERS.key?(k)
|
440
516
|
if to_delete
|
441
517
|
to_delete << k
|
442
518
|
else
|
443
519
|
to_delete = [k]
|
444
520
|
end
|
445
521
|
|
522
|
+
new_k = k.tr(",", "_")
|
523
|
+
if env.key?(new_k)
|
524
|
+
next
|
525
|
+
end
|
526
|
+
|
446
527
|
unless to_add
|
447
528
|
to_add = {}
|
448
529
|
end
|
449
530
|
|
450
|
-
to_add[
|
531
|
+
to_add[new_k] = v
|
451
532
|
end
|
452
533
|
end
|
453
534
|
|
454
|
-
if to_delete
|
535
|
+
if to_delete # rubocop:disable Style/SafeNavigation
|
455
536
|
to_delete.each { |k| env.delete(k) }
|
537
|
+
end
|
538
|
+
|
539
|
+
if to_add
|
456
540
|
env.merge! to_add
|
457
541
|
end
|
458
542
|
end
|
@@ -464,7 +548,7 @@ module Puma
|
|
464
548
|
# @version 5.0.3
|
465
549
|
#
|
466
550
|
def str_early_hints(headers)
|
467
|
-
eh_str = +"
|
551
|
+
eh_str = +""
|
468
552
|
headers.each_pair do |k, vs|
|
469
553
|
next if illegal_header_key?(k)
|
470
554
|
|
@@ -473,11 +557,11 @@ module Puma
|
|
473
557
|
next if illegal_header_value?(v)
|
474
558
|
eh_str << "#{k}: #{v}\r\n"
|
475
559
|
end
|
476
|
-
|
560
|
+
elsif !(vs.to_s.empty? || !illegal_header_value?(vs))
|
477
561
|
eh_str << "#{k}: #{vs}\r\n"
|
478
562
|
end
|
479
563
|
end
|
480
|
-
|
564
|
+
eh_str.freeze
|
481
565
|
end
|
482
566
|
private :str_early_hints
|
483
567
|
|
@@ -496,12 +580,13 @@ module Puma
|
|
496
580
|
# @param content_length [Integer,nil] content length if it can be determined from the
|
497
581
|
# response body
|
498
582
|
# @param io_buffer [Puma::IOBuffer] modified inn place
|
499
|
-
# @param
|
500
|
-
#
|
583
|
+
# @param force_keep_alive [Boolean] 'anded' with keep_alive, based on system
|
584
|
+
# status and `@max_fast_inline`
|
501
585
|
# @return [Hash] resp_info
|
502
586
|
# @version 5.0.3
|
503
587
|
#
|
504
|
-
def str_headers(env, status, headers, res_body, io_buffer,
|
588
|
+
def str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
|
589
|
+
|
505
590
|
line_ending = LINE_END
|
506
591
|
colon = COLON
|
507
592
|
|
@@ -544,13 +629,8 @@ module Puma
|
|
544
629
|
# if running without request queueing
|
545
630
|
resp_info[:keep_alive] &&= @queue_requests
|
546
631
|
|
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)
|
632
|
+
# see prepare_response
|
633
|
+
resp_info[:keep_alive] &&= force_keep_alive
|
554
634
|
|
555
635
|
resp_info[:response_hijack] = nil
|
556
636
|
|
@@ -560,7 +640,8 @@ module Puma
|
|
560
640
|
case k.downcase
|
561
641
|
when CONTENT_LENGTH2
|
562
642
|
next if illegal_header_value?(vs)
|
563
|
-
|
643
|
+
# nil.to_i is 0, nil&.to_i is nil
|
644
|
+
resp_info[:content_length] = vs&.to_i
|
564
645
|
next
|
565
646
|
when TRANSFER_ENCODING
|
566
647
|
resp_info[:allow_chunked] = false
|
data/lib/puma/runner.rb
CHANGED
@@ -8,6 +8,9 @@ module Puma
|
|
8
8
|
# serve requests. This class spawns a new instance of `Puma::Server` via
|
9
9
|
# a call to `start_server`.
|
10
10
|
class Runner
|
11
|
+
|
12
|
+
include ::Puma::Const::PipeRequest
|
13
|
+
|
11
14
|
def initialize(launcher)
|
12
15
|
@launcher = launcher
|
13
16
|
@log_writer = launcher.log_writer
|
@@ -27,7 +30,7 @@ module Puma
|
|
27
30
|
def wakeup!
|
28
31
|
return unless @wakeup
|
29
32
|
|
30
|
-
@wakeup.write
|
33
|
+
@wakeup.write PIPE_WAKEUP unless @wakeup.closed?
|
31
34
|
|
32
35
|
rescue SystemCallError, IOError
|
33
36
|
Puma::Util.purge_interrupt_queue
|
@@ -70,12 +73,16 @@ module Puma
|
|
70
73
|
|
71
74
|
app = Puma::App::Status.new @launcher, token
|
72
75
|
|
73
|
-
# A Reactor is not created
|
76
|
+
# A Reactor is not created and nio4r is not loaded when 'queue_requests: false'
|
74
77
|
# Use `nil` for events, no hooks in control server
|
75
78
|
control = Puma::Server.new app, nil,
|
76
79
|
{ min_threads: 0, max_threads: 1, queue_requests: false, log_writer: @log_writer }
|
77
80
|
|
78
|
-
|
81
|
+
begin
|
82
|
+
control.binder.parse [str], nil, 'Starting control server'
|
83
|
+
rescue Errno::EADDRINUSE, Errno::EACCES => e
|
84
|
+
raise e, "Error: Control server address '#{str}' is already in use. Original error: #{e.message}"
|
85
|
+
end
|
79
86
|
|
80
87
|
control.run thread_name: 'ctl'
|
81
88
|
@control = control
|
@@ -87,7 +94,10 @@ module Puma
|
|
87
94
|
end
|
88
95
|
|
89
96
|
# @!attribute [r] ruby_engine
|
97
|
+
# @deprecated Use `RUBY_DESCRIPTION` instead
|
90
98
|
def ruby_engine
|
99
|
+
warn "Puma::Runner#ruby_engine is deprecated; use RUBY_DESCRIPTION instead. It will be removed in puma v7."
|
100
|
+
|
91
101
|
if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
|
92
102
|
"ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
|
93
103
|
else
|
@@ -105,7 +115,8 @@ module Puma
|
|
105
115
|
environment = @options[:environment]
|
106
116
|
|
107
117
|
log "Puma starting in #{mode} mode..."
|
108
|
-
log "* Puma version: #{Puma::Const::PUMA_VERSION} (
|
118
|
+
log "* Puma version: #{Puma::Const::PUMA_VERSION} (\"#{Puma::Const::CODE_NAME}\")"
|
119
|
+
log "* Ruby version: #{RUBY_DESCRIPTION}"
|
109
120
|
log "* Min threads: #{min_t}"
|
110
121
|
log "* Max threads: #{max_t}"
|
111
122
|
log "* Environment: #{environment}"
|
@@ -182,6 +193,10 @@ module Puma
|
|
182
193
|
end
|
183
194
|
end
|
184
195
|
|
196
|
+
def utc_iso8601(val)
|
197
|
+
"#{val.utc.strftime '%FT%T'}Z"
|
198
|
+
end
|
199
|
+
|
185
200
|
def stats
|
186
201
|
{
|
187
202
|
versions: {
|
@@ -194,5 +209,12 @@ module Puma
|
|
194
209
|
}
|
195
210
|
}
|
196
211
|
end
|
212
|
+
|
213
|
+
# this method call should always be guarded by `@log_writer.debug?`
|
214
|
+
def debug_loaded_extensions(str)
|
215
|
+
@log_writer.debug "────────────────────────────────── #{str}"
|
216
|
+
re_ext = /\.#{RbConfig::CONFIG['DLEXT']}\z/i
|
217
|
+
$LOADED_FEATURES.grep(re_ext).each { |f| @log_writer.debug(" #{f}") }
|
218
|
+
end
|
197
219
|
end
|
198
220
|
end
|