puma 4.1.1 → 5.0.0

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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +149 -10
  3. data/LICENSE +23 -20
  4. data/README.md +30 -46
  5. data/docs/architecture.md +3 -3
  6. data/docs/deployment.md +9 -3
  7. data/docs/fork_worker.md +31 -0
  8. data/docs/jungle/README.md +13 -0
  9. data/{tools → docs}/jungle/rc.d/README.md +0 -0
  10. data/{tools → docs}/jungle/rc.d/puma +0 -0
  11. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  12. data/{tools → docs}/jungle/upstart/README.md +0 -0
  13. data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
  14. data/{tools → docs}/jungle/upstart/puma.conf +0 -0
  15. data/docs/plugins.md +20 -10
  16. data/docs/signals.md +7 -6
  17. data/docs/systemd.md +1 -63
  18. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  19. data/ext/puma_http11/extconf.rb +6 -0
  20. data/ext/puma_http11/http11_parser.c +40 -63
  21. data/ext/puma_http11/http11_parser.java.rl +21 -37
  22. data/ext/puma_http11/http11_parser.rl +3 -1
  23. data/ext/puma_http11/http11_parser_common.rl +3 -3
  24. data/ext/puma_http11/mini_ssl.c +15 -2
  25. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  26. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  27. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +91 -106
  28. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
  29. data/ext/puma_http11/puma_http11.c +9 -38
  30. data/lib/puma.rb +23 -0
  31. data/lib/puma/app/status.rb +46 -30
  32. data/lib/puma/binder.rb +112 -124
  33. data/lib/puma/cli.rb +11 -15
  34. data/lib/puma/client.rb +250 -209
  35. data/lib/puma/cluster.rb +203 -85
  36. data/lib/puma/commonlogger.rb +2 -2
  37. data/lib/puma/configuration.rb +31 -42
  38. data/lib/puma/const.rb +24 -19
  39. data/lib/puma/control_cli.rb +46 -17
  40. data/lib/puma/detect.rb +17 -0
  41. data/lib/puma/dsl.rb +162 -70
  42. data/lib/puma/error_logger.rb +97 -0
  43. data/lib/puma/events.rb +35 -31
  44. data/lib/puma/io_buffer.rb +9 -2
  45. data/lib/puma/jruby_restart.rb +0 -58
  46. data/lib/puma/launcher.rb +117 -58
  47. data/lib/puma/minissl.rb +60 -18
  48. data/lib/puma/minissl/context_builder.rb +73 -0
  49. data/lib/puma/null_io.rb +1 -1
  50. data/lib/puma/plugin.rb +6 -12
  51. data/lib/puma/rack/builder.rb +0 -4
  52. data/lib/puma/reactor.rb +16 -9
  53. data/lib/puma/runner.rb +11 -32
  54. data/lib/puma/server.rb +173 -193
  55. data/lib/puma/single.rb +7 -64
  56. data/lib/puma/state_file.rb +6 -3
  57. data/lib/puma/thread_pool.rb +104 -81
  58. data/lib/rack/handler/puma.rb +1 -5
  59. data/tools/Dockerfile +16 -0
  60. data/tools/trickletest.rb +0 -1
  61. metadata +23 -24
  62. data/ext/puma_http11/io_buffer.c +0 -155
  63. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  64. data/lib/puma/convenient.rb +0 -25
  65. data/lib/puma/daemon_ext.rb +0 -33
  66. data/lib/puma/delegation.rb +0 -13
  67. data/lib/puma/tcp_logger.rb +0 -41
  68. data/tools/jungle/README.md +0 -19
  69. data/tools/jungle/init.d/README.md +0 -61
  70. data/tools/jungle/init.d/puma +0 -421
  71. data/tools/jungle/init.d/run-puma +0 -18
@@ -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
@@ -22,6 +32,26 @@ 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
+ # @version 5.0.0
42
+ #
43
+ def ssl_version_state
44
+ IS_JRUBY ? [nil, nil] : @engine.ssl_vers_st
45
+ end
46
+
47
+ # Used to check the handshake status, in particular when a TCP connection
48
+ # is made with TLSv1.3 as an available protocol
49
+ # @version 5.0.0
50
+ def bad_tlsv1_3?
51
+ HAS_TLS1_3 && @engine.ssl_vers_st == ['TLSv1.3', 'SSLERR']
52
+ end
53
+ private :bad_tlsv1_3?
54
+
25
55
  def readpartial(size)
26
56
  while true
27
57
  output = @engine.read
@@ -41,6 +71,7 @@ module Puma
41
71
 
