puma 7.2.0-java → 8.0.0-java
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 +45 -0
- data/README.md +1 -2
- data/docs/5.0-Upgrade.md +98 -0
- data/docs/6.0-Upgrade.md +56 -0
- data/docs/7.0-Upgrade.md +52 -0
- data/docs/8.0-Upgrade.md +100 -0
- data/docs/grpc.md +62 -0
- data/docs/images/favicon.svg +1 -0
- data/docs/images/running-puma.svg +1 -0
- data/docs/images/standard-logo.svg +1 -0
- data/docs/signals.md +1 -1
- data/ext/puma_http11/http11_parser.java.rl +51 -65
- data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +168 -104
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +71 -85
- data/lib/puma/cli.rb +1 -1
- data/lib/puma/client.rb +90 -66
- data/lib/puma/client_env.rb +171 -0
- data/lib/puma/cluster.rb +1 -1
- data/lib/puma/configuration.rb +69 -7
- data/lib/puma/const.rb +2 -2
- data/lib/puma/control_cli.rb +1 -1
- data/lib/puma/detect.rb +11 -0
- data/lib/puma/dsl.rb +74 -8
- data/lib/puma/launcher.rb +3 -4
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/{request.rb → response.rb} +15 -186
- data/lib/puma/server.rb +69 -34
- data/lib/puma/server_plugin_control.rb +32 -0
- data/lib/puma/thread_pool.rb +129 -23
- data/lib/rack/handler/puma.rb +1 -1
- metadata +14 -3
data/lib/puma/detect.rb
CHANGED
|
@@ -35,6 +35,13 @@ module Puma
|
|
|
35
35
|
IS_WINDOWS
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
BACKTRACE_SIGNAL =
|
|
39
|
+
if Signal.list.key?("INFO")
|
|
40
|
+
"SIGINFO"
|
|
41
|
+
elsif Signal.list.key?("PWR")
|
|
42
|
+
"SIGPWR"
|
|
43
|
+
end
|
|
44
|
+
|
|
38
45
|
# @version 5.0.0
|
|
39
46
|
def self.mri?
|
|
40
47
|
IS_MRI
|
|
@@ -44,4 +51,8 @@ module Puma
|
|
|
44
51
|
def self.forkable?
|
|
45
52
|
HAS_FORK
|
|
46
53
|
end
|
|
54
|
+
|
|
55
|
+
def self.backtrace_signal
|
|
56
|
+
BACKTRACE_SIGNAL
|
|
57
|
+
end
|
|
47
58
|
end
|
data/lib/puma/dsl.rb
CHANGED
|
@@ -155,7 +155,7 @@ module Puma
|
|
|
155
155
|
end
|
|
156
156
|
|
|
157
157
|
def default_host
|
|
158
|
-
@options[:default_host] || Configuration
|
|
158
|
+
@options[:default_host] || Configuration.default_tcp_host
|
|
159
159
|
end
|
|
160
160
|
|
|
161
161
|
def inject(&blk)
|
|
@@ -260,7 +260,8 @@ module Puma
|
|
|
260
260
|
# accepted protocols. Multiple urls can be bound to, calling +bind+ does
|
|
261
261
|
# not overwrite previous bindings.
|
|
262
262
|
#
|
|
263
|
-
# The default is "tcp://
|
|
263
|
+
# The default is "tcp://[::]:9292" when IPv6 interfaces are available,
|
|
264
|
+
# otherwise "tcp://0.0.0.0:9292".
|
|
264
265
|
#
|
|
265
266
|
# You can use query parameters within the url to specify options:
|
|
266
267
|
#
|
|
@@ -279,7 +280,7 @@ module Puma
|
|
|
279
280
|
# @example SSL cert for mutual TLS (mTLS)
|
|
280
281
|
# bind 'ssl://127.0.0.1:9292?key=key.key&cert=cert.pem&ca=ca.pem&verify_mode=force_peer'
|
|
281
282
|
# @example Disable optimization for low latency
|
|
282
|
-
# bind 'tcp://
|
|
283
|
+
# bind 'tcp://[::]:9292?low_latency=false'
|
|
283
284
|
# @example Socket permissions
|
|
284
285
|
# bind 'unix:///var/run/puma.sock?umask=0111'
|
|
285
286
|
#
|
|
@@ -349,7 +350,7 @@ module Puma
|
|
|
349
350
|
|
|
350
351
|
# Define how long persistent connections can be idle before Puma closes them.
|
|
351
352
|
#
|
|
352
|
-
# The default is
|
|
353
|
+
# The default is 65 seconds.
|
|
353
354
|
#
|
|
354
355
|
# @example
|
|
355
356
|
# persistent_timeout 30
|
|
@@ -595,6 +596,29 @@ module Puma
|
|
|
595
596
|
@options[:max_threads] = max
|
|
596
597
|
end
|
|
597
598
|
|
|
599
|
+
# Configure the max number of IO threads.
|
|
600
|
+
#
|
|
601
|
+
# When request handlers know the current requests will no longer use a significant amount
|
|
602
|
+
# of CPU, they can mark the current request as IO bound using <tt>env["puma.mark_as_io_bound"]</tt>.
|
|
603
|
+
#
|
|
604
|
+
# Threads marked as IO bound are allowed to go over the max thread limit.
|
|
605
|
+
#
|
|
606
|
+
# @example
|
|
607
|
+
# threads 5
|
|
608
|
+
# max_io_threads 5
|
|
609
|
+
#
|
|
610
|
+
# The above example allows for 5 regular threads and 5 IO threads to process requests concurrently.
|
|
611
|
+
# Any IO thread over the limit is counted as a regular thread, hence the above configuration also
|
|
612
|
+
# allows for 3 regular threads and 7 IO threads for example.
|
|
613
|
+
def max_io_threads(max)
|
|
614
|
+
max = Integer(max)
|
|
615
|
+
if max < 0
|
|
616
|
+
raise "The maximum number of IO threads (#{max}) must be a positive number"
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
@options[:max_io_threads] = max
|
|
620
|
+
end
|
|
621
|
+
|
|
598
622
|
# Instead of using +bind+ and manually constructing a URI like:
|
|
599
623
|
#
|
|
600
624
|
# bind 'ssl://127.0.0.1:9292?key=key_path&cert=cert_path'
|
|
@@ -727,6 +751,44 @@ module Puma
|
|
|
727
751
|
@options[:silence_fork_callback_warning] = true
|
|
728
752
|
end
|
|
729
753
|
|
|
754
|
+
# Code to run only in single mode.
|
|
755
|
+
# Runs after all config files are loaded.
|
|
756
|
+
#
|
|
757
|
+
# This can be called multiple times.
|
|
758
|
+
#
|
|
759
|
+
# @note Single mode only.
|
|
760
|
+
#
|
|
761
|
+
# @example
|
|
762
|
+
# single do
|
|
763
|
+
# silence_fork_callback_warning
|
|
764
|
+
# end
|
|
765
|
+
#
|
|
766
|
+
def single(&block)
|
|
767
|
+
raise ArgumentError, "A block must be provided to `single`" unless block
|
|
768
|
+
|
|
769
|
+
@options[:single] ||= []
|
|
770
|
+
@options[:single] << block
|
|
771
|
+
end
|
|
772
|
+
|
|
773
|
+
# Code to run only in cluster mode.
|
|
774
|
+
# Runs after all config files are loaded.
|
|
775
|
+
#
|
|
776
|
+
# This can be called multiple times.
|
|
777
|
+
#
|
|
778
|
+
# @note Cluster mode only.
|
|
779
|
+
#
|
|
780
|
+
# @example
|
|
781
|
+
# cluster do
|
|
782
|
+
# prune_bundler
|
|
783
|
+
# end
|
|
784
|
+
#
|
|
785
|
+
def cluster(&block)
|
|
786
|
+
raise ArgumentError, "A block must be provided to `cluster`" unless block
|
|
787
|
+
|
|
788
|
+
@options[:cluster] ||= []
|
|
789
|
+
@options[:cluster] << block
|
|
790
|
+
end
|
|
791
|
+
|
|
730
792
|
# Code to run immediately before master process
|
|
731
793
|
# forks workers (once on boot). These hooks can block if necessary
|
|
732
794
|
# to wait for background operations unknown to Puma to finish before
|
|
@@ -1040,6 +1102,7 @@ module Puma
|
|
|
1040
1102
|
# new Bundler context and thus can float around as the release
|
|
1041
1103
|
# dictates.
|
|
1042
1104
|
#
|
|
1105
|
+
# @note Cluster mode only.
|
|
1043
1106
|
# @note This is incompatible with +preload_app!+.
|
|
1044
1107
|
# @note This is only supported for RubyGems 2.2+
|
|
1045
1108
|
#
|
|
@@ -1145,7 +1208,7 @@ module Puma
|
|
|
1145
1208
|
|
|
1146
1209
|
# Change the default worker timeout for booting.
|
|
1147
1210
|
#
|
|
1148
|
-
# The default is
|
|
1211
|
+
# The default is 60 seconds.
|
|
1149
1212
|
#
|
|
1150
1213
|
# @note Cluster mode only.
|
|
1151
1214
|
#
|
|
@@ -1227,11 +1290,14 @@ module Puma
|
|
|
1227
1290
|
# threads will be written to $stdout. This can help figure
|
|
1228
1291
|
# out why shutdown is hanging.
|
|
1229
1292
|
#
|
|
1230
|
-
|
|
1231
|
-
|
|
1293
|
+
# If `on_force` is true, the backtraces will be written only
|
|
1294
|
+
# when the shutdown is forced i.e. not graceful.
|
|
1295
|
+
#
|
|
1296
|
+
# @see force_shutdown_after
|
|
1297
|
+
def shutdown_debug(val = true, on_force: false)
|
|
1298
|
+
@options[:shutdown_debug] = val && on_force ? :on_force : val
|
|
1232
1299
|
end
|
|
1233
1300
|
|
|
1234
|
-
|
|
1235
1301
|
# Maximum delay of worker accept loop.
|
|
1236
1302
|
#
|
|
1237
1303
|
# Attempts to route traffic to less-busy workers by causing a busy worker to delay
|
data/lib/puma/launcher.rb
CHANGED
|
@@ -476,8 +476,8 @@ module Puma
|
|
|
476
476
|
end
|
|
477
477
|
|
|
478
478
|
begin
|
|
479
|
-
|
|
480
|
-
Signal.trap
|
|
479
|
+
if Puma.backtrace_signal
|
|
480
|
+
Signal.trap Puma.backtrace_signal do
|
|
481
481
|
thread_status do |name, backtrace|
|
|
482
482
|
@log_writer.log(name)
|
|
483
483
|
@log_writer.log(backtrace.map { |bt| " #{bt}" })
|
|
@@ -485,8 +485,7 @@ module Puma
|
|
|
485
485
|
end
|
|
486
486
|
end
|
|
487
487
|
rescue Exception
|
|
488
|
-
|
|
489
|
-
# to see this constantly on Linux.
|
|
488
|
+
log "*** SIGINFO/SIGPWR not implemented, signal based backtrace unavailable!"
|
|
490
489
|
end
|
|
491
490
|
end
|
|
492
491
|
|
data/lib/puma/puma_http11.jar
CHANGED
|
Binary file
|
|
@@ -12,7 +12,7 @@ module Puma
|
|
|
12
12
|
# #handle_request, which is called in Server#process_client.
|
|
13
13
|
# @version 5.0.3
|
|
14
14
|
#
|
|
15
|
-
module
|
|
15
|
+
module Response # :nodoc:
|
|
16
16
|
|
|
17
17
|
# Single element array body: smaller bodies are written to io_buffer first,
|
|
18
18
|
# then a single write from io_buffer. Larger sizes are written separately.
|
|
@@ -46,10 +46,11 @@ module Puma
|
|
|
46
46
|
# elsewhere, i.e. the connection has been hijacked by the Rack application.
|
|
47
47
|
#
|
|
48
48
|
# Finally, it'll return +true+ on keep-alive connections.
|
|
49
|
+
# @param processor [Puma::ThreadPool::ProcessorThread]
|
|
49
50
|
# @param client [Puma::Client]
|
|
50
51
|
# @param requests [Integer]
|
|
51
52
|
# @return [:close, :keep_alive, :async]
|
|
52
|
-
def handle_request(client, requests)
|
|
53
|
+
def handle_request(processor, client, requests)
|
|
53
54
|
env = client.env
|
|
54
55
|
io_buffer = client.io_buffer
|
|
55
56
|
socket = client.io # io may be a MiniSSL::Socket
|
|
@@ -58,24 +59,6 @@ module Puma
|
|
|
58
59
|
|
|
59
60
|
return :close if closed_socket?(socket)
|
|
60
61
|
|
|
61
|
-
if client.http_content_length_limit_exceeded
|
|
62
|
-
return prepare_response(413, {}, ["Payload Too Large"], requests, client)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
normalize_env env, client
|
|
66
|
-
|
|
67
|
-
env[PUMA_SOCKET] = socket
|
|
68
|
-
|
|
69
|
-
if env[HTTPS_KEY] && socket.peercert
|
|
70
|
-
env[PUMA_PEERCERT] = socket.peercert
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
env[HIJACK_P] = true
|
|
74
|
-
env[HIJACK] = client.method :full_hijack
|
|
75
|
-
|
|
76
|
-
env[RACK_INPUT] = client.body
|
|
77
|
-
env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP
|
|
78
|
-
|
|
79
62
|
if @early_hints
|
|
80
63
|
env[EARLY_HINTS] = lambda { |headers|
|
|
81
64
|
begin
|
|
@@ -89,22 +72,11 @@ module Puma
|
|
|
89
72
|
}
|
|
90
73
|
end
|
|
91
74
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
# A rack extension. If the app writes #call'ables to this
|
|
95
|
-
# array, we will invoke them when the request is done.
|
|
96
|
-
#
|
|
97
|
-
env[RACK_AFTER_REPLY] ||= []
|
|
98
|
-
env[RACK_RESPONSE_FINISHED] ||= []
|
|
75
|
+
env["puma.mark_as_io_bound"] = -> { processor.mark_as_io_thread! }
|
|
99
76
|
|
|
100
77
|
begin
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
@app.call(env)
|
|
104
|
-
end
|
|
105
|
-
else
|
|
106
|
-
@log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
|
|
107
|
-
status, headers, app_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
|
|
78
|
+
status, headers, app_body = @thread_pool.with_force_shutdown do
|
|
79
|
+
@app.call(env)
|
|
108
80
|
end
|
|
109
81
|
|
|
110
82
|
# app_body needs to always be closed, hold value in case lowlevel_error
|
|
@@ -136,7 +108,6 @@ module Puma
|
|
|
136
108
|
prepare_response(status, headers, res_body, requests, client)
|
|
137
109
|
ensure
|
|
138
110
|
io_buffer.reset
|
|
139
|
-
uncork_socket client.io
|
|
140
111
|
app_body.close if app_body.respond_to? :close
|
|
141
112
|
client&.tempfile_close
|
|
142
113
|
if after_reply = env[RACK_AFTER_REPLY]
|
|
@@ -245,7 +216,8 @@ module Puma
|
|
|
245
216
|
|
|
246
217
|
io_buffer << LINE_END
|
|
247
218
|
fast_write_str socket, io_buffer.read_and_reset
|
|
248
|
-
|
|
219
|
+
|
|
220
|
+
uncork_socket socket.flush
|
|
249
221
|
return keep_alive ? :keep_alive : :close
|
|
250
222
|
end
|
|
251
223
|
else
|
|
@@ -264,34 +236,18 @@ module Puma
|
|
|
264
236
|
# response_hijack.call
|
|
265
237
|
if response_hijack
|
|
266
238
|
fast_write_str socket, io_buffer.read_and_reset
|
|
267
|
-
uncork_socket socket
|
|
239
|
+
uncork_socket socket.flush
|
|
268
240
|
response_hijack.call socket
|
|
269
241
|
return :async
|
|
270
242
|
end
|
|
271
243
|
|
|
272
244
|
fast_write_response socket, body, io_buffer, chunked, content_length.to_i
|
|
273
245
|
body.close if close_body
|
|
246
|
+
|
|
274
247
|
# if we're shutting down, close keep_alive connections
|
|
275
248
|
!shutting_down? && keep_alive ? :keep_alive : :close
|
|
276
249
|
end
|
|
277
250
|
|
|
278
|
-
HTTP_ON_VALUES = { "on" => true, HTTPS => true }
|
|
279
|
-
private_constant :HTTP_ON_VALUES
|
|
280
|
-
|
|
281
|
-
# @param env [Hash] see Puma::Client#env, from request
|
|
282
|
-
# @return [Puma::Const::PORT_443,Puma::Const::PORT_80]
|
|
283
|
-
#
|
|
284
|
-
def default_server_port(env)
|
|
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"
|
|
289
|
-
PORT_443
|
|
290
|
-
else
|
|
291
|
-
PORT_80
|
|
292
|
-
end
|
|
293
|
-
end
|
|
294
|
-
|
|
295
251
|
# Used to write 'early hints', 'no body' responses, 'hijacked' responses,
|
|
296
252
|
# and body segments (called by `fast_write_response`).
|
|
297
253
|
# Writes a string to a socket (normally `Client#io`) using `write_nonblock`.
|
|
@@ -406,6 +362,7 @@ module Puma
|
|
|
406
362
|
end
|
|
407
363
|
end
|
|
408
364
|
socket.flush
|
|
365
|
+
uncork_socket socket
|
|
409
366
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
|
410
367
|
raise ConnectionError, SOCKET_WRITE_ERR_MSG
|
|
411
368
|
rescue Errno::EPIPE, SystemCallError, IOError
|
|
@@ -414,85 +371,6 @@ module Puma
|
|
|
414
371
|
|
|
415
372
|
private :fast_write_str, :fast_write_response
|
|
416
373
|
|
|
417
|
-
# Given a Hash +env+ for the request read from +client+, add
|
|
418
|
-
# and fixup keys to comply with Rack's env guidelines.
|
|
419
|
-
# @param env [Hash] see Puma::Client#env, from request
|
|
420
|
-
# @param client [Puma::Client] only needed for Client#peerip
|
|
421
|
-
#
|
|
422
|
-
def normalize_env(env, client)
|
|
423
|
-
if host = env[HTTP_HOST]
|
|
424
|
-
# host can be a hostname, ipv4 or bracketed ipv6. Followed by an optional port.
|
|
425
|
-
if colon = host.rindex("]:") # IPV6 with port
|
|
426
|
-
env[SERVER_NAME] = host[0, colon+1]
|
|
427
|
-
env[SERVER_PORT] = host[colon+2, host.bytesize]
|
|
428
|
-
elsif !host.start_with?("[") && colon = host.index(":") # not hostname or IPV4 with port
|
|
429
|
-
env[SERVER_NAME] = host[0, colon]
|
|
430
|
-
env[SERVER_PORT] = host[colon+1, host.bytesize]
|
|
431
|
-
else
|
|
432
|
-
env[SERVER_NAME] = host
|
|
433
|
-
env[SERVER_PORT] = default_server_port(env)
|
|
434
|
-
end
|
|
435
|
-
else
|
|
436
|
-
env[SERVER_NAME] = LOCALHOST
|
|
437
|
-
env[SERVER_PORT] = default_server_port(env)
|
|
438
|
-
end
|
|
439
|
-
|
|
440
|
-
unless env[REQUEST_PATH]
|
|
441
|
-
# it might be a dumbass full host request header
|
|
442
|
-
uri = begin
|
|
443
|
-
URI.parse(env[REQUEST_URI])
|
|
444
|
-
rescue URI::InvalidURIError
|
|
445
|
-
raise Puma::HttpParserError
|
|
446
|
-
end
|
|
447
|
-
env[REQUEST_PATH] = uri.path
|
|
448
|
-
|
|
449
|
-
# A nil env value will cause a LintError (and fatal errors elsewhere),
|
|
450
|
-
# so only set the env value if there actually is a value.
|
|
451
|
-
env[QUERY_STRING] = uri.query if uri.query
|
|
452
|
-
end
|
|
453
|
-
|
|
454
|
-
env[PATH_INFO] = env[REQUEST_PATH].to_s # #to_s in case it's nil
|
|
455
|
-
|
|
456
|
-
# From https://www.ietf.org/rfc/rfc3875 :
|
|
457
|
-
# "Script authors should be aware that the REMOTE_ADDR and
|
|
458
|
-
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
|
459
|
-
# may not identify the ultimate source of the request.
|
|
460
|
-
# They identify the client for the immediate request to the
|
|
461
|
-
# server; that client may be a proxy, gateway, or other
|
|
462
|
-
# intermediary acting on behalf of the actual source client."
|
|
463
|
-
#
|
|
464
|
-
|
|
465
|
-
unless env.key?(REMOTE_ADDR)
|
|
466
|
-
begin
|
|
467
|
-
addr = client.peerip
|
|
468
|
-
rescue Errno::ENOTCONN
|
|
469
|
-
# Client disconnects can result in an inability to get the
|
|
470
|
-
# peeraddr from the socket; default to unspec.
|
|
471
|
-
if client.peer_family == Socket::AF_INET6
|
|
472
|
-
addr = UNSPECIFIED_IPV6
|
|
473
|
-
else
|
|
474
|
-
addr = UNSPECIFIED_IPV4
|
|
475
|
-
end
|
|
476
|
-
end
|
|
477
|
-
|
|
478
|
-
# Set unix socket addrs to localhost
|
|
479
|
-
if addr.empty?
|
|
480
|
-
if client.peer_family == Socket::AF_INET6
|
|
481
|
-
addr = LOCALHOST_IPV6
|
|
482
|
-
else
|
|
483
|
-
addr = LOCALHOST_IPV4
|
|
484
|
-
end
|
|
485
|
-
end
|
|
486
|
-
|
|
487
|
-
env[REMOTE_ADDR] = addr
|
|
488
|
-
end
|
|
489
|
-
|
|
490
|
-
# The legacy HTTP_VERSION header can be sent as a client header.
|
|
491
|
-
# Rack v4 may remove using HTTP_VERSION. If so, remove this line.
|
|
492
|
-
env[HTTP_VERSION] = env[SERVER_PROTOCOL] if @env_set_http_version
|
|
493
|
-
end
|
|
494
|
-
private :normalize_env
|
|
495
|
-
|
|
496
374
|
# @param header_key [#to_s]
|
|
497
375
|
# @return [Boolean]
|
|
498
376
|
#
|
|
@@ -506,56 +384,6 @@ module Puma
|
|
|
506
384
|
def illegal_header_value?(header_value)
|
|
507
385
|
!!(ILLEGAL_HEADER_VALUE_REGEX =~ header_value.to_s)
|
|
508
386
|
end
|
|
509
|
-
private :illegal_header_key?, :illegal_header_value?
|
|
510
|
-
|
|
511
|
-
# Fixup any headers with `,` in the name to have `_` now. We emit
|
|
512
|
-
# headers with `,` in them during the parse phase to avoid ambiguity
|
|
513
|
-
# with the `-` to `_` conversion for critical headers. But here for
|
|
514
|
-
# compatibility, we'll convert them back. This code is written to
|
|
515
|
-
# avoid allocation in the common case (ie there are no headers
|
|
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
|
-
#
|
|
522
|
-
# @param env [Hash] see Puma::Client#env, from request, modifies in place
|
|
523
|
-
# @version 5.0.3
|
|
524
|
-
#
|
|
525
|
-
def req_env_post_parse(env)
|
|
526
|
-
to_delete = nil
|
|
527
|
-
to_add = nil
|
|
528
|
-
|
|
529
|
-
env.each do |k,v|
|
|
530
|
-
if k.start_with?("HTTP_") && k.include?(",") && !UNMASKABLE_HEADERS.key?(k)
|
|
531
|
-
if to_delete
|
|
532
|
-
to_delete << k
|
|
533
|
-
else
|
|
534
|
-
to_delete = [k]
|
|
535
|
-
end
|
|
536
|
-
|
|
537
|
-
new_k = k.tr(",", "_")
|
|
538
|
-
if env.key?(new_k)
|
|
539
|
-
next
|
|
540
|
-
end
|
|
541
|
-
|
|
542
|
-
unless to_add
|
|
543
|
-
to_add = {}
|
|
544
|
-
end
|
|
545
|
-
|
|
546
|
-
to_add[new_k] = v
|
|
547
|
-
end
|
|
548
|
-
end
|
|
549
|
-
|
|
550
|
-
if to_delete # rubocop:disable Style/SafeNavigation
|
|
551
|
-
to_delete.each { |k| env.delete(k) }
|
|
552
|
-
end
|
|
553
|
-
|
|
554
|
-
if to_add
|
|
555
|
-
env.merge! to_add
|
|
556
|
-
end
|
|
557
|
-
end
|
|
558
|
-
private :req_env_post_parse
|
|
559
387
|
|
|
560
388
|
# Used in the lambda for env[ `Puma::Const::EARLY_HINTS` ]
|
|
561
389
|
# @param headers [Hash] the headers returned by the Rack application
|
|
@@ -652,7 +480,8 @@ module Puma
|
|
|
652
480
|
headers.each do |k, vs|
|
|
653
481
|
next if illegal_header_key?(k)
|
|
654
482
|
|
|
655
|
-
|
|
483
|
+
key = k.downcase
|
|
484
|
+
case key
|
|
656
485
|
when CONTENT_LENGTH2
|
|
657
486
|
next if illegal_header_value?(vs)
|
|
658
487
|
# nil.to_i is 0, nil&.to_i is nil
|
|
@@ -679,10 +508,10 @@ module Puma
|
|
|
679
508
|
if ary
|
|
680
509
|
ary.each do |v|
|
|
681
510
|
next if illegal_header_value?(v)
|
|
682
|
-
io_buffer.append
|
|
511
|
+
io_buffer.append key, colon, v, line_ending
|
|
683
512
|
end
|
|
684
513
|
else
|
|
685
|
-
io_buffer.append
|
|
514
|
+
io_buffer.append key, colon, line_ending
|
|
686
515
|
end
|
|
687
516
|
end
|
|
688
517
|
|
data/lib/puma/server.rb
CHANGED
|
@@ -11,7 +11,7 @@ require_relative 'reactor'
|
|
|
11
11
|
require_relative 'client'
|
|
12
12
|
require_relative 'binder'
|
|
13
13
|
require_relative 'util'
|
|
14
|
-
require_relative '
|
|
14
|
+
require_relative 'response'
|
|
15
15
|
require_relative 'configuration'
|
|
16
16
|
require_relative 'cluster_accept_loop_delay'
|
|
17
17
|
|
|
@@ -31,15 +31,15 @@ module Puma
|
|
|
31
31
|
# Each `Puma::Server` will have one reactor and one thread pool.
|
|
32
32
|
class Server
|
|
33
33
|
module FiberPerRequest
|
|
34
|
-
def handle_request(client, requests)
|
|
34
|
+
def handle_request(processor, client, requests)
|
|
35
35
|
Fiber.new do
|
|
36
36
|
super
|
|
37
37
|
end.resume
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
include
|
|
42
|
-
include
|
|
41
|
+
include Const
|
|
42
|
+
include Response
|
|
43
43
|
|
|
44
44
|
attr_reader :options
|
|
45
45
|
attr_reader :thread
|
|
@@ -259,7 +259,9 @@ module Puma
|
|
|
259
259
|
|
|
260
260
|
@status = :run
|
|
261
261
|
|
|
262
|
-
@thread_pool = ThreadPool.new(thread_name, options, server: self)
|
|
262
|
+
@thread_pool = ThreadPool.new(thread_name, options, server: self) do |processor, client|
|
|
263
|
+
process_client(processor, client)
|
|
264
|
+
end
|
|
263
265
|
|
|
264
266
|
if @queue_requests
|
|
265
267
|
@reactor = Reactor.new(@io_selector_backend) { |c|
|
|
@@ -304,7 +306,7 @@ module Puma
|
|
|
304
306
|
# The method checks to see if it has the full header and body with
|
|
305
307
|
# the `Puma::Client#try_to_finish` method. If the full request has been sent,
|
|
306
308
|
# then the request is passed to the ThreadPool (`@thread_pool << client`)
|
|
307
|
-
# so that a "
|
|
309
|
+
# so that a "processor thread" can pick up the request and begin to execute application logic.
|
|
308
310
|
# The Client is then removed from the reactor (return `true`).
|
|
309
311
|
#
|
|
310
312
|
# If a client object times out, a 408 response is written, its connection is closed,
|
|
@@ -401,6 +403,7 @@ module Puma
|
|
|
401
403
|
next
|
|
402
404
|
end
|
|
403
405
|
drain += 1 if shutting_down?
|
|
406
|
+
|
|
404
407
|
client = new_client(io, sock)
|
|
405
408
|
client.send(addr_send_name, addr_value) if addr_value
|
|
406
409
|
pool << client
|
|
@@ -444,7 +447,9 @@ module Puma
|
|
|
444
447
|
def new_client(io, sock)
|
|
445
448
|
client = Client.new(io, @binder.env(sock))
|
|
446
449
|
client.listener = sock
|
|
450
|
+
client.env_set_http_version = @env_set_http_version
|
|
447
451
|
client.http_content_length_limit = @http_content_length_limit
|
|
452
|
+
client.supported_http_methods = @supported_http_methods
|
|
448
453
|
client
|
|
449
454
|
end
|
|
450
455
|
|
|
@@ -470,14 +475,14 @@ module Puma
|
|
|
470
475
|
# Given a connection on +client+, handle the incoming requests,
|
|
471
476
|
# or queue the connection in the Reactor if no request is available.
|
|
472
477
|
#
|
|
473
|
-
# This method is called from a ThreadPool
|
|
478
|
+
# This method is called from a ThreadPool processor thread.
|
|
474
479
|
#
|
|
475
480
|
# This method supports HTTP Keep-Alive so it may, depending on if the client
|
|
476
481
|
# indicates that it supports keep alive, wait for another request before
|
|
477
482
|
# returning.
|
|
478
483
|
#
|
|
479
484
|
# Return true if one or more requests were processed.
|
|
480
|
-
def process_client(client)
|
|
485
|
+
def process_client(processor, client)
|
|
481
486
|
close_socket = true
|
|
482
487
|
|
|
483
488
|
requests = 0
|
|
@@ -500,7 +505,7 @@ module Puma
|
|
|
500
505
|
while can_loop
|
|
501
506
|
can_loop = false
|
|
502
507
|
@requests_count += 1
|
|
503
|
-
case handle_request(client, requests + 1)
|
|
508
|
+
case handle_request(processor, client, requests + 1)
|
|
504
509
|
when :close
|
|
505
510
|
when :async
|
|
506
511
|
close_socket = false
|
|
@@ -577,7 +582,7 @@ module Puma
|
|
|
577
582
|
lowlevel_error(e, client.env)
|
|
578
583
|
@log_writer.ssl_error e, client.io
|
|
579
584
|
when HttpParserError
|
|
580
|
-
response_to_error(client, requests, e, 400)
|
|
585
|
+
response_to_error(client, requests, e, client.error_status_code || 400)
|
|
581
586
|
@log_writer.parse_error e, client
|
|
582
587
|
when HttpParserError501
|
|
583
588
|
response_to_error(client, requests, e, 501)
|
|
@@ -610,7 +615,15 @@ module Puma
|
|
|
610
615
|
end
|
|
611
616
|
|
|
612
617
|
def response_to_error(client, requests, err, status_code)
|
|
613
|
-
|
|
618
|
+
# @todo remove sometime later
|
|
619
|
+
if status_code == 413
|
|
620
|
+
status = 413
|
|
621
|
+
res_body = ["Payload Too Large"]
|
|
622
|
+
headers = {}
|
|
623
|
+
headers[CONTENT_LENGTH2] = 17
|
|
624
|
+
else
|
|
625
|
+
status, headers, res_body = lowlevel_error(err, client.env, status_code)
|
|
626
|
+
end
|
|
614
627
|
prepare_response(status, headers, res_body, requests, client)
|
|
615
628
|
end
|
|
616
629
|
private :response_to_error
|
|
@@ -618,32 +631,11 @@ module Puma
|
|
|
618
631
|
# Wait for all outstanding requests to finish.
|
|
619
632
|
#
|
|
620
633
|
def graceful_shutdown
|
|
621
|
-
if options[:shutdown_debug]
|
|
622
|
-
threads = Thread.list
|
|
623
|
-
total = threads.size
|
|
624
|
-
|
|
625
|
-
pid = Process.pid
|
|
626
|
-
|
|
627
|
-
$stdout.syswrite "#{pid}: === Begin thread backtrace dump ===\n"
|
|
628
|
-
|
|
629
|
-
threads.each_with_index do |t,i|
|
|
630
|
-
$stdout.syswrite "#{pid}: Thread #{i+1}/#{total}: #{t.inspect}\n"
|
|
631
|
-
$stdout.syswrite "#{pid}: #{t.backtrace.join("\n#{pid}: ")}\n\n"
|
|
632
|
-
end
|
|
633
|
-
$stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
|
|
634
|
-
end
|
|
635
|
-
|
|
636
634
|
if @status != :restart
|
|
637
635
|
@binder.close
|
|
638
636
|
end
|
|
639
637
|
|
|
640
|
-
|
|
641
|
-
if timeout = options[:force_shutdown_after]
|
|
642
|
-
@thread_pool.shutdown timeout.to_f
|
|
643
|
-
else
|
|
644
|
-
@thread_pool.shutdown
|
|
645
|
-
end
|
|
646
|
-
end
|
|
638
|
+
@thread_pool.shutdown(options[:force_shutdown_after])
|
|
647
639
|
end
|
|
648
640
|
|
|
649
641
|
def notify_safely(message)
|
|
@@ -660,7 +652,7 @@ module Puma
|
|
|
660
652
|
end
|
|
661
653
|
private :notify_safely
|
|
662
654
|
|
|
663
|
-
# Stops the acceptor thread and then causes the
|
|
655
|
+
# Stops the acceptor thread and then causes the processor threads to finish
|
|
664
656
|
# off the request queue before finally exiting.
|
|
665
657
|
|
|
666
658
|
def stop(sync=false)
|
|
@@ -730,6 +722,49 @@ module Puma
|
|
|
730
722
|
@binder.add_unix_listener path, umask, mode, backlog
|
|
731
723
|
end
|
|
732
724
|
|
|
725
|
+
# Updates the minimum and maximum number of threads in the thread pool.
|
|
726
|
+
#
|
|
727
|
+
# This method allows dynamic adjustment of the thread pool size while the server
|
|
728
|
+
# is running. It validates the provided values and updates both the thread pool
|
|
729
|
+
# and the server's thread configuration.
|
|
730
|
+
#
|
|
731
|
+
# @param min [Integer] The minimum number of threads to maintain in the pool.
|
|
732
|
+
# Defaults to the current minimum if not specified. Must be greater than 0
|
|
733
|
+
# and less than or equal to max.
|
|
734
|
+
# @param max [Integer] The maximum number of threads allowed in the pool.
|
|
735
|
+
# Defaults to the current maximum if not specified. Must be greater than or
|
|
736
|
+
# equal to min.
|
|
737
|
+
#
|
|
738
|
+
# @return [void]
|
|
739
|
+
#
|
|
740
|
+
# @note If validation fails, a warning message is logged and no changes are made.
|
|
741
|
+
#
|
|
742
|
+
# @example Update both min and max threads
|
|
743
|
+
# server.update_thread_pool_min_max(min: 2, max: 8)
|
|
744
|
+
#
|
|
745
|
+
# @example Update only the minimum threads
|
|
746
|
+
# server.update_thread_pool_min_max(min: 4)
|
|
747
|
+
#
|
|
748
|
+
# @example Update only the maximum threads
|
|
749
|
+
# server.update_thread_pool_min_max(max: 16)
|
|
750
|
+
#
|
|
751
|
+
def update_thread_pool_min_max(min: @min_threads, max: @max_threads)
|
|
752
|
+
if min > max
|
|
753
|
+
@log_writer.log "`min' value cannot be greater than `max' value."
|
|
754
|
+
return
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
if min < 0
|
|
758
|
+
@log_writer.log "`min' value cannot be less than 0"
|
|
759
|
+
return
|
|
760
|
+
end
|
|
761
|
+
|
|
762
|
+
@thread_pool&.with_mutex do
|
|
763
|
+
@thread_pool.min, @thread_pool.max = min, max
|
|
764
|
+
@min_threads, @max_threads = min, max
|
|
765
|
+
end
|
|
766
|
+
end
|
|
767
|
+
|
|
733
768
|
# @!attribute [r] connected_ports
|
|
734
769
|
def connected_ports
|
|
735
770
|
@binder.connected_ports
|