puma 4.0.1 → 4.2.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.

@@ -93,7 +93,10 @@ module Puma
93
93
  # parsing exception.
94
94
  #
95
95
  def parse_error(server, env, error)
96
- @stderr.puts "#{Time.now}: HTTP parse error, malformed request (#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}): #{error.inspect}\n---\n"
96
+ @stderr.puts "#{Time.now}: HTTP parse error, malformed request " \
97
+ "(#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}#{env[REQUEST_PATH]}): " \
98
+ "#{error.inspect}" \
99
+ "\n---\n"
97
100
  end
98
101
 
99
102
  # An SSL error has occurred.
@@ -2,12 +2,9 @@
2
2
 
3
3
  require 'puma/events'
4
4
  require 'puma/detect'
5
-
6
5
  require 'puma/cluster'
7
6
  require 'puma/single'
8
-
9
7
  require 'puma/const'
10
-
11
8
  require 'puma/binder'
12
9
 
13
10
  module Puma
@@ -63,6 +60,9 @@ module Puma
63
60
  @options = @config.options
64
61
  @config.clamp
65
62
 
63
+ @events.formatter = Events::PidFormatter.new if clustered?
64
+ @events.formatter = options[:log_formatter] if @options[:log_formatter]
65
+
66
66
  generate_restart_data
67
67
 
68
68
  if clustered? && !Process.respond_to?(:fork)
@@ -81,7 +81,6 @@ module Puma
81
81
  set_rack_environment
82
82
 
83
83
  if clustered?
84
- @events.formatter = Events::PidFormatter.new
85
84
  @options[:logger] = @events
86
85
 
87
86
  @runner = Cluster.new(self, @events)
@@ -124,19 +123,6 @@ module Puma
124
123
  File.unlink(path) if path && File.exist?(path)
125
124
  end
126
125
 
127
- # If configured, write the pid of the current process out
128
- # to a file.
129
- def write_pid
130
- path = @options[:pidfile]
131
- return unless path
132
-
133
- File.open(path, 'w') { |f| f.puts Process.pid }
134
- cur = Process.pid
135
- at_exit do
136
- delete_pidfile if cur == Process.pid
137
- end
138
- end
139
-
140
126
  # Begin async shutdown of the server
141
127
  def halt
142
128
  @status = :halt
@@ -198,6 +184,7 @@ module Puma
198
184
  when :exit
199
185
  # nothing
200
186
  end
187
+ @binder.close_unix_paths
201
188
  end
202
189
 
203
190
  # Return which tcp port the launcher is using, if it's using TCP
@@ -215,16 +202,24 @@ module Puma
215
202
  end
216
203
 
217
204
  def close_binder_listeners
218
- @binder.listeners.each do |l, io|
219
- io.close
220
- uri = URI.parse(l)
221
- next unless uri.scheme == 'unix'
222
- File.unlink("#{uri.host}#{uri.path}")
223
- end
205
+ @binder.close_listeners
224
206
  end
225
207
 
226
208
  private
227
209
 
210
+ # If configured, write the pid of the current process out
211
+ # to a file.
212
+ def write_pid
213
+ path = @options[:pidfile]
214
+ return unless path
215
+
216
+ File.open(path, 'w') { |f| f.puts Process.pid }
217
+ cur = Process.pid
218
+ at_exit do
219
+ delete_pidfile if cur == Process.pid
220
+ end
221
+ end
222
+
228
223
  def reload_worker_directory
229
224
  @runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
230
225
  end
@@ -244,48 +239,71 @@ module Puma
244
239
  Dir.chdir(@restart_dir)
245
240
  Kernel.exec(*argv)
246
241
  else
247
- redirects = {:close_others => true}
248
- @binder.listeners.each_with_index do |(l, io), i|
249
- ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
250
- redirects[io.to_i] = io.to_i
251
- end
252
-
253
242
  argv = restart_args
254
243
  Dir.chdir(@restart_dir)
255
- argv += [redirects]
244
+ argv += [@binder.redirects_for_restart]
256
245
  Kernel.exec(*argv)
257
246
  end
258
247
  end
