puma 5.0.0.beta1-java → 5.0.3-java

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.

Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1188 -559
  3. data/README.md +15 -8
  4. data/bin/puma-wild +3 -9
  5. data/docs/architecture.md +3 -3
  6. data/docs/deployment.md +10 -7
  7. data/docs/jungle/README.md +0 -4
  8. data/docs/jungle/rc.d/puma +2 -2
  9. data/docs/nginx.md +1 -1
  10. data/docs/restart.md +46 -23
  11. data/docs/signals.md +7 -7
  12. data/docs/systemd.md +1 -1
  13. data/ext/puma_http11/ext_help.h +1 -1
  14. data/ext/puma_http11/http11_parser.c +3 -1
  15. data/ext/puma_http11/http11_parser.rl +3 -1
  16. data/ext/puma_http11/mini_ssl.c +53 -38
  17. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  18. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
  19. data/ext/puma_http11/puma_http11.c +22 -11
  20. data/lib/puma.rb +16 -0
  21. data/lib/puma/app/status.rb +47 -44
  22. data/lib/puma/binder.rb +40 -12
  23. data/lib/puma/client.rb +68 -82
  24. data/lib/puma/cluster.rb +30 -187
  25. data/lib/puma/cluster/worker.rb +170 -0
  26. data/lib/puma/cluster/worker_handle.rb +83 -0
  27. data/lib/puma/commonlogger.rb +2 -2
  28. data/lib/puma/configuration.rb +9 -7
  29. data/lib/puma/const.rb +2 -1
  30. data/lib/puma/control_cli.rb +2 -0
  31. data/lib/puma/detect.rb +9 -0
  32. data/lib/puma/dsl.rb +77 -39
  33. data/lib/puma/error_logger.rb +97 -0
  34. data/lib/puma/events.rb +37 -31
  35. data/lib/puma/launcher.rb +20 -10
  36. data/lib/puma/minissl.rb +55 -10
  37. data/lib/puma/minissl/context_builder.rb +0 -3
  38. data/lib/puma/puma_http11.jar +0 -0
  39. data/lib/puma/queue_close.rb +26 -0
  40. data/lib/puma/reactor.rb +77 -373
  41. data/lib/puma/request.rb +438 -0
  42. data/lib/puma/runner.rb +7 -19
  43. data/lib/puma/server.rb +229 -506
  44. data/lib/puma/single.rb +3 -2
  45. data/lib/puma/state_file.rb +1 -1
  46. data/lib/puma/thread_pool.rb +32 -5
  47. data/lib/puma/util.rb +12 -0
  48. metadata +12 -10
  49. data/docs/jungle/upstart/README.md +0 -61
  50. data/docs/jungle/upstart/puma-manager.conf +0 -31
  51. data/docs/jungle/upstart/puma.conf +0 -69
  52. data/lib/puma/accept_nonblock.rb +0 -29
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma/const'
4
+
5
+ module Puma
6
+ # The implementation of a detailed error logging.
7
+ # @version 5.0.0
8
+ #
9
+ class ErrorLogger
10
+ include Const
11
+
12
+ attr_reader :ioerr
13
+
14
+ REQUEST_FORMAT = %{"%s %s%s" - (%s)}
15
+
16
+ def initialize(ioerr)
17
+ @ioerr = ioerr
18
+ @ioerr.sync = true
19
+
20
+ @debug = ENV.key? 'PUMA_DEBUG'
21
+ end
22
+
23
+ def self.stdio
24
+ new $stderr
25
+ end
26
+
27
+ # Print occured error details.
28
+ # +options+ hash with additional options:
29
+ # - +error+ is an exception object
30
+ # - +req+ the http request
31
+ # - +text+ (default nil) custom string to print in title
32
+ # and before all remaining info.
33
+ #
34
+ def info(options={})
35
+ ioerr.puts title(options)
36
+ end
37
+
38
+ # Print occured error details only if
39
+ # environment variable PUMA_DEBUG is defined.
40
+ # +options+ hash with additional options:
41
+ # - +error+ is an exception object
42
+ # - +req+ the http request
43
+ # - +text+ (default nil) custom string to print in title
44
+ # and before all remaining info.
45
+ #
46
+ def debug(options={})
47
+ return unless @debug
48
+
49
+ error = options[:error]
50
+ req = options[:req]
51
+
52
+ string_block = []
53
+ string_block << title(options)
54
+ string_block << request_dump(req) if request_parsed?(req)
55
+ string_block << error.backtrace if error
56
+
57
+ ioerr.puts string_block.join("\n")
58
+ end
59
+
60
+ def title(options={})
61
+ text = options[:text]
62
+ req = options[:req]
63
+ error = options[:error]
64
+
65
+ string_block = ["#{Time.now}"]
66
+ string_block << " #{text}" if text
67
+ string_block << " (#{request_title(req)})" if request_parsed?(req)
68
+ string_block << ": #{error.inspect}" if error
69
+ string_block.join('')
70
+ end
71
+
72
+ def request_dump(req)
73
+ "Headers: #{request_headers(req)}\n" \
74
+ "Body: #{req.body}"
75
+ end
76
+
77
+ def request_title(req)
78
+ env = req.env
79
+
80
+ REQUEST_FORMAT % [
81
+ env[REQUEST_METHOD],
82
+ env[REQUEST_PATH] || env[PATH_INFO],
83
+ env[QUERY_STRING] || "",
84
+ env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-"
85
+ ]
86
+ end
87
+
88
+ def request_headers(req)
89
+ headers = req.env.select { |key, _| key.start_with?('HTTP_') }
90
+ headers.map { |key, value| [key[5..-1], value] }.to_h.inspect
91
+ end
92
+
93
+ def request_parsed?(req)
94
+ req && req.env[REQUEST_METHOD]
95
+ end
96
+ end
97
+ 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,49 @@ 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
+ # @version 5.0.0
95
+ #
96
+ def connection_error(error, req, text="HTTP connection error")
97
+ @error_logger.info(error: error, req: req, text: text)
98
+ end
99
+
91
100
  # An HTTP parse error has occurred.
