puma 6.4.1 → 7.2.1
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 +407 -8
- data/README.md +109 -49
- data/docs/deployment.md +58 -23
- data/docs/fork_worker.md +11 -1
- data/docs/java_options.md +54 -0
- data/docs/jungle/README.md +1 -1
- data/docs/kubernetes.md +11 -16
- data/docs/plugins.md +6 -2
- data/docs/restart.md +2 -2
- data/docs/signals.md +21 -21
- data/docs/stats.md +11 -5
- data/docs/systemd.md +14 -5
- data/ext/puma_http11/extconf.rb +20 -32
- data/ext/puma_http11/mini_ssl.c +29 -9
- data/ext/puma_http11/org/jruby/puma/Http11.java +40 -9
- data/ext/puma_http11/puma_http11.c +125 -118
- data/lib/puma/app/status.rb +11 -3
- data/lib/puma/binder.rb +21 -11
- data/lib/puma/cli.rb +10 -8
- data/lib/puma/client.rb +183 -83
- data/lib/puma/cluster/worker.rb +24 -21
- data/lib/puma/cluster/worker_handle.rb +38 -8
- data/lib/puma/cluster.rb +73 -47
- data/lib/puma/cluster_accept_loop_delay.rb +91 -0
- data/lib/puma/commonlogger.rb +3 -3
- data/lib/puma/configuration.rb +131 -60
- data/lib/puma/const.rb +31 -12
- data/lib/puma/control_cli.rb +10 -6
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +411 -121
- data/lib/puma/error_logger.rb +7 -5
- data/lib/puma/events.rb +25 -10
- data/lib/puma/io_buffer.rb +8 -4
- data/lib/puma/jruby_restart.rb +0 -16
- data/lib/puma/launcher/bundle_pruner.rb +1 -1
- data/lib/puma/launcher.rb +73 -55
- data/lib/puma/log_writer.rb +9 -9
- data/lib/puma/minissl/context_builder.rb +1 -0
- data/lib/puma/minissl.rb +1 -1
- data/lib/puma/null_io.rb +26 -0
- data/lib/puma/plugin/systemd.rb +3 -3
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/reactor.rb +19 -13
- data/lib/puma/request.rb +71 -39
- data/lib/puma/runner.rb +15 -17
- data/lib/puma/sd_notify.rb +1 -4
- data/lib/puma/server.rb +134 -73
- data/lib/puma/single.rb +7 -4
- data/lib/puma/state_file.rb +3 -2
- data/lib/puma/thread_pool.rb +57 -80
- data/lib/puma/util.rb +0 -7
- data/lib/puma.rb +10 -0
- data/lib/rack/handler/puma.rb +10 -7
- data/tools/Dockerfile +15 -5
- metadata +14 -15
- data/ext/puma_http11/ext_help.h +0 -15
data/lib/puma/request.rb
CHANGED
|
@@ -36,25 +36,27 @@ module Puma
|
|
|
36
36
|
# Takes the request contained in +client+, invokes the Rack application to construct
|
|
37
37
|
# the response and writes it back to +client.io+.
|
|
38
38
|
#
|
|
39
|
-
# It'll return +
|
|
39
|
+
# It'll return +:close+ when the connection is closed, this doesn't mean
|
|
40
40
|
# that the response wasn't successful.
|
|
41
41
|
#
|
|
42
|
+
# It'll return +:keep_alive+ if the connection is a pipeline or keep-alive connection.
|
|
43
|
+
# Which may contain additional requests.
|
|
44
|
+
#
|
|
42
45
|
# It'll return +:async+ if the connection remains open but will be handled
|
|
43
46
|
# elsewhere, i.e. the connection has been hijacked by the Rack application.
|
|
44
47
|
#
|
|
45
48
|
# Finally, it'll return +true+ on keep-alive connections.
|
|
46
49
|
# @param client [Puma::Client]
|
|
47
50
|
# @param requests [Integer]
|
|
48
|
-
# @return [
|
|
49
|
-
#
|
|
51
|
+
# @return [:close, :keep_alive, :async]
|
|
50
52
|
def handle_request(client, requests)
|
|
51
53
|
env = client.env
|
|
52
54
|
io_buffer = client.io_buffer
|
|
53
55
|
socket = client.io # io may be a MiniSSL::Socket
|
|
54
56
|
app_body = nil
|
|
57
|
+
error = nil
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
return false if closed_socket?(socket)
|
|
59
|
+
return :close if closed_socket?(socket)
|
|
58
60
|
|
|
59
61
|
if client.http_content_length_limit_exceeded
|
|
60
62
|
return prepare_response(413, {}, ["Payload Too Large"], requests, client)
|
|
@@ -69,7 +71,7 @@ module Puma
|
|
|
69
71
|
end
|
|
70
72
|
|
|
71
73
|
env[HIJACK_P] = true
|
|
72
|
-
env[HIJACK] = client
|
|
74
|
+
env[HIJACK] = client.method :full_hijack
|
|
73
75
|
|
|
74
76
|
env[RACK_INPUT] = client.body
|
|
75
77
|
env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP
|
|
@@ -93,6 +95,7 @@ module Puma
|
|
|
93
95
|
# array, we will invoke them when the request is done.
|
|
94
96
|
#
|
|
95
97
|
env[RACK_AFTER_REPLY] ||= []
|
|
98
|
+
env[RACK_RESPONSE_FINISHED] ||= []
|
|
96
99
|
|
|
97
100
|
begin
|
|
98
101
|
if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
|
|
@@ -120,28 +123,41 @@ module Puma
|
|
|
120
123
|
|
|
121
124
|
return :async
|
|
122
125
|
end
|
|
123
|
-
rescue ThreadPool::ForceShutdown =>
|
|
124
|
-
@log_writer.unknown_error
|
|
126
|
+
rescue ThreadPool::ForceShutdown => error
|
|
127
|
+
@log_writer.unknown_error error, client, "Rack app"
|
|
125
128
|
@log_writer.log "Detected force shutdown of a thread"
|
|
126
129
|
|
|
127
|
-
status, headers, res_body = lowlevel_error(
|
|
128
|
-
rescue Exception =>
|
|
129
|
-
@log_writer.unknown_error
|
|
130
|
+
status, headers, res_body = lowlevel_error(error, env, 503)
|
|
131
|
+
rescue Exception => error
|
|
132
|
+
@log_writer.unknown_error error, client, "Rack app"
|
|
130
133
|
|
|
131
|
-
status, headers, res_body = lowlevel_error(
|
|
134
|
+
status, headers, res_body = lowlevel_error(error, env, 500)
|
|
132
135
|
end
|
|
133
136
|
prepare_response(status, headers, res_body, requests, client)
|
|
134
137
|
ensure
|
|
135
138
|
io_buffer.reset
|
|
136
139
|
uncork_socket client.io
|
|
137
140
|
app_body.close if app_body.respond_to? :close
|
|
138
|
-
client
|
|
139
|
-
after_reply = env[RACK_AFTER_REPLY]
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
client&.tempfile_close
|
|
142
|
+
if after_reply = env[RACK_AFTER_REPLY]
|
|
143
|
+
after_reply.each do |o|
|
|
144
|
+
begin
|
|
145
|
+
o.call
|
|
146
|
+
rescue StandardError => e
|
|
147
|
+
@log_writer.debug_error e
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
if response_finished = env[RACK_RESPONSE_FINISHED]
|
|
153
|
+
response_finished.reverse_each do |o|
|
|
154
|
+
begin
|
|
155
|
+
o.call(env, status, headers, error)
|
|
156
|
+
rescue StandardError => e
|
|
157
|
+
@log_writer.debug_error e
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
145
161
|
end
|
|
146
162
|
|
|
147
163
|
# Assembles the headers and prepares the body for actually sending the
|
|
@@ -153,21 +169,16 @@ module Puma
|
|
|
153
169
|
# a call to `Server#lowlevel_error`
|
|
154
170
|
# @param requests [Integer] number of inline requests handled
|
|
155
171
|
# @param client [Puma::Client]
|
|
156
|
-
# @return [
|
|
172
|
+
# @return [:close, :keep_alive, :async]
|
|
157
173
|
def prepare_response(status, headers, res_body, requests, client)
|
|
158
174
|
env = client.env
|
|
159
175
|
socket = client.io
|
|
160
176
|
io_buffer = client.io_buffer
|
|
161
177
|
|
|
162
|
-
return
|
|
178
|
+
return :close if closed_socket?(socket)
|
|
163
179
|
|
|
164
180
|
# Close the connection after a reasonable number of inline requests
|
|
165
|
-
|
|
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)
|
|
181
|
+
force_keep_alive = @enable_keep_alives && client.requests_served < @max_keep_alive
|
|
171
182
|
|
|
172
183
|
resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
|
|
173
184
|
|
|
@@ -187,7 +198,8 @@ module Puma
|
|
|
187
198
|
elsif res_body.is_a?(File) && res_body.respond_to?(:size)
|
|
188
199
|
body = res_body
|
|
189
200
|
content_length = body.size
|
|
190
|
-
elsif res_body.respond_to?(:to_path) &&
|
|
201
|
+
elsif res_body.respond_to?(:to_path) && (fn = res_body.to_path) &&
|
|
202
|
+
File.readable?(fn)
|
|
191
203
|
body = File.open fn, 'rb'
|
|
192
204
|
content_length = body.size
|
|
193
205
|
close_body = true
|
|
@@ -195,7 +207,7 @@ module Puma
|
|
|
195
207
|
body = res_body
|
|
196
208
|
end
|
|
197
209
|
elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) &&
|
|
198
|
-
File.readable?(fn = res_body.to_path)
|
|
210
|
+
(fn = res_body.to_path) && File.readable?(fn = res_body.to_path)
|
|
199
211
|
body = File.open fn, 'rb'
|
|
200
212
|
content_length = body.size
|
|
201
213
|
close_body = true
|
|
@@ -234,7 +246,7 @@ module Puma
|
|
|
234
246
|
io_buffer << LINE_END
|
|
235
247
|
fast_write_str socket, io_buffer.read_and_reset
|
|
236
248
|
socket.flush
|
|
237
|
-
return keep_alive
|
|
249
|
+
return keep_alive ? :keep_alive : :close
|
|
238
250
|
end
|
|
239
251
|
else
|
|
240
252
|
if content_length
|
|
@@ -259,14 +271,21 @@ module Puma
|
|
|
259
271
|
|
|
260
272
|
fast_write_response socket, body, io_buffer, chunked, content_length.to_i
|
|
261
273
|
body.close if close_body
|
|
262
|
-
keep_alive
|
|
274
|
+
# if we're shutting down, close keep_alive connections
|
|
275
|
+
!shutting_down? && keep_alive ? :keep_alive : :close
|
|
263
276
|
end
|
|
264
277
|
|
|
278
|
+
HTTP_ON_VALUES = { "on" => true, HTTPS => true }
|
|
279
|
+
private_constant :HTTP_ON_VALUES
|
|
280
|
+
|
|
265
281
|
# @param env [Hash] see Puma::Client#env, from request
|
|
266
282
|
# @return [Puma::Const::PORT_443,Puma::Const::PORT_80]
|
|
267
283
|
#
|
|
268
284
|
def default_server_port(env)
|
|
269
|
-
if [
|
|
285
|
+
if HTTP_ON_VALUES[env[HTTPS_KEY]] ||
|
|
286
|
+
env[HTTP_X_FORWARDED_PROTO]&.start_with?(HTTPS) ||
|
|
287
|
+
env[HTTP_X_FORWARDED_SCHEME] == HTTPS ||
|
|
288
|
+
env[HTTP_X_FORWARDED_SSL] == "on"
|
|
270
289
|
PORT_443
|
|
271
290
|
else
|
|
272
291
|
PORT_80
|
|
@@ -470,7 +489,7 @@ module Puma
|
|
|
470
489
|
|
|
471
490
|
# The legacy HTTP_VERSION header can be sent as a client header.
|
|
472
491
|
# Rack v4 may remove using HTTP_VERSION. If so, remove this line.
|
|
473
|
-
env[HTTP_VERSION] = env[SERVER_PROTOCOL]
|
|
492
|
+
env[HTTP_VERSION] = env[SERVER_PROTOCOL] if @env_set_http_version
|
|
474
493
|
end
|
|
475
494
|
private :normalize_env
|
|
476
495
|
|
|
@@ -495,6 +514,11 @@ module Puma
|
|
|
495
514
|
# compatibility, we'll convert them back. This code is written to
|
|
496
515
|
# avoid allocation in the common case (ie there are no headers
|
|
497
516
|
# with `,` in their names), that's why it has the extra conditionals.
|
|
517
|
+
#
|
|
518
|
+
# @note If a normalized version of a `,` header already exists, we ignore
|
|
519
|
+
# the `,` version. This prevents clobbering headers managed by proxies
|
|
520
|
+
# but not by clients (Like X-Forwarded-For).
|
|
521
|
+
#
|
|
498
522
|
# @param env [Hash] see Puma::Client#env, from request, modifies in place
|
|
499
523
|
# @version 5.0.3
|
|
500
524
|
#
|
|
@@ -503,23 +527,31 @@ module Puma
|
|
|
503
527
|
to_add = nil
|
|
504
528
|
|
|
505
529
|
env.each do |k,v|
|
|
506
|
-
if k.start_with?("HTTP_") && k.include?(",") && k
|
|
530
|
+
if k.start_with?("HTTP_") && k.include?(",") && !UNMASKABLE_HEADERS.key?(k)
|
|
507
531
|
if to_delete
|
|
508
532
|
to_delete << k
|
|
509
533
|
else
|
|
510
534
|
to_delete = [k]
|
|
511
535
|
end
|
|
512
536
|
|
|
537
|
+
new_k = k.tr(",", "_")
|
|
538
|
+
if env.key?(new_k)
|
|
539
|
+
next
|
|
540
|
+
end
|
|
541
|
+
|
|
513
542
|
unless to_add
|
|
514
543
|
to_add = {}
|
|
515
544
|
end
|
|
516
545
|
|
|
517
|
-
to_add[
|
|
546
|
+
to_add[new_k] = v
|
|
518
547
|
end
|
|
519
548
|
end
|
|
520
549
|
|
|
521
|
-
if to_delete
|
|
550
|
+
if to_delete # rubocop:disable Style/SafeNavigation
|
|
522
551
|
to_delete.each { |k| env.delete(k) }
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
if to_add
|
|
523
555
|
env.merge! to_add
|
|
524
556
|
end
|
|
525
557
|
end
|
|
@@ -564,7 +596,7 @@ module Puma
|
|
|
564
596
|
# response body
|
|
565
597
|
# @param io_buffer [Puma::IOBuffer] modified inn place
|
|
566
598
|
# @param force_keep_alive [Boolean] 'anded' with keep_alive, based on system
|
|
567
|
-
# status and `@
|
|
599
|
+
# status and `@max_keep_alive`
|
|
568
600
|
# @return [Hash] resp_info
|
|
569
601
|
# @version 5.0.3
|
|
570
602
|
#
|
|
@@ -647,10 +679,10 @@ module Puma
|
|
|
647
679
|
if ary
|
|
648
680
|
ary.each do |v|
|
|
649
681
|
next if illegal_header_value?(v)
|
|
650
|
-
io_buffer.append k, colon, v, line_ending
|
|
682
|
+
io_buffer.append k.downcase, colon, v, line_ending
|
|
651
683
|
end
|
|
652
684
|
else
|
|
653
|
-
io_buffer.append k, colon, line_ending
|
|
685
|
+
io_buffer.append k.downcase, colon, line_ending
|
|
654
686
|
end
|
|
655
687
|
end
|
|
656
688
|
|
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,10 +30,9 @@ 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
|
-
Puma::Util.purge_interrupt_queue
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
def development?
|
|
@@ -68,7 +70,7 @@ module Puma
|
|
|
68
70
|
token = nil if token.empty? || token == 'none'
|
|
69
71
|
end
|
|
70
72
|
|
|
71
|
-
app = Puma::App::Status.new @launcher, token
|
|
73
|
+
app = Puma::App::Status.new @launcher, token: token, data_only: @options[:control_data_only]
|
|
72
74
|
|
|
73
75
|
# A Reactor is not created and nio4r is not loaded when 'queue_requests: false'
|
|
74
76
|
# Use `nil` for events, no hooks in control server
|
|
@@ -90,26 +92,14 @@ module Puma
|
|
|
90
92
|
@control.binder.close_listeners if @control
|
|
91
93
|
end
|
|
92
94
|
|
|
93
|
-
# @!attribute [r] ruby_engine
|
|
94
|
-
def ruby_engine
|
|
95
|
-
if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
|
|
96
|
-
"ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
|
|
97
|
-
else
|
|
98
|
-
if defined?(RUBY_ENGINE_VERSION)
|
|
99
|
-
"#{RUBY_ENGINE} #{RUBY_ENGINE_VERSION} - ruby #{RUBY_VERSION}"
|
|
100
|
-
else
|
|
101
|
-
"#{RUBY_ENGINE} #{RUBY_VERSION}"
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
|
|
106
95
|
def output_header(mode)
|
|
107
96
|
min_t = @options[:min_threads]
|
|
108
97
|
max_t = @options[:max_threads]
|
|
109
98
|
environment = @options[:environment]
|
|
110
99
|
|
|
111
100
|
log "Puma starting in #{mode} mode..."
|
|
112
|
-
log "* Puma version: #{Puma::Const::PUMA_VERSION} (
|
|
101
|
+
log "* Puma version: #{Puma::Const::PUMA_VERSION} (\"#{Puma::Const::CODE_NAME}\")"
|
|
102
|
+
log "* Ruby version: #{RUBY_DESCRIPTION}"
|
|
113
103
|
log "* Min threads: #{min_t}"
|
|
114
104
|
log "* Max threads: #{max_t}"
|
|
115
105
|
log "* Environment: #{environment}"
|
|
@@ -121,6 +111,14 @@ module Puma
|
|
|
121
111
|
end
|
|
122
112
|
end
|
|
123
113
|
|
|
114
|
+
def warn_ruby_mn_threads
|
|
115
|
+
return if !ENV.key?('RUBY_MN_THREADS')
|
|
116
|
+
|
|
117
|
+
log "! WARNING: Detected `RUBY_MN_THREADS=#{ENV['RUBY_MN_THREADS']}`"
|
|
118
|
+
log "! This setting is known to cause performance regressions with Puma."
|
|
119
|
+
log "! Consider disabling this environment variable: https://github.com/puma/puma/issues/3720"
|
|
120
|
+
end
|
|
121
|
+
|
|
124
122
|
def redirected_io?
|
|
125
123
|
@options[:redirect_stdout] || @options[:redirect_stderr]
|
|
126
124
|
end
|
data/lib/puma/sd_notify.rb
CHANGED
|
@@ -137,10 +137,7 @@ module Puma
|
|
|
137
137
|
ENV.delete("NOTIFY_SOCKET") if unset_env
|
|
138
138
|
|
|
139
139
|
begin
|
|
140
|
-
Addrinfo.unix(sock, :DGRAM).connect
|
|
141
|
-
s.close_on_exec = true
|
|
142
|
-
s.write(state)
|
|
143
|
-
end
|
|
140
|
+
Addrinfo.unix(sock, :DGRAM).connect { |s| s.write state }
|
|
144
141
|
rescue StandardError => e
|
|
145
142
|
raise NotifyError, "#{e.class}: #{e.message}", e.backtrace
|
|
146
143
|
end
|