42
72
  def engine_read_all
43
73
  output = @engine.read
74
+ raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
44
75
  while output and additional_output = @engine.read
45
76
  output << additional_output
46
77
  end
@@ -107,14 +138,18 @@ module Puma
107
138
  alias_method :<<, :write
108
139
 
109
140
  # This is a temporary fix to deal with websockets code using
110
- # write_nonblock. The problem with implementing it properly
141
+ # write_nonblock.
142
+
143
+ # The problem with implementing it properly
111
144
  # is that it means we'd have to have the ability to rewind
112
145
  # an engine because after we write+extract, the socket
113
146
  # write_nonblock call might raise an exception and later
114
147
  # 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.
148
+ # it had already written the data in.
149
+ #
150
+ # So for the time being (and since write blocking is quite rare),
151
+ # go ahead and actually block in write_nonblock.
152
+ #
118
153
  def write_nonblock(data, *_)
119
154
  write data
120
155
  end
@@ -125,11 +160,14 @@ module Puma
125
160
 
126
161
  def read_and_drop(timeout = 1)
127
162
  return :timeout unless IO.select([@socket], nil, nil, timeout)
128
- return :eof unless read_nonblock(1024)
129
- :drop
130
- rescue Errno::EAGAIN
131
- # do nothing
132
- :eagain
163
+ case @socket.read_nonblock(1024, exception: false)
164
+ when nil
165
+ :eof
166
+ when :wait_readable
167
+ :eagain
168
+ else
169
+ :drop
170
+ end
133
171
  end
134
172
 
135
173
  def should_drop_bytes?
@@ -141,9 +179,7 @@ module Puma
141
179
  # Read any drop any partially initialized sockets and any received bytes during shutdown.
142
180
  # Don't let this socket hold this loop forever.
143
181
  # If it can't send more packets within 1s, then give up.
144
- while should_drop_bytes?
145
- return if [:timeout, :eof].include?(read_and_drop(1))
146
- end
182
+ return if [:timeout, :eof].include?(read_and_drop(1)) while should_drop_bytes?
147
183
  rescue IOError, SystemCallError
148
184
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
149
185
  # nothing
@@ -166,12 +202,13 @@ module Puma
166
202
  end
167
203
  end
168
204
 
169
- if defined?(JRUBY_VERSION)
205
+ if IS_JRUBY
206
+ OPENSSL_NO_SSL3 = false
207
+ OPENSSL_NO_TLS1 = false
208
+
170
209
  class SSLError < StandardError
171
210
  # Define this for jruby even though it isn't used.
172
211
  end
173
-
174
- def self.check; end
175
212
  end
176
213
 
177
214
  class Context
@@ -183,7 +220,7 @@ module Puma
183
220
  @no_tlsv1_1 = false
184
221
  end
185
222
 
186
- if defined?(JRUBY_VERSION)
223
+ if IS_JRUBY
187
224
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
188
225
  attr_reader :keystore
189
226
  attr_accessor :keystore_pass
@@ -228,13 +265,13 @@ module Puma
228
265
 
229
266
  # disables TLSv1
230
267
  def no_tlsv1=(tlsv1)
231
- raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
268
+ raise ArgumentError, "Invalid value of no_tlsv1=" unless ['true', 'false', true, false].include?(tlsv1)
232
269
  @no_tlsv1 = tlsv1
233
270
  end
234
271
 
235
272
  # disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
236
273
  def no_tlsv1_1=(tlsv1_1)
237
- raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1_1)
274
+ raise ArgumentError, "Invalid value of no_tlsv1_1=" unless ['true', 'false', true, false].include?(tlsv1_1)
238
275
  @no_tlsv1_1 = tlsv1_1
239
276
  end
240
277
 
@@ -270,6 +307,11 @@ module Puma
270
307
  Socket.new io, engine
271
308
  end
272
309
 
310
+ # @version 5.0.0
311
+ def addr
312
+ @socket.addr
313
+ end
314
+
273
315
  def close
274
316
  @socket.close unless @socket.closed? # closed? call is for Windows
275
317
  end