92
- # +server+ is the Server object, +env+ the request, and +error+ a
93
- # parsing exception.
101
+ # +error+ a parsing exception,
102
+ # and +req+ the request.
94
103
  #
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"
104
+ def parse_error(error, req)
105
+ @error_logger.info(error: error, req: req, text: 'HTTP parse error, malformed request')
100
106
  end
101
107
 
102
108
  # 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.
109
+ # @param error <Puma::MiniSSL::SSLError>
110
+ # @param ssl_socket <Puma::MiniSSL::Socket>
105
111
  #
106
- def ssl_error(server, peeraddr, peercert, error)
112
+ def ssl_error(error, ssl_socket)
113
+ peeraddr = ssl_socket.peeraddr.last rescue "<unknown>"
114
+ peercert = ssl_socket.peercert
107
115
  subject = peercert ? peercert.subject : nil
108
- @stderr.puts "#{Time.now}: SSL error, peer: #{peeraddr}, peer cert: #{subject}, #{error.inspect}"
116
+ @error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
109
117
  end
110
118
 
111
119
  # An unknown error has occurred.
112
- # +server+ is the Server object, +error+ an exception object,
113
- # +kind+ some additional info, and +env+ the request.
120
+ # +error+ an exception object, +req+ the request,
121
+ # and +text+ additional info
114
122
  #
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
123
+ def unknown_error(error, req=nil, text="Unknown error")
124
+ @error_logger.info(error: error, req: req, text: text)
125
+ end
126
+
127
+ # Log occurred error debug dump.
128
+ # +error+ an exception object, +req+ the request,
129
+ # and +text+ additional info
130
+ # @version 5.0.0
131
+ #
132
+ def debug_error(error, req=nil, text="")
133
+ @error_logger.debug(error: error, req: req, text: text)
128
134
  end
129
135
 
130
136
  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
@@ -186,10 +188,13 @@ module Puma
186
188
  end
187
189
 
188
190
  # Return all tcp ports the launcher may be using, TCP or SSL
191
+ # @!attribute [r] connected_ports
192
+ # @version 5.0.0
189
193
  def connected_ports
190
194
  @binder.connected_ports
191
195
  end
192
196
 
197
+ # @!attribute [r] restart_args
193
198
  def restart_args
194
199
  cmd = @options[:restart_cmd]
195
200
  if cmd
@@ -204,6 +209,8 @@ module Puma
204
209
  @binder.close_listeners
205
210
  end
206
211
 
212
+ # @!attribute [r] thread_status
213
+ # @version 5.0.0
207
214
  def thread_status
