puma 5.0.0.beta1 → 5.0.0.beta2

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.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

@@ -5,7 +5,6 @@ require 'puma/util'
5
5
  require 'puma/plugin'
6
6
 
7
7
  require 'time'
8
- require 'json'
9
8
 
10
9
  module Puma
11
10
  # This class is instantiated by the `Puma::Launcher` and used
@@ -95,6 +94,7 @@ module Puma
95
94
 
96
95
  def ping!(status)
97
96
  @last_checkin = Time.now
97
+ require 'json'
98
98
  @last_status = JSON.parse(status, symbolize_names: true)
99
99
  end
100
100
 
@@ -248,6 +248,7 @@ module Puma
248
248
  $0 = title
249
249
 
250
250
  Signal.trap "SIGINT", "IGNORE"
251
+ Signal.trap "SIGCHLD", "DEFAULT"
251
252
 
252
253
  fork_worker = @options[:fork_worker] && index == 0
253
254
 
@@ -284,9 +285,11 @@ module Puma
284
285
 
285
286
  if fork_worker
286
287
  restart_server.clear
288
+ worker_pids = []
287
289
  Signal.trap "SIGCHLD" do
288
- Process.wait(-1, Process::WNOHANG) rescue nil
289
- wakeup!
290
+ wakeup! if worker_pids.reject! do |p|
291
+ Process.wait(p, Process::WNOHANG) rescue true
292
+ end
290
293
  end
291
294
 
292
295
  Thread.new do
@@ -303,7 +306,7 @@ module Puma
303
306
  elsif idx == 0 # restart server
304
307
  restart_server << true << false
305
308
  else # fork worker
306
- pid = spawn_worker(idx, master)
309
+ worker_pids << pid = spawn_worker(idx, master)
307
310
  @worker_write << "f#{pid}:#{idx}\n" rescue nil
308
311
  end
309
312
  end
@@ -330,6 +333,7 @@ module Puma
330
333
  while true
331
334
  sleep Const::WORKER_CHECK_INTERVAL
332
335
  begin
336
+ require 'json'
333
337
  io << "p#{Process.pid}#{server.stats.to_json}\n"
334
338
  rescue IOError
335
339
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
@@ -3,7 +3,7 @@
3
3
  module Puma
4
4
  # Rack::CommonLogger forwards every request to the given +app+, and
5
5
  # logs a line in the
6
- # {Apache common log format}[http://httpd.apache.org/docs/1.3/logs.html#common]
6
+ # {Apache common log format}[https://httpd.apache.org/docs/1.3/logs.html#common]
7
7
  # to the +logger+.
8
8
  #
9
9
  # If +logger+ is nil, CommonLogger will fall back +rack.errors+, which is
@@ -16,7 +16,7 @@ module Puma
16
16
  # (which is called without arguments in order to make the error appear for
17
17
  # sure)
18
18
  class CommonLogger
19
- # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
19
+ # Common Log Format: https://httpd.apache.org/docs/1.3/logs.html#common
20
20
  #
21
21
  # lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 -
22
22
  #
@@ -100,7 +100,7 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "5.0.0.beta1".freeze
103
+ PUMA_VERSION = VERSION = "5.0.0.beta2".freeze
104
104
  CODE_NAME = "Spoony Bard".freeze
105
105
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
106
106
 
@@ -443,8 +443,8 @@ module Puma
443
443
  #
444
444
  # @note Cluster mode only.
445
445
  # @example
446
- # on_worker_fork do
447
- # puts 'Before worker fork...'
446
+ # on_worker_boot do
447
+ # puts 'Before worker boot...'
448
448
  # end
449
449
  def on_worker_boot(&block)
450
450
  @options[:before_worker_boot] ||= []
@@ -769,7 +769,7 @@ module Puma
769
769
  # also increase time to boot and fork. See your logs for details on how much
770
770
  # time this adds to your boot process. For most apps, it will be less than one
771
771
  # second.
772
- def nakayoshi_fork(enabled=false)
772
+ def nakayoshi_fork(enabled=true)
773
773
  @options[:nakayoshi_fork] = enabled
774
774
  end
775
775
  end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma/const'