@@ -0,0 +1,73 @@
1
+ module Puma
2
+ module MiniSSL
3
+ class ContextBuilder
4
+ def initialize(params, events)
5
+ @params = params
6
+ @events = events
7
+ end
8
+
9
+ def context
10
+ ctx = MiniSSL::Context.new
11
+
12
+ if defined?(JRUBY_VERSION)
13
+ unless params['keystore']
14
+ events.error "Please specify the Java keystore via 'keystore='"
15
+ end
16
+
17
+ ctx.keystore = params['keystore']
18
+
19
+ unless params['keystore-pass']
20
+ events.error "Please specify the Java keystore password via 'keystore-pass='"
21
+ end
22
+
23
+ ctx.keystore_pass = params['keystore-pass']
24
+ ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
25
+ else
26
+ unless params['key']
27
+ events.error "Please specify the SSL key via 'key='"
28
+ end
29
+
30
+ ctx.key = params['key']
31
+
32
+ unless params['cert']
33
+ events.error "Please specify the SSL cert via 'cert='"
34
+ end
35
+
36
+ ctx.cert = params['cert']
37
+
38
+ if ['peer', 'force_peer'].include?(params['verify_mode'])
39
+ unless params['ca']
40
+ events.error "Please specify the SSL ca via 'ca='"
41
+ end
42
+ end
43
+
44
+ ctx.ca = params['ca'] if params['ca']
45
+ ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
46
+ end
47
+
48
+ ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
49
+ ctx.no_tlsv1_1 = true if params['no_tlsv1_1'] == 'true'
50
+
51
+ if params['verify_mode']
52
+ ctx.verify_mode = case params['verify_mode']
53
+ when "peer"
54
+ MiniSSL::VERIFY_PEER
55
+ when "force_peer"
56
+ MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
57
+ when "none"
58
+ MiniSSL::VERIFY_NONE
59
+ else
60
+ events.error "Please specify a valid verify_mode="
61
+ MiniSSL::VERIFY_NONE
62
+ end
63
+ end
64
+
65
+ ctx
66
+ end
67
+
68
+ private
69
+
70
+ attr_reader :params, :events
71
+ end
72
+ end
73
+ end
@@ -15,7 +15,7 @@ module Puma
15
15
  # Mimics IO#read with no data.
16
16
  #
17
17
  def read(count = nil, _buffer = nil)
18
- (count && count > 0) ? nil : ""
18
+ count && count > 0 ? nil : ""
19
19
  end
20
20
 
21
21
  def rewind
@@ -10,7 +10,7 @@ module Puma
10
10
 
11
11
  def create(name)
12
12
  if cls = Plugins.find(name)
13
- plugin = cls.new(Plugin)
13
+ plugin = cls.new
14
14
  @instances << plugin
15
15
  return plugin
16
16
  end
@@ -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
@@ -101,17 +104,8 @@ module Puma
101
104
  Plugins.register name, cls
102
105
  end
103
106
 
104
- def initialize(loader)
105
- @loader = loader
106
- end
107
-
108
107
  def in_background(&blk)
109
108
  Plugins.add_background blk
110
109
  end
111
-
112
- def workers_supported?
113
- return false if Puma.jruby? || Puma.windows?
114
- true
115
- end
116
110
  end
117
111
  end
@@ -67,10 +67,6 @@ module Puma::Rack
67
67
  options[:environment] = e
68
68
  }
69
69
 
70
- opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
71
- options[:daemonize] = d ? true : false
72
- }
73
-
74
70
  opts.on("-P", "--pid FILE", "file to store PID") { |f|
75
71
  options[:pid] = ::File.expand_path(f)
76
72
  }
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'puma/util'
4
- require 'puma/minissl'
4
+ require 'puma/minissl' if ::Puma::HAS_SSL
5
5
 
6
6
  require 'nio'
7
7
 
@@ -189,7 +189,12 @@ module Puma
189
189
  if submon.value == @ready
190
190
  false
191
191
  else
192
- submon.value.close
192
+ if submon.value.can_close?
193
+ submon.value.close
194
+ else
195
+ # Pass remaining open client connections to the thread pool.
196
+ @app_pool << submon.value
197
+ end
193
198
  begin
194
199
  selector.deregister submon.value
195
200
  rescue IOError
@@ -225,7 +230,7 @@ module Puma
225
230
  # will be flooding them with errors when persistent connections
226
231
  # are closed.
227
232
  rescue ConnectionError
228
- c.write_500
233
+ c.write_error(500)
229
234
  c.close
230
235
 
231
236
  clear_monitor mon
@@ -237,7 +242,8 @@ module Puma
237
242
  ssl_socket = c.io
238
243
  begin
239
244
  addr = ssl_socket.peeraddr.last
240
- rescue IOError
245
+ # EINVAL can happen when browser closes socket w/security exception
246
+ rescue IOError, Errno::EINVAL
241
247
  addr = "<unknown>"
242
248
  end
243
249
 