208
215
  Thread.list.each do |thread|
209
216
  name = "Thread: TID-#{thread.object_id.to_s(36)}"
@@ -257,16 +264,14 @@ module Puma
257
264
  end
258
265
  end
259
266
 
260
- def dependencies_and_files_to_require_after_prune
267
+ # @!attribute [r] files_to_require_after_prune
268
+ def files_to_require_after_prune
261
269
  puma = spec_for_gem("puma")
262
270
 
263
- deps = puma.runtime_dependencies.map do |d|
264
- "#{d.name}:#{spec_for_gem(d.name).version}"
265
- end
266
-
267
- [deps, require_paths_for_gem(puma) + extra_runtime_deps_directories]
271
+ require_paths_for_gem(puma) + extra_runtime_deps_directories
268
272
  end
269
273
 
274
+ # @!attribute [r] extra_runtime_deps_directories
270
275
  def extra_runtime_deps_directories
271
276
  Array(@options[:extra_runtime_dependencies]).map do |d_name|
272
277
  if (spec = spec_for_gem(d_name))
@@ -278,6 +283,7 @@ module Puma
278
283
  end.flatten.compact
279
284
  end
280
285
 
286
+ # @!attribute [r] puma_wild_location
281
287
  def puma_wild_location
282
288
  puma = spec_for_gem("puma")
283
289
  dirs = require_paths_for_gem(puma)
@@ -286,6 +292,7 @@ module Puma
286
292
  end
287
293
 
288
294
  def prune_bundler
295
+ return if ENV['PUMA_BUNDLER_PRUNED']
289
296
  return unless defined?(Bundler)
290
297
  require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
291
298
  unless puma_wild_location
@@ -293,7 +300,7 @@ module Puma
293
300
  return
294
301
  end
295
302
 
296
- deps, dirs = dependencies_and_files_to_require_after_prune
303
+ dirs = files_to_require_after_prune
297
304
 
298
305
  log '* Pruning Bundler environment'
299
306
  home = ENV['GEM_HOME']
@@ -302,7 +309,7 @@ module Puma
302
309
  ENV['GEM_HOME'] = home
303
310
  ENV['BUNDLE_GEMFILE'] = bundle_gemfile
304
311
  ENV['PUMA_BUNDLER_PRUNED'] = '1'
305
- args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
312
+ args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':')] + @original_argv
306
313
  # Ruby 2.0+ defaults to true which breaks socket activation
307
314
  args += [{:close_others => false}]
308
315
  Kernel.exec(*args)
@@ -340,6 +347,7 @@ module Puma
340
347
  Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
341
348
  end
342
349
 
350
+ # @!attribute [r] title
343
351
  def title
344
352
  buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
345
353
  buffer += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
@@ -351,6 +359,7 @@ module Puma
351
359
  ENV['RACK_ENV'] = environment
352
360
  end
353
361
 
362
+ # @!attribute [r] environment
354
363
  def environment
355
364
  @environment
356
365
  end
@@ -475,6 +484,7 @@ module Puma
475
484
  "You must have RubyGems #{min_version}+ to use this feature."
476
485
  end
477
486
 
487
+ # @version 5.0.0
478
488
  def with_unbundled_env
479
489
  bundler_ver = Gem::Version.new(Bundler::VERSION)
480
490
  if bundler_ver < Gem::Version.new('2.1.0')
@@ -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
+ # Define constant at runtime, as it's easy to determine at built time,
14
+ # but Puma could (it shouldn't) be loaded with an older OpenSSL version
15
+ # @version 5.0.0
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
@@ -14,6 +24,7 @@ module Puma
14
24
  @peercert = nil
15
25
  end
16
26
 
27
+ # @!attribute [r] to_io
17
28
  def to_io
18
29
  @socket
19
30
  end
@@ -22,6 +33,27 @@ module Puma
22
33
  @socket.closed?
23
34
  end
24
35
 