4
+
5
+ module Puma
6
+ # The implementation of a detailed error logging.
7
+ #
8
+ class ErrorLogger
9
+ include Const
10
+
11
+ attr_reader :ioerr
12
+
13
+ REQUEST_FORMAT = %{"%s %s%s" - (%s)}
14
+
15
+ def initialize(ioerr)
16
+ @ioerr = ioerr
17
+ @ioerr.sync = true
18
+
19
+ @debug = ENV.key? 'PUMA_DEBUG'
20
+ end
21
+
22
+ def self.stdio
23
+ new $stderr
24
+ end
25
+
26
+ # Print occured error details.
27
+ # +options+ hash with additional options:
28
+ # - +error+ is an exception object
29
+ # - +req+ the http request
30
+ # - +text+ (default nil) custom string to print in title
31
+ # and before all remaining info.
32
+ #
33
+ def info(options={})
34
+ ioerr.puts title(options)
35
+ end
36
+
37
+ # Print occured error details only if
38
+ # environment variable PUMA_DEBUG is defined.
39
+ # +options+ hash with additional options:
40
+ # - +error+ is an exception object
41
+ # - +req+ the http request
42
+ # - +text+ (default nil) custom string to print in title
43
+ # and before all remaining info.
44
+ #
45
+ def debug(options={})
46
+ return unless @debug
47
+
48
+ error = options[:error]
49
+ req = options[:req]
50
+
51
+ string_block = []
52
+ string_block << title(options)
53
+ string_block << request_dump(req) if req
54
+ string_block << error_backtrace(options) if error
55
+
56
+ ioerr.puts string_block.join("\n")
57
+ end
58
+
59
+ def title(options={})
60
+ text = options[:text]
61
+ req = options[:req]
62
+ error = options[:error]
63
+
64
+ string_block = ["#{Time.now}"]
65
+ string_block << " #{text}" if text
66
+ string_block << " (#{request_title(req)})" if request_parsed?(req)
67
+ string_block << ": #{error.inspect}" if error
68
+ string_block.join('')
69
+ end
70
+
71
+ def request_dump(req)
72
+ "Headers: #{request_headers(req)}\n" \
73
+ "Body: #{req.body}"
74
+ end
75
+
76
+ def request_title(req)
77
+ env = req.env
78
+
79
+ REQUEST_FORMAT % [
80
+ env[REQUEST_METHOD],
81
+ env[REQUEST_PATH] || env[PATH_INFO],
82
+ env[QUERY_STRING] || "",
83
+ env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-"
84
+ ]
85
+ end
86
+
87
+ def request_headers(req)
88
+ headers = req.env.select { |key, _| key.start_with?('HTTP_') }
89
+ headers.map { |key, value| [key[5..-1], value] }.to_h.inspect
90
+ end
91
+
92
+ def request_parsed?(req)
93
+ req && req.env[REQUEST_METHOD]
94
+ end
95
+ end
96
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/const'
4
3
  require "puma/null_io"
4
+ require 'puma/error_logger'
5
5
  require 'stringio'
6
6
 
7
7
  module Puma
@@ -23,8 +23,6 @@ module Puma
23
23
  end
24
24
  end
25
25
 
26
- include Const
27
-
28
26
  # Create an Events object that prints to +stdout+ and +stderr+.
29
27
  #
30
28
  def initialize(stdout, stderr)
@@ -36,6 +34,7 @@ module Puma
36
34
  @stderr.sync = true
37
35
 
38
36
  @debug = ENV.key? 'PUMA_DEBUG'
37
+ @error_logger = ErrorLogger.new(@stderr)
39
38
 
40
39
  @hooks = Hash.new { |h,k| h[k] = [] }
41
40
  end
@@ -66,7 +65,8 @@ module Puma
66
65
  # Write +str+ to +@stdout+
67
66
  #
68
67
  def log(str)
69
- @stdout.puts format(str)
68
+ @stdout.puts format(str) if @stdout.respond_to? :puts
69
+ rescue Errno::EPIPE
70
70
  end
71
71
 
72
72
  def write(str)
@@ -80,7 +80,7 @@ module Puma
80
80
  # Write +str+ to +@stderr+
81
81
  #
82
82
  def error(str)