@@ -246,22 +252,22 @@ module Puma
246
252
  c.close
247
253
  clear_monitor mon
248
254
 
249
- @events.ssl_error @server, addr, cert, e
255
+ @events.ssl_error e, addr, cert
250
256
 
251
257
  # The client doesn't know HTTP well
252
258
  rescue HttpParserError => e
253
259
  @server.lowlevel_error(e, c.env)
254
260
 
255
- c.write_400
261
+ c.write_error(400)
256
262
  c.close
257
263
 
258
264
  clear_monitor mon
259
265
 
260
- @events.parse_error @server, c.env, e
266
+ @events.parse_error e, c
261
267
  rescue StandardError => e
262
268
  @server.lowlevel_error(e, c.env)
263
269
 
264
- c.write_500
270
+ c.write_error(500)
265
271
  c.close
266
272
 
267
273
  clear_monitor mon
@@ -277,7 +283,7 @@ module Puma
277
283
  while @timeouts.first.value.timeout_at < now
278
284
  mon = @timeouts.shift
279
285
  c = mon.value
280
- c.write_408 if c.in_data_phase
286
+ c.write_error(408) if c.in_data_phase
281
287
  c.close
282
288
 
283
289
  clear_monitor mon
@@ -307,6 +313,7 @@ module Puma
307
313
 
308
314
  def run_in_thread
309
315
  @thread = Thread.new do
316
+ Puma.set_thread_name "reactor"
310
317
  begin
311
318
  run_internal
312
319
  rescue StandardError => e
@@ -17,10 +17,6 @@ module Puma
17
17
  @started_at = Time.now
18
18
  end
19
19
 
20
- def daemon?
21
- @options[:daemon]
22
- end
23
-
24
20
  def development?
25
21
  @options[:environment] == "development"
26
22
  end
@@ -33,7 +29,8 @@ module Puma
33
29
  @events.log str
34
30
  end
35
31
 
36
- def before_restart
32
+ # @version 5.0.0
33
+ def stop_control
37
34
  @control.stop(true) if @control
38
35
  end
39
36
 
@@ -51,36 +48,27 @@ module Puma
51
48
 
52
49
  require 'puma/app/status'
53
50
 
54
- uri = URI.parse str
55
-
56
- app = Puma::App::Status.new @launcher
57
-
58
51
  if token = @options[:control_auth_token]
59
- app.auth_token = token unless token.empty? || token == 'none'
52
+ token = nil if token.empty? || token == 'none'
60
53
  end
61
54
 
55
+ app = Puma::App::Status.new @launcher, token
56
+
62
57
  control = Puma::Server.new app, @launcher.events
63
58
  control.min_threads = 0
64
59
  control.max_threads = 1
65
60
 
66
- case uri.scheme
67
- when "tcp"
68
- log "* Starting control server on #{str}"
69
- control.add_tcp_listener uri.host, uri.port
70
- when "unix"
71
- log "* Starting control server on #{str}"
72
- path = "#{uri.host}#{uri.path}"
73
- mask = @options[:control_url_umask]
74
-
75
- control.add_unix_listener path, mask
76
- else
77
- error "Invalid control URI: #{str}"
78
- end
61
+ control.binder.parse [str], self, 'Starting control server'
79
62
 
80
63
  control.run
81
64
  @control = control
82
65
  end
83
66
 
67
+ # @version 5.0.0
68
+ def close_control_listeners
69
+ @control.binder.close_listeners if @control
70
+ end
71
+
84
72
  def ruby_engine
85
73
  if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
86
74
  "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
@@ -101,10 +89,6 @@ module Puma
101
89
  log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
102
90
  log "* Min threads: #{min_t}, max threads: #{max_t}"
103
91
  log "* Environment: #{ENV['RACK_ENV']}"
104
-
105
- if @options[:mode] == :tcp
106
- log "* Mode: Lopez Express (tcp)"
107
- end
108
92
  end
109
93
 
110
94
  def redirected_io?
@@ -143,7 +127,6 @@ module Puma
143
127
  exit 1
144
128
  end
145
129
 
146
- # Load the app before we daemonize.
147
130
  begin
148
131
  @app = @launcher.config.app
149
132
  rescue Exception => e
@@ -167,10 +150,6 @@ module Puma
167
150
  server.max_threads = max_t
168
151
  server.inherit_binder @launcher.binder
169
152
 
170
- if @options[:mode] == :tcp
171
- server.tcp_mode!
172
- end
173
-
174
153
  if @options[:early_hints]
175
154
  server.early_hints = true
176
155
  end