puma 5.4.0 → 5.5.1
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 +40 -2
- data/README.md +42 -6
- data/docs/architecture.md +49 -16
- data/docs/compile_options.md +4 -2
- data/docs/deployment.md +53 -52
- data/docs/plugins.md +15 -15
- data/docs/rails_dev_mode.md +2 -3
- data/docs/restart.md +6 -6
- data/docs/signals.md +10 -10
- data/docs/stats.md +8 -8
- data/docs/systemd.md +64 -67
- data/ext/puma_http11/http11_parser.c +19 -12
- data/ext/puma_http11/http11_parser_common.rl +1 -1
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +34 -34
- data/lib/puma/binder.rb +29 -1
- data/lib/puma/cli.rb +5 -0
- data/lib/puma/client.rb +45 -3
- data/lib/puma/cluster/worker.rb +2 -12
- data/lib/puma/cluster.rb +0 -10
- data/lib/puma/configuration.rb +1 -1
- data/lib/puma/const.rb +4 -2
- data/lib/puma/control_cli.rb +1 -1
- data/lib/puma/dsl.rb +13 -3
- data/lib/puma/launcher.rb +2 -0
- data/lib/puma/minissl.rb +6 -21
- data/lib/puma/runner.rb +19 -6
- data/lib/puma/server.rb +14 -7
- data/lib/puma/util.rb +7 -0
- metadata +5 -5
data/lib/puma/client.rb
CHANGED
@@ -56,6 +56,7 @@ module Puma
|
|
56
56
|
@parser = HttpParser.new
|
57
57
|
@parsed_bytes = 0
|
58
58
|
@read_header = true
|
59
|
+
@read_proxy = false
|
59
60
|
@ready = false
|
60
61
|
|
61
62
|
@body = nil
|
@@ -71,6 +72,7 @@ module Puma
|
|
71
72
|
@peerip = nil
|
72
73
|
@listener = nil
|
73
74
|
@remote_addr_header = nil
|
75
|
+
@expect_proxy_proto = false
|
74
76
|
|
75
77
|
@body_remain = 0
|
76
78
|
|
@@ -106,7 +108,7 @@ module Puma
|
|
106
108
|
|
107
109
|
# @!attribute [r] in_data_phase
|
108
110
|
def in_data_phase
|
109
|
-
|
111
|
+
!(@read_header || @read_proxy)
|
110
112
|
end
|
111
113
|
|
112
114
|
def set_timeout(val)
|
@@ -121,6 +123,7 @@ module Puma
|
|
121
123
|
def reset(fast_check=true)
|
122
124
|
@parser.reset
|
123
125
|
@read_header = true
|
126
|
+
@read_proxy = !!@expect_proxy_proto
|
124
127
|
@env = @proto_env.dup
|
125
128
|
@body = nil
|
126
129
|
@tempfile = nil
|
@@ -131,6 +134,8 @@ module Puma
|
|
131
134
|
@in_last_chunk = false
|
132
135
|
|
133
136
|
if @buffer
|
137
|
+
return false unless try_to_parse_proxy_protocol
|
138
|
+
|
134
139
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
135
140
|
|
136
141
|
if @parser.finished?
|
@@ -157,12 +162,36 @@ module Puma
|
|
157
162
|
begin
|
158
163
|
@io.close
|
159
164
|
rescue IOError
|
160
|
-
|
165
|
+
Puma::Util.purge_interrupt_queue
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# If necessary, read the PROXY protocol from the buffer. Returns
|
170
|
+
# false if more data is needed.
|
171
|
+
def try_to_parse_proxy_protocol
|
172
|
+
if @read_proxy
|
173
|
+
if @expect_proxy_proto == :v1
|
174
|
+
if @buffer.include? "\r\n"
|
175
|
+
if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
|
176
|
+
if md[1]
|
177
|
+
@peerip = md[1].split(" ")[0]
|
178
|
+
end
|
179
|
+
@buffer = md.post_match
|
180
|
+
end
|
181
|
+
# if the buffer has a \r\n but doesn't have a PROXY protocol
|
182
|
+
# request, this is just HTTP from a non-PROXY client; move on
|
183
|
+
@read_proxy = false
|
184
|
+
return @buffer.size > 0
|
185
|
+
else
|
186
|
+
return false
|
187
|
+
end
|
188
|
+
end
|
161
189
|
end
|
190
|
+
true
|
162
191
|
end
|
163
192
|
|
164
193
|
def try_to_finish
|
165
|
-
return read_body
|
194
|
+
return read_body if in_data_phase
|
166
195
|
|
167
196
|
begin
|
168
197
|
data = @io.read_nonblock(CHUNK_SIZE)
|
@@ -187,6 +216,8 @@ module Puma
|
|
187
216
|
@buffer = data
|
188
217
|
end
|
189
218
|
|
219
|
+
return false unless try_to_parse_proxy_protocol
|
220
|
+
|
190
221
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
191
222
|
|
192
223
|
if @parser.finished?
|
@@ -243,6 +274,17 @@ module Puma
|
|
243
274
|
@parsed_bytes == 0
|
244
275
|
end
|
245
276
|
|
277
|
+
def expect_proxy_proto=(val)
|
278
|
+
if val
|
279
|
+
if @read_header
|
280
|
+
@read_proxy = true
|
281
|
+
end
|
282
|
+
else
|
283
|
+
@read_proxy = false
|
284
|
+
end
|
285
|
+
@expect_proxy_proto = val
|
286
|
+
end
|
287
|
+
|
246
288
|
private
|
247
289
|
|
248
290
|
def setup_body
|
data/lib/puma/cluster/worker.rb
CHANGED
@@ -106,7 +106,7 @@ module Puma
|
|
106
106
|
begin
|
107
107
|
@worker_write << "b#{Process.pid}:#{index}\n"
|
108
108
|
rescue SystemCallError, IOError
|
109
|
-
|
109
|
+
Puma::Util.purge_interrupt_queue
|
110
110
|
STDERR.puts "Master seems to have exited, exiting."
|
111
111
|
return
|
112
112
|
end
|
@@ -127,7 +127,7 @@ module Puma
|
|
127
127
|
payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m}, "requests_count": #{rc} }\n!
|
128
128
|
io << payload
|
129
129
|
rescue IOError
|
130
|
-
|
130
|
+
Puma::Util.purge_interrupt_queue
|
131
131
|
break
|
132
132
|
end
|
133
133
|
sleep Const::WORKER_CHECK_INTERVAL
|
@@ -168,16 +168,6 @@ module Puma
|
|
168
168
|
@launcher.config.run_hooks :after_worker_fork, idx, @launcher.events
|
169
169
|
pid
|
170
170
|
end
|
171
|
-
|
172
|
-
def wakeup!
|
173
|
-
return unless @wakeup
|
174
|
-
|
175
|
-
begin
|
176
|
-
@wakeup.write "!" unless @wakeup.closed?
|
177
|
-
rescue SystemCallError, IOError
|
178
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
179
|
-
end
|
180
|
-
end
|
181
171
|
end
|
182
172
|
end
|
183
173
|
end
|
data/lib/puma/cluster.rb
CHANGED
@@ -164,16 +164,6 @@ module Puma
|
|
164
164
|
].compact.min
|
165
165
|
end
|
166
166
|
|
167
|
-
def wakeup!
|
168
|
-
return unless @wakeup
|
169
|
-
|
170
|
-
begin
|
171
|
-
@wakeup.write "!" unless @wakeup.closed?
|
172
|
-
rescue SystemCallError, IOError
|
173
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
167
|
def worker(index, master)
|
178
168
|
@workers = []
|
179
169
|
|
data/lib/puma/configuration.rb
CHANGED
@@ -200,7 +200,7 @@ module Puma
|
|
200
200
|
:worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
|
201
201
|
:remote_address => :socket,
|
202
202
|
:tag => method(:infer_tag),
|
203
|
-
:environment => -> { ENV['RACK_ENV'] || ENV['RAILS_ENV'] ||
|
203
|
+
:environment => -> { ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development' },
|
204
204
|
:rackup => DefaultRackup,
|
205
205
|
:logger => STDOUT,
|
206
206
|
:persistent_timeout => Const::PERSISTENT_TIMEOUT,
|
data/lib/puma/const.rb
CHANGED
@@ -100,8 +100,8 @@ module Puma
|
|
100
100
|
# too taxing on performance.
|
101
101
|
module Const
|
102
102
|
|
103
|
-
PUMA_VERSION = VERSION = "5.
|
104
|
-
CODE_NAME = "
|
103
|
+
PUMA_VERSION = VERSION = "5.5.1".freeze
|
104
|
+
CODE_NAME = "Zawgyi".freeze
|
105
105
|
|
106
106
|
PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
|
107
107
|
|
@@ -247,5 +247,7 @@ module Puma
|
|
247
247
|
|
248
248
|
# Banned keys of response header
|
249
249
|
BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
|
250
|
+
|
251
|
+
PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
|
250
252
|
end
|
251
253
|
end
|
data/lib/puma/control_cli.rb
CHANGED
data/lib/puma/dsl.rb
CHANGED
@@ -585,7 +585,7 @@ module Puma
|
|
585
585
|
# end
|
586
586
|
def after_worker_fork(&block)
|
587
587
|
@options[:after_worker_fork] ||= []
|
588
|
-
@options[:after_worker_fork]
|
588
|
+
@options[:after_worker_fork] << block
|
589
589
|
end
|
590
590
|
|
591
591
|
alias_method :after_worker_boot, :after_worker_fork
|
@@ -818,7 +818,7 @@ module Puma
|
|
818
818
|
# a kernel syscall is required which for very fast rack handlers
|
819
819
|
# slows down the handling significantly.
|
820
820
|
#
|
821
|
-
# There are
|
821
|
+
# There are 5 possible values:
|
822
822
|
#
|
823
823
|
# 1. **:socket** (the default) - read the peername from the socket using the
|
824
824
|
# syscall. This is the normal behavior.
|
@@ -828,7 +828,10 @@ module Puma
|
|
828
828
|
# `set_remote_address header: "X-Real-IP"`.
|
829
829
|
# Only the first word (as separated by spaces or comma) is used, allowing
|
830
830
|
# headers such as X-Forwarded-For to be used as well.
|
831
|
-
# 4.
|
831
|
+
# 4. **proxy_protocol: :v1**- set the remote address to the value read from the
|
832
|
+
# HAproxy PROXY protocol, version 1. If the request does not have the PROXY
|
833
|
+
# protocol attached to it, will fall back to :socket
|
834
|
+
# 5. **\<Any string\>** - this allows you to hardcode remote address to any value
|
832
835
|
# you wish. Because Puma never uses this field anyway, it's format is
|
833
836
|
# entirely in your hands.
|
834
837
|
#
|
@@ -846,6 +849,13 @@ module Puma
|
|
846
849
|
if hdr = val[:header]
|
847
850
|
@options[:remote_address] = :header
|
848
851
|
@options[:remote_address_header] = "HTTP_" + hdr.upcase.tr("-", "_")
|
852
|
+
elsif protocol_version = val[:proxy_protocol]
|
853
|
+
@options[:remote_address] = :proxy_protocol
|
854
|
+
protocol_version = protocol_version.downcase.to_sym
|
855
|
+
unless [:v1].include?(protocol_version)
|
856
|
+
raise "Invalid value for proxy_protocol - #{protocol_version.inspect}"
|
857
|
+
end
|
858
|
+
@options[:remote_address_proxy_protocol] = protocol_version
|
849
859
|
else
|
850
860
|
raise "Invalid value for set_remote_address - #{val.inspect}"
|
851
861
|
end
|
data/lib/puma/launcher.rb
CHANGED
@@ -319,10 +319,12 @@ module Puma
|
|
319
319
|
log '* Pruning Bundler environment'
|
320
320
|
home = ENV['GEM_HOME']
|
321
321
|
bundle_gemfile = Bundler.original_env['BUNDLE_GEMFILE']
|
322
|
+
bundle_app_config = Bundler.original_env['BUNDLE_APP_CONFIG']
|
322
323
|
with_unbundled_env do
|
323
324
|
ENV['GEM_HOME'] = home
|
324
325
|
ENV['BUNDLE_GEMFILE'] = bundle_gemfile
|
325
326
|
ENV['PUMA_BUNDLER_PRUNED'] = '1'
|
327
|
+
ENV["BUNDLE_APP_CONFIG"] = bundle_app_config
|
326
328
|
args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':')] + @original_argv
|
327
329
|
# Ruby 2.0+ defaults to true which breaks socket activation
|
328
330
|
args += [{:close_others => false}]
|
data/lib/puma/minissl.rb
CHANGED
@@ -161,30 +161,15 @@ module Puma
|
|
161
161
|
@socket.flush
|
162
162
|
end
|
163
163
|
|
164
|
-
def read_and_drop(timeout = 1)
|
165
|
-
return :timeout unless @socket.wait_readable(timeout)
|
166
|
-
case @socket.read_nonblock(1024, exception: false)
|
167
|
-
when nil
|
168
|
-
:eof
|
169
|
-
when :wait_readable
|
170
|
-
:eagain
|
171
|
-
else
|
172
|
-
:drop
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
def should_drop_bytes?
|
177
|
-
@engine.init? || !@engine.shutdown
|
178
|
-
end
|
179
|
-
|
180
164
|
def close
|
181
165
|
begin
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
166
|
+
unless @engine.shutdown
|
167
|
+
while alert_data = @engine.extract
|
168
|
+
@socket.write alert_data
|
169
|
+
end
|
170
|
+
end
|
186
171
|
rescue IOError, SystemCallError
|
187
|
-
|
172
|
+
Puma::Util.purge_interrupt_queue
|
188
173
|
# nothing
|
189
174
|
ensure
|
190
175
|
@socket.close
|
data/lib/puma/runner.rb
CHANGED
@@ -15,6 +15,16 @@ module Puma
|
|
15
15
|
@app = nil
|
16
16
|
@control = nil
|
17
17
|
@started_at = Time.now
|
18
|
+
@wakeup = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def wakeup!
|
22
|
+
return unless @wakeup
|
23
|
+
|
24
|
+
@wakeup.write "!" unless @wakeup.closed?
|
25
|
+
|
26
|
+
rescue SystemCallError, IOError
|
27
|
+
Puma::Util.purge_interrupt_queue
|
18
28
|
end
|
19
29
|
|
20
30
|
def development?
|
@@ -108,9 +118,7 @@ module Puma
|
|
108
118
|
append = @options[:redirect_append]
|
109
119
|
|
110
120
|
if stdout
|
111
|
-
|
112
|
-
raise "Cannot redirect STDOUT to #{stdout}"
|
113
|
-
end
|
121
|
+
ensure_output_directory_exists(stdout, 'STDOUT')
|
114
122
|
|
115
123
|
STDOUT.reopen stdout, (append ? "a" : "w")
|
116
124
|
STDOUT.puts "=== puma startup: #{Time.now} ==="
|
@@ -118,9 +126,7 @@ module Puma
|
|
118
126
|
end
|
119
127
|
|
120
128
|
if stderr
|
121
|
-
|
122
|
-
raise "Cannot redirect STDERR to #{stderr}"
|
123
|
-
end
|
129
|
+
ensure_output_directory_exists(stderr, 'STDERR')
|
124
130
|
|
125
131
|
STDERR.reopen stderr, (append ? "a" : "w")
|
126
132
|
STDERR.puts "=== puma startup: #{Time.now} ==="
|
@@ -159,5 +165,12 @@ module Puma
|
|
159
165
|
server.inherit_binder @launcher.binder
|
160
166
|
server
|
161
167
|
end
|
168
|
+
|
169
|
+
private
|
170
|
+
def ensure_output_directory_exists(path, io_name)
|
171
|
+
unless Dir.exist?(File.dirname(path))
|
172
|
+
raise "Cannot redirect #{io_name} to #{path}"
|
173
|
+
end
|
174
|
+
end
|
162
175
|
end
|
163
176
|
end
|
data/lib/puma/server.rb
CHANGED
@@ -146,7 +146,7 @@ module Puma
|
|
146
146
|
begin
|
147
147
|
skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if skt.kind_of? TCPSocket
|
148
148
|
rescue IOError, SystemCallError
|
149
|
-
|
149
|
+
Puma::Util.purge_interrupt_queue
|
150
150
|
end
|
151
151
|
end
|
152
152
|
|
@@ -155,7 +155,7 @@ module Puma
|
|
155
155
|
begin
|
156
156
|
skt.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if skt.kind_of? TCPSocket
|
157
157
|
rescue IOError, SystemCallError
|
158
|
-
|
158
|
+
Puma::Util.purge_interrupt_queue
|
159
159
|
end
|
160
160
|
end
|
161
161
|
else
|
@@ -176,7 +176,7 @@ module Puma
|
|
176
176
|
begin
|
177
177
|
tcp_info = skt.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
|
178
178
|
rescue IOError, SystemCallError
|
179
|
-
|
179
|
+
Puma::Util.purge_interrupt_queue
|
180
180
|
@precheck_closing = false
|
181
181
|
false
|
182
182
|
else
|
@@ -323,6 +323,8 @@ module Puma
|
|
323
323
|
remote_addr_value = @options[:remote_address_value]
|
324
324
|
when :header
|
325
325
|
remote_addr_header = @options[:remote_address_header]
|
326
|
+
when :proxy_protocol
|
327
|
+
remote_addr_proxy_protocol = @options[:remote_address_proxy_protocol]
|
326
328
|
end
|
327
329
|
|
328
330
|
while @status == :run || (drain && shutting_down?)
|
@@ -348,11 +350,16 @@ module Puma
|
|
348
350
|
client.peerip = remote_addr_value
|
349
351
|
elsif remote_addr_header
|
350
352
|
client.remote_addr_header = remote_addr_header
|
353
|
+
elsif remote_addr_proxy_protocol
|
354
|
+
client.expect_proxy_proto = remote_addr_proxy_protocol
|
351
355
|
end
|
352
356
|
pool << client
|
353
357
|
end
|
354
358
|
end
|
355
|
-
rescue
|
359
|
+
rescue IOError, Errno::EBADF
|
360
|
+
# In the case that any of the sockets are unexpectedly close.
|
361
|
+
raise
|
362
|
+
rescue StandardError => e
|
356
363
|
@events.unknown_error e, nil, "Listen loop"
|
357
364
|
end
|
358
365
|
end
|
@@ -484,7 +491,7 @@ module Puma
|
|
484
491
|
begin
|
485
492
|
client.close if close_socket
|
486
493
|
rescue IOError, SystemCallError
|
487
|
-
|
494
|
+
Puma::Util.purge_interrupt_queue
|
488
495
|
# Already closed
|
489
496
|
rescue StandardError => e
|
490
497
|
@events.unknown_error e, nil, "Client"
|
@@ -576,11 +583,11 @@ module Puma
|
|
576
583
|
@notify << message
|
577
584
|
rescue IOError, NoMethodError, Errno::EPIPE
|
578
585
|
# The server, in another thread, is shutting down
|
579
|
-
|
586
|
+
Puma::Util.purge_interrupt_queue
|
580
587
|
rescue RuntimeError => e
|
581
588
|
# Temporary workaround for https://bugs.ruby-lang.org/issues/13239
|
582
589
|
if e.message.include?('IOError')
|
583
|
-
|
590
|
+
Puma::Util.purge_interrupt_queue
|
584
591
|
else
|
585
592
|
raise e
|
586
593
|
end
|
data/lib/puma/util.rb
CHANGED
@@ -10,6 +10,13 @@ module Puma
|
|
10
10
|
IO.pipe
|
11
11
|
end
|
12
12
|
|
13
|
+
# An instance method on Thread has been provided to address https://bugs.ruby-lang.org/issues/13632,
|
14
|
+
# which currently effects some older versions of Ruby: 2.2.7 2.2.8 2.2.9 2.2.10 2.3.4 2.4.1
|
15
|
+
# Additional context: https://github.com/puma/puma/pull/1345
|
16
|
+
def purge_interrupt_queue
|
17
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
18
|
+
end
|
19
|
+
|
13
20
|
# Unescapes a URI escaped string with +encoding+. +encoding+ will be the
|
14
21
|
# target encoding of the string returned, and it defaults to UTF-8
|
15
22
|
if defined?(::Encoding)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: puma
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Evan Phoenix
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-10-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nio4r
|
@@ -24,9 +24,9 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '2.0'
|
27
|
-
description: Puma is a simple, fast, threaded, and highly
|
27
|
+
description: Puma is a simple, fast, threaded, and highly parallel HTTP 1.1 server
|
28
28
|
for Ruby/Rack applications. Puma is intended for use in both development and production
|
29
|
-
environments. It's great for highly
|
29
|
+
environments. It's great for highly parallel Ruby implementations such as Rubinius
|
30
30
|
and JRuby as well as as providing process worker support to support CRuby well.
|
31
31
|
email:
|
32
32
|
- evan@phx.io
|
@@ -143,6 +143,6 @@ requirements: []
|
|
143
143
|
rubygems_version: 3.2.3
|
144
144
|
signing_key:
|
145
145
|
specification_version: 4
|
146
|
-
summary: Puma is a simple, fast, threaded, and highly
|
146
|
+
summary: Puma is a simple, fast, threaded, and highly parallel HTTP 1.1 server for
|
147
147
|
Ruby/Rack applications
|
148
148
|
test_files: []
|