36
+ # Returns a two element array,
37
+ # first is protocol version (SSL_get_version),
38
+ # second is 'handshake' state (SSL_state_string)
39
+ #
40
+ # Used for dropping tcp connections to ssl.
41
+ # See OpenSSL ssl/ssl_stat.c SSL_state_string for info
42
+ # @!attribute [r] ssl_version_state
43
+ # @version 5.0.0
44
+ #
45
+ def ssl_version_state
46
+ IS_JRUBY ? [nil, nil] : @engine.ssl_vers_st
47
+ end
48
+
49
+ # Used to check the handshake status, in particular when a TCP connection
50
+ # is made with TLSv1.3 as an available protocol
51
+ # @version 5.0.0
52
+ def bad_tlsv1_3?
53
+ HAS_TLS1_3 && @engine.ssl_vers_st == ['TLSv1.3', 'SSLERR']
54
+ end
55
+ private :bad_tlsv1_3?
56
+
25
57
  def readpartial(size)
26
58
  while true
27
59
  output = @engine.read
@@ -41,6 +73,7 @@ module Puma
41
73
 
42
74
  def engine_read_all
43
75
  output = @engine.read
76
+ raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
44
77
  while output and additional_output = @engine.read
45
78
  output << additional_output
46
79
  end
@@ -107,14 +140,18 @@ module Puma
107
140
  alias_method :<<, :write
108
141
 
109
142
  # This is a temporary fix to deal with websockets code using
110
- # write_nonblock. The problem with implementing it properly
143
+ # write_nonblock.
144
+
145
+ # The problem with implementing it properly
111
146
  # is that it means we'd have to have the ability to rewind
112
147
  # an engine because after we write+extract, the socket
113
148
  # write_nonblock call might raise an exception and later
114
149
  # code would pass the same data in, but the engine would think
115
- # it had already written the data in. So for the time being
116
- # (and since write blocking is quite rare), go ahead and actually
117
- # block in write_nonblock.
150
+ # it had already written the data in.
151
+ #
152
+ # So for the time being (and since write blocking is quite rare),
153
+ # go ahead and actually block in write_nonblock.
154
+ #
118
155
  def write_nonblock(data, *_)
119
156
  write data
120
157
  end
@@ -153,10 +190,12 @@ module Puma
153
190
  end
154
191
  end
155
192
 
193
+ # @!attribute [r] peeraddr
156
194
  def peeraddr
157
195
  @socket.peeraddr
158
196
  end
159
197
 
198
+ # @!attribute [r] peercert
160
199
  def peercert
161
200
  return @peercert if @peercert
162
201
 
@@ -167,12 +206,13 @@ module Puma
167
206
  end
168
207
  end
169
208
 
170
- if defined?(JRUBY_VERSION)
209
+ if IS_JRUBY
210
+ OPENSSL_NO_SSL3 = false
211
+ OPENSSL_NO_TLS1 = false
212
+
171
213
  class SSLError < StandardError
172
214
  # Define this for jruby even though it isn't used.
173
215
  end
174
-
175
- def self.check; end
176
216
  end
177
217
 
178
218
  class Context
@@ -184,7 +224,7 @@ module Puma
184
224
  @no_tlsv1_1 = false
185
225
  end
186
226
 
187
- if defined?(JRUBY_VERSION)
227
+ if IS_JRUBY
188
228
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
189
229
  attr_reader :keystore
190
230
  attr_accessor :keystore_pass
@@ -228,14 +268,16 @@ module Puma
228
268
  end
229
269
 
230
270
  # disables TLSv1
271
+ # @!attribute [w] no_tlsv1=
231
272
  def no_tlsv1=(tlsv1)
232
- raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
273
+ raise ArgumentError, "Invalid value of no_tlsv1=" unless ['true', 'false', true, false].include?(tlsv1)
233
274
  @no_tlsv1 = tlsv1
234
275
  end
235
276
 
236
277
  # disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
278
+ # @!attribute [w] no_tlsv1_1=
237
279
  def no_tlsv1_1=(tlsv1_1)
238
- raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1_1)
280
+ raise ArgumentError, "Invalid value of no_tlsv1_1=" unless ['true', 'false', true, false].include?(tlsv1_1)
239
281
  @no_tlsv1_1 = tlsv1_1
240
282
  end
241
283
 
@@ -251,6 +293,7 @@ module Puma
251
293
  @ctx = ctx
252
294
  end
253
295
 
296
+ # @!attribute [r] to_io
254
297
  def to_io
255
298
  @socket
256
299
  end
@@ -271,6 +314,8 @@ module Puma
271
314
  Socket.new io, engine
272
315
  end
273
316
 
317
+ # @!attribute [r] addr
318
+ # @version 5.0.0
274
319
  def addr
275
320
  @socket.addr
276
321
  end