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.

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
- !@read_header
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
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
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 unless @read_header
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
@@ -106,7 +106,7 @@ module Puma
106
106
  begin
107
107
  @worker_write << "b#{Process.pid}:#{index}\n"
108
108
  rescue SystemCallError, IOError
109
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
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
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
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
 
@@ -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'] || "development" },
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.4.0".freeze
104
- CODE_NAME = "Super Flight".freeze
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
@@ -47,7 +47,7 @@ module Puma
47
47
  @control_auth_token = nil
48
48
  @config_file = nil
49
49
  @command = nil
50
- @environment = ENV['RACK_ENV'] || ENV['RAILS_ENV']
50
+ @environment = ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']
51
51
 
52
52
  @argv = argv.dup
53
53
  @stdout = stdout
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] = block
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 4 possible values:
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. **\<Any string\>** - this allows you to hardcode remote address to any value
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
- # Read any drop any partially initialized sockets and any received bytes during shutdown.
183
- # Don't let this socket hold this loop forever.
184
- # If it can't send more packets within 1s, then give up.
185
- return if [:timeout, :eof].include?(read_and_drop(1)) while should_drop_bytes?
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
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
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
- unless Dir.exist?(File.dirname(stdout))
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
- unless Dir.exist?(File.dirname(stderr))
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
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
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
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
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
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
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 Object => e
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
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
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
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
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
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
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.0
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-07-29 00:00:00.000000000 Z
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 concurrent HTTP 1.1 server
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 concurrent Ruby implementations such as Rubinius
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 concurrent HTTP 1.1 server for
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: []