83
- @stderr.puts format("ERROR: #{str}")
83
+ @error_logger.info(text: format("ERROR: #{str}"))
84
84
  exit 1
85
85
  end
86
86
 
@@ -88,43 +88,45 @@ module Puma
88
88
  formatter.call(str)
89
89
  end
90
90
 
91
+ # An HTTP connection error has occurred.
92
+ # +error+ a connection exception, +req+ the request,
93
+ # and +text+ additional info
94
+ #
95
+ def connection_error(error, req, text="HTTP connection error")
96
+ @error_logger.info(error: error, req: req, text: text)
97
+ end
98
+
91
99
  # An HTTP parse error has occurred.
92
- # +server+ is the Server object, +env+ the request, and +error+ a
93
- # parsing exception.
100
+ # +error+ a parsing exception,
101
+ # and +req+ the request.
94
102
  #
95
- def parse_error(server, env, error)
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"
103
+ def parse_error(error, req)
104
+ @error_logger.info(error: error, req: req, text: 'HTTP parse error, malformed request')
100
105
  end
101
106
 
102
107
  # An SSL error has occurred.
103
- # +server+ is the Server object, +peeraddr+ peer address, +peercert+
104
- # any peer certificate (if present), and +error+ an exception object.
108
+ # +error+ an exception object, +peeraddr+ peer address,
109
+ # and +peercert+ any peer certificate (if present).
105
110
  #
106
- def ssl_error(server, peeraddr, peercert, error)
111
+ def ssl_error(error, peeraddr, peercert)
107
112
  subject = peercert ? peercert.subject : nil
108
- @stderr.puts "#{Time.now}: SSL error, peer: #{peeraddr}, peer cert: #{subject}, #{error.inspect}"
113
+ @error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
109
114
  end
110
115
 
111
116
  # An unknown error has occurred.
112
- # +server+ is the Server object, +error+ an exception object,
113
- # +kind+ some additional info, and +env+ the request.
117
+ # +error+ an exception object, +req+ the request,
118
+ # and +text+ additional info
114
119
  #
115
- def unknown_error(server, error, kind="Unknown", env=nil)
116
- if error.respond_to? :render
117
- error.render "#{Time.now}: #{kind} error", @stderr
118
- else
119
- if env
120
- string_block = [ "#{Time.now}: #{kind} error handling request { #{env['REQUEST_METHOD']} #{env['PATH_INFO']} }" ]
121
- string_block << error.inspect
122
- else
123
- string_block = [ "#{Time.now}: #{kind} error: #{error.inspect}" ]
124
- end
125
- string_block << error.backtrace
126
- @stderr.puts string_block.join("\n")
127
- end
120
+ def unknown_error(error, req=nil, text="Unknown error")
121
+ @error_logger.info(error: error, req: req, text: text)
122
+ end
123
+
124
+ # Log occurred error debug dump.
125
+ # +error+ an exception object, +req+ the request,
126
+ # and +text+ additional info
127
+ #
128
+ def debug_error(error, req=nil, text="")
129
+ @error_logger.debug(error: error, req: req, text: text)
128
130
  end
129
131
 
130
132
  def on_booted(&block)
@@ -47,7 +47,7 @@ module Puma
47
47
  @original_argv = @argv.dup
48
48
  @config = conf
49
49
 
50
- @binder = Binder.new(@events)
50
+ @binder = Binder.new(@events, conf)
51
51
  @binder.create_inherited_fds(ENV).each { |k| ENV.delete k }
52
52
  @binder.create_activated_fds(ENV).each { |k| ENV.delete k }
53
53
 
@@ -111,6 +111,7 @@ module Puma
111
111
  sf.pid = Process.pid
112
112
  sf.control_url = @options[:control_url]
113
113
  sf.control_auth_token = @options[:control_auth_token]
114
+ sf.running_from = File.expand_path('.')
114
115
 
115
116
  sf.save path, permission
116
117
  end
@@ -172,12 +173,13 @@ module Puma
172
173
  case @status
173
174
  when :halt
174
175
  log "* Stopping immediately!"
176
+ @runner.stop_control
175
177
  when :run, :stop
176
178
  graceful_stop
177
179
  when :restart