259
248
 
260
- def prune_bundler
261
- return unless defined?(Bundler)
262
- puma = Bundler.rubygems.loaded_specs("puma")
263
- dirs = puma.require_paths.map { |x| File.join(puma.full_gem_path, x) }
249
+ def dependencies_and_files_to_require_after_prune
250
+ puma = spec_for_gem("puma")
251
+
252
+ deps = puma.runtime_dependencies.map do |d|
253
+ "#{d.name}:#{spec_for_gem(d.name).version}"
254
+ end
255
+
256
+ [deps, require_paths_for_gem(puma) + extra_runtime_deps_directories]
257
+ end
258
+
259
+ def extra_runtime_deps_directories
260
+ Array(@options[:extra_runtime_dependencies]).map do |d_name|
261
+ if (spec = spec_for_gem(d_name))
262
+ require_paths_for_gem(spec)
263
+ else
264
+ log "* Could not load extra dependency: #{d_name}"
265
+ nil
266
+ end
267
+ end.flatten.compact
268
+ end
269
+
270
+ def puma_wild_location
271
+ puma = spec_for_gem("puma")
272
+ dirs = require_paths_for_gem(puma)
264
273
  puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
274
+ File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
275
+ end
265
276
 
266
- unless puma_lib_dir
277
+ def prune_bundler
278
+ return unless defined?(Bundler)
279
+ require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
280
+ unless puma_wild_location
267
281
  log "! Unable to prune Bundler environment, continuing"
268
282
  return
269
283
  end
270
284
 
271
- deps = puma.runtime_dependencies.map do |d|
272
- spec = Bundler.rubygems.loaded_specs(d.name)
273
- "#{d.name}:#{spec.version.to_s}"
274
- end
285
+ deps, dirs = dependencies_and_files_to_require_after_prune
275
286
 
276
287
  log '* Pruning Bundler environment'
277
288
  home = ENV['GEM_HOME']
278
289
  Bundler.with_clean_env do
279
290
  ENV['GEM_HOME'] = home
280
291
  ENV['PUMA_BUNDLER_PRUNED'] = '1'
281
- wild = File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
282
- args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
292
+ args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
283
293
  # Ruby 2.0+ defaults to true which breaks socket activation
284
294
  args += [{:close_others => false}]
285
295
  Kernel.exec(*args)
286
296
  end
287
297
  end
288
298
 
299
+ def spec_for_gem(gem_name)
300
+ Bundler.rubygems.loaded_specs(gem_name)
301
+ end
302
+
303
+ def require_paths_for_gem(gem_spec)
304
+ gem_spec.full_require_paths
305
+ end
306
+
289
307
  def log(str)
290
308
  @events.log str
291
309
  end
@@ -305,6 +323,21 @@ module Puma
305
323
  log "- Goodbye!"
306
324
  end
307
325
 
326
+ def log_thread_status
327
+ Thread.list.each do |thread|
328
+ log "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
329
+ logstr = "Thread: TID-#{thread.object_id.to_s(36)}"
330
+ logstr += " #{thread.name}" if thread.respond_to?(:name)
331
+ log logstr
332
+
333
+ if thread.backtrace
334
+ log thread.backtrace.join("\n")
335
+ else
336
+ log "<no backtrace available>"
337
+ end
338
+ end
339
+ end
340
+
308
341
  def set_process_title
309
342
  Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
310
343
  end
@@ -404,12 +437,6 @@ module Puma
404
437
 
405
438
  begin
406
439
  Signal.trap "SIGINT" do
407
- if Puma.jruby?
408
- @status = :exit
409
- graceful_stop
410
- exit
411
- end
412
-
413
440
  stop
414
441
  end
415
442
  rescue Exception
@@ -427,6 +454,22 @@ module Puma
427
454
  rescue Exception
428
455
  log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
429
456
  end
457
+
458
+ begin
459
+ Signal.trap "SIGINFO" do
460
+ log_thread_status
461
+ end
462
+ rescue Exception
463
+ # Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
464
+ # to see this constantly on Linux.
465
+ end
466
+ end
467
+
468
+ def require_rubygems_min_version!(min_version, feature)
469
+ return if min_version <= Gem::Version.new(Gem::VERSION)
470
+
471
+ raise "#{feature} is not supported on your version of RubyGems. " \
472
+ "You must have RubyGems #{min_version}+ to use this feature."
430
473
  end
431
474
  end
432
475
  end
@@ -54,22 +54,21 @@ module Puma
54
54
  output = engine_read_all
55
55
  return output if output
56
56
 
57
- begin
58
- data = @socket.read_nonblock(size, exception: false)
59
- if data == :wait_readable || data == :wait_writable
60
- if @socket.to_io.respond_to?(data)
61
- @socket.to_io.__send__(data)
62
- elsif data == :wait_readable
63
- IO.select([@socket.to_io])
64
- else
65
- IO.select(nil, [@socket.to_io])
66
- end
67
- elsif !data
68
- return nil
69
- else
70
- break
71
- end
72
- end while true
57
+ data = @socket.read_nonblock(size, exception: false)
58
+ if data == :wait_readable || data == :wait_writable
59
+ # It would make more sense to let @socket.read_nonblock raise
60
+ # EAGAIN if necessary but it seems like it'll misbehave on Windows.
61
+ # I don't have a Windows machine to debug this so I can't explain
62
+ # exactly whats happening in that OS. Please let me know if you
63
+ # find out!
64
+ #
65
+ # In the meantime, we can emulate the correct behavior by
66
+ # capturing :wait_readable & :wait_writable and raising EAGAIN
67
+ # ourselves.
68
+ raise IO::EAGAINWaitReadable
69
+ elsif data.nil?
70
+ return nil
71
+ end
73
72
 
74
73
  @engine.inject(data)
75
74
  output = engine_read_all
@@ -177,10 +176,11 @@ module Puma
177
176
 
178
177
  class Context
179
178
  attr_accessor :verify_mode
180
- attr_reader :no_tlsv1
179
+ attr_reader :no_tlsv1, :no_tlsv1_1
181
180
 
182
181
  def initialize
183
- @no_tlsv1 = false
182
+ @no_tlsv1 = false
183
+ @no_tlsv1_1 = false
184
184
  end
185
185
 
186
186
  if defined?(JRUBY_VERSION)
@@ -220,18 +220,24 @@ module Puma
220
220
  @ca = ca
221
221
  end
222
222
 
223
-
224
223
  def check
225
224
  raise "Key not configured" unless @key
226
225
  raise "Cert not configured" unless @cert
227
226
  end
228
227
  end
229
228
 
229
+ # disables TLSv1
230
230
  def no_tlsv1=(tlsv1)
231
231
  raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
232
232
  @no_tlsv1 = tlsv1
233
233
  end
234
234
 
235
+ # disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
236
+ def no_tlsv1_1=(tlsv1_1)
237
+ raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1_1)
238
+ @no_tlsv1_1 = tlsv1_1
239
+ end
240
+
235
241
  end
236
242
 
237
243
  VERIFY_NONE = 0
@@ -62,8 +62,11 @@ module Puma
62
62
  end
63
63
 
64
64
  def fire_background
65
- @background.each do |b|
66
- Thread.new(&b)
65
+ @background.each_with_index do |b, i|
66
+ Thread.new do
67
+ Puma.set_thread_name "plugin background #{i}"
68
+ b.call
69
+ end
67
70
  end
68
71
  end
69
72
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/plugin'
2
4
 
3
5
  Puma::Plugin.create do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  end
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma::Rack
2
4
  # Rack::URLMap takes a hash mapping urls or paths to apps, and
3
5
  # dispatches accordingly. Support for HTTP/1.1 host names exists if
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/handler/puma'
2
4
 
3
5
  module Rack::Handler
@@ -23,7 +23,7 @@ module Puma
23
23
  # A connection comes into a `Puma::Server` instance, it is then passed to a `Puma::Reactor` instance,
24
24
  # which stores it in an array and waits for any of the connections to be ready for reading.
25
25
  #