178
180
  log "* Restarting..."
179
181
  ENV.replace(previous_env)
180
- @runner.before_restart
182
+ @runner.stop_control
181
183
  restart!
182
184
  when :exit
183
185
  # nothing
@@ -286,6 +288,7 @@ module Puma
286
288
  end
287
289
 
288
290
  def prune_bundler
291
+ return if ENV['PUMA_BUNDLER_PRUNED']
289
292
  return unless defined?(Bundler)
290
293
  require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
291
294
  unless puma_wild_location
@@ -5,8 +5,18 @@ begin
5
5
  rescue LoadError
6
6
  end
7
7
 
8
+ # need for Puma::MiniSSL::OPENSSL constants used in `HAS_TLS1_3`
9
+ require 'puma/puma_http11'
10
+
8
11
  module Puma
9
12
  module MiniSSL
13
+
14
+ # define constant at runtime, as it's easy to determine at built time,
15
+ # but Puma could (it shouldn't) be loaded with an older OpenSSL version
16
+ HAS_TLS1_3 = !IS_JRUBY &&
17
+ (OPENSSL_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) != -1 &&
18
+ (OPENSSL_LIBRARY_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) !=-1
19
+
10
20
  class Socket
11
21
  def initialize(socket, engine)
12
22
  @socket = socket
@@ -22,6 +32,24 @@ module Puma
22
32
  @socket.closed?
23
33
  end
24
34
 
35
+ # returns a two element array
36
+ # first is protocol version (SSL_get_version)
37
+ # second is 'handshake' state (SSL_state_string)
38
+ #
39
+ # used for dropping tcp connections to ssl
40
+ # see OpenSSL ssl/ssl_stat.c SSL_state_string for info
41
+ #
42
+ def ssl_version_state
43
+ IS_JRUBY ? [nil, nil] : @engine.ssl_vers_st
44
+ end
45
+
46
+ # used to check the handshake status, in particular when a TCP connection
47
+ # is made with TLSv1.3 as an available protocol
48
+ def bad_tlsv1_3?
49
+ HAS_TLS1_3 && @engine.ssl_vers_st == ['TLSv1.3', 'SSLERR']
50
+ end
51
+ private :bad_tlsv1_3?
52
+
25
53
  def readpartial(size)
26
54
  while true
27
55
  output = @engine.read
@@ -41,6 +69,7 @@ module Puma
41
69
 
42
70
  def engine_read_all
43
71
  output = @engine.read
72
+ raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
44
73
  while output and additional_output = @engine.read
45
74
  output << additional_output
46
75
  end
@@ -167,7 +196,10 @@ module Puma
167
196
  end
168
197
  end
169
198
 
170
- if defined?(JRUBY_VERSION)
199
+ if IS_JRUBY
200
+ OPENSSL_NO_SSL3 = false
201
+ OPENSSL_NO_TLS1 = false
202
+
171
203
  class SSLError < StandardError
172
204
  # Define this for jruby even though it isn't used.
173
205
  end
@@ -184,7 +216,7 @@ module Puma
184
216
  @no_tlsv1_1 = false
185
217
  end
186
218
 
187
- if defined?(JRUBY_VERSION)
219
+ if IS_JRUBY
188
220
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
189
221
  attr_reader :keystore
190
222
  attr_accessor :keystore_pass
@@ -252,7 +252,7 @@ module Puma
252
252
  c.close
253
253
  clear_monitor mon
254
254
 
255
- @events.ssl_error @server, addr, cert, e
255
+ @events.ssl_error e, addr, cert
256
256
 
257
257
  # The client doesn't know HTTP well
258
258
  rescue HttpParserError => e
@@ -263,7 +263,7 @@ module Puma
263
263
 
264
264
  clear_monitor mon
265
265
 
266
- @events.parse_error @server, c.env, e
266
+ @events.parse_error e, c
267
267
  rescue StandardError => e
268
268
  @server.lowlevel_error(e, c.env)
269
269
 
@@ -30,7 +30,7 @@ module Puma
30
30
  @events.log str
31
31
  end
32
32
 
33
- def before_restart
33
+ def stop_control
34
34
  @control.stop(true) if @control
35
35
  end
36
36