26
- # The waiting/wake up is performed with nio4r, which will use the apropriate backend (libev, Java NIO or
26
+ # The waiting/wake up is performed with nio4r, which will use the appropriate backend (libev, Java NIO or
27
27
  # just plain IO#select). The call to `NIO::Selector#select` will "wake up" and
28
28
  # return the references to any objects that caused it to "wake". The reactor
29
29
  # then loops through each of these request objects, and sees if they're complete. If they
@@ -225,7 +225,7 @@ module Puma
225
225
  # will be flooding them with errors when persistent connections
226
226
  # are closed.
227
227
  rescue ConnectionError
228
- c.write_500
228
+ c.write_error(500)
229
229
  c.close
230
230
 
231
231
  clear_monitor mon
@@ -252,7 +252,7 @@ module Puma
252
252
  rescue HttpParserError => e
253
253
  @server.lowlevel_error(e, c.env)
254
254
 
255
- c.write_400
255
+ c.write_error(400)
256
256
  c.close
257
257
 
258
258
  clear_monitor mon
@@ -261,7 +261,7 @@ module Puma
261
261
  rescue StandardError => e
262
262
  @server.lowlevel_error(e, c.env)
263
263
 
264
- c.write_500
264
+ c.write_error(500)
265
265
  c.close
266
266
 
267
267
  clear_monitor mon
@@ -277,7 +277,7 @@ module Puma
277
277
  while @timeouts.first.value.timeout_at < now
278
278
  mon = @timeouts.shift
279
279
  c = mon.value
280
- c.write_408 if c.in_data_phase
280
+ c.write_error(408) if c.in_data_phase
281
281
  c.close
282
282
 
283
283
  clear_monitor mon
@@ -307,6 +307,7 @@ module Puma
307
307
 
308
308
  def run_in_thread
309
309
  @thread = Thread.new do
310
+ Puma.set_thread_name "reactor"
310
311
  begin
311
312
  run_internal
312
313
  rescue StandardError => e
@@ -14,6 +14,7 @@ module Puma
14
14
  @options = cli.options
15
15
  @app = nil
16
16
  @control = nil
17
+ @started_at = Time.now
17
18
  end
18
19
 
19
20
  def daemon?
@@ -52,12 +53,12 @@ module Puma
52
53
 
53
54
  uri = URI.parse str
54
55
 
55
- app = Puma::App::Status.new @launcher
56
-
57
56
  if token = @options[:control_auth_token]
58
- app.auth_token = token unless token.empty? || token == 'none'
57
+ token = nil if token.empty? || token == 'none'
59
58
  end
60
59
 
60
+ app = Puma::App::Status.new @launcher, token
61
+
61
62
  control = Puma::Server.new app, @launcher.events
62
63
  control.min_threads = 0
63
64
  control.max_threads = 1
@@ -9,13 +9,13 @@ require 'puma/null_io'
9
9
  require 'puma/reactor'
10
10
  require 'puma/client'
11
11
  require 'puma/binder'
12
- require 'puma/delegation'
13
12
  require 'puma/accept_nonblock'
14
13
  require 'puma/util'
15
14
 
16
15
  require 'puma/puma_http11'
17
16
 
18
17
  require 'socket'
18
+ require 'forwardable'
19
19
 
20
20
  module Puma
21
21
 
@@ -32,7 +32,7 @@ module Puma
32
32
  class Server
33
33
 
34
34
  include Puma::Const
35
- extend Puma::Delegation
35
+ extend Forwardable
36
36
 
37
37
  attr_reader :thread
38
38
  attr_reader :events
@@ -89,10 +89,7 @@ module Puma
89
89
 
90
90
  attr_accessor :binder, :leak_stack_on_error, :early_hints
91
91
 
92
- forward :add_tcp_listener, :@binder
93
- forward :add_ssl_listener, :@binder
94
- forward :add_unix_listener, :@binder
95
- forward :connected_port, :@binder
92
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_port
96
93
 
97
94
  def inherit_binder(bind)
98
95
  @binder = bind
@@ -207,7 +204,10 @@ module Puma
207
204
  @events.fire :state, :running
208
205
 
209
206
  if background
210
- @thread = Thread.new { handle_servers_lopez_mode }
207
+ @thread = Thread.new do
208
+ Puma.set_thread_name "server"
209
+ handle_servers_lopez_mode
210
+ end
211
211
  return @thread
212
212
  else
213
213
  handle_servers_lopez_mode
@@ -317,7 +317,7 @@ module Puma
317
317
 
318
318
  @events.ssl_error self, addr, cert, e
319
319
  rescue HttpParserError => e
320
- client.write_400
320
+ client.write_error(400)
321
321
  client.close
322
322
 
323
323
  @events.parse_error self, client.env, e
@@ -351,7 +351,10 @@ module Puma
351
351
  @events.fire :state, :running
352
352
 
353
353
  if background
354
- @thread = Thread.new { handle_servers }
354
+ @thread = Thread.new do
355
+ Puma.set_thread_name "server"
356
+ handle_servers
357
+ end
355
358
  return @thread
356
359
  else
357
360
  handle_servers
@@ -505,7 +508,7 @@ module Puma
505
508
  rescue HttpParserError => e
506
509
  lowlevel_error(e, client.env)
507
510
 
508
- client.write_400
511
+ client.write_error(400)
509
512
 
510
513
  @events.parse_error self, client.env, e
511
514
 
@@ -513,7 +516,7 @@ module Puma
513
516
  rescue StandardError => e
514
517
  lowlevel_error(e, client.env)
515
518
 
516
- client.write_500
519
+ client.write_error(500)
517
520
 
518
521
  @events.unknown_error self, e, "Read"
519
522
 
@@ -588,8 +591,11 @@ module Puma
588
591
  end
589
592
 
590
593
  def default_server_port(env)
591
- return PORT_443 if env[HTTPS_KEY] == 'on' || env[HTTPS_KEY] == 'https'
592
- env['HTTP_X_FORWARDED_PROTO'] == 'https' ? PORT_443 : PORT_80
594
+ if ['on', HTTPS].include?(env[HTTPS_KEY]) || env[HTTP_X_FORWARDED_PROTO].to_s[0...5] == HTTPS || env[HTTP_X_FORWARDED_SCHEME] == HTTPS || env[HTTP_X_FORWARDED_SSL] == "on"
595
+ PORT_443
596
+ else
597
+ PORT_80
598
+ end
593
599
  end
594
600
 
595
601
  # Takes the request +req+, invokes the Rack application to construct
@@ -627,23 +633,27 @@ module Puma
627
633
  head = env[REQUEST_METHOD] == HEAD
628
634
 
629
635
  env[RACK_INPUT] = body
630
- env[RACK_URL_SCHEME] = env[HTTPS_KEY] ? HTTPS : HTTP
636
+ env[RACK_URL_SCHEME] = default_server_port(env) == PORT_443 ? HTTPS : HTTP
631
637
 
632
638
  if @early_hints
633
639
  env[EARLY_HINTS] = lambda { |headers|
634
- fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
640
+ begin
641
+ fast_write client, "HTTP/1.1 103 Early Hints\r\n".freeze
635
642
 
636
- headers.each_pair do |k, vs|
637
- if vs.respond_to?(:to_s) && !vs.to_s.empty?
638
- vs.to_s.split(NEWLINE).each do |v|
639
- fast_write client, "#{k}: #{v}\r\n"
643
+ headers.each_pair do |k, vs|
644
+ if vs.respond_to?(:to_s) && !vs.to_s.empty?
645
+ vs.to_s.split(NEWLINE).each do |v|
646
+ fast_write client, "#{k}: #{v}\r\n"
647
+ end
648
+ else
649
+ fast_write client, "#{k}: #{vs}\r\n"
640
650
  end
641
- else
642
- fast_write client, "#{k}: #{vs}\r\n"
643
651
  end
644
- end
645
652
 
646
- fast_write client, "\r\n".freeze
653
+ fast_write client, "\r\n".freeze
654
+ rescue ConnectionError
655
+ # noop, if we lost the socket we just won't send the early hints
656
+ end
647
657
  }
648
658
  end
649
659