puma 3.12.0 → 5.3.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.

Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1413 -439
  3. data/LICENSE +23 -20
  4. data/README.md +131 -60
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +24 -19
  7. data/docs/compile_options.md +19 -0
  8. data/docs/deployment.md +38 -13
  9. data/docs/fork_worker.md +33 -0
  10. data/docs/jungle/README.md +9 -0
  11. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  12. data/{tools → docs}/jungle/rc.d/puma +2 -2
  13. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  14. data/docs/kubernetes.md +66 -0
  15. data/docs/nginx.md +1 -1
  16. data/docs/plugins.md +20 -10
  17. data/docs/rails_dev_mode.md +29 -0
  18. data/docs/restart.md +47 -22
  19. data/docs/signals.md +7 -6
  20. data/docs/stats.md +142 -0
  21. data/docs/systemd.md +48 -70
  22. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  23. data/ext/puma_http11/ext_help.h +1 -1
  24. data/ext/puma_http11/extconf.rb +27 -0
  25. data/ext/puma_http11/http11_parser.c +84 -109
  26. data/ext/puma_http11/http11_parser.h +1 -1
  27. data/ext/puma_http11/http11_parser.java.rl +22 -38
  28. data/ext/puma_http11/http11_parser.rl +4 -2
  29. data/ext/puma_http11/http11_parser_common.rl +3 -3
  30. data/ext/puma_http11/mini_ssl.c +262 -87
  31. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  32. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  33. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +89 -106
  34. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +92 -22
  35. data/ext/puma_http11/puma_http11.c +34 -50
  36. data/lib/puma/app/status.rb +68 -49
  37. data/lib/puma/binder.rb +197 -144
  38. data/lib/puma/cli.rb +17 -15
  39. data/lib/puma/client.rb +257 -226
  40. data/lib/puma/cluster/worker.rb +176 -0
  41. data/lib/puma/cluster/worker_handle.rb +90 -0
  42. data/lib/puma/cluster.rb +223 -212
  43. data/lib/puma/commonlogger.rb +4 -2
  44. data/lib/puma/configuration.rb +58 -51
  45. data/lib/puma/const.rb +41 -19
  46. data/lib/puma/control_cli.rb +117 -73
  47. data/lib/puma/detect.rb +26 -3
  48. data/lib/puma/dsl.rb +531 -123
  49. data/lib/puma/error_logger.rb +104 -0
  50. data/lib/puma/events.rb +57 -31
  51. data/lib/puma/io_buffer.rb +9 -5
  52. data/lib/puma/jruby_restart.rb +2 -58
  53. data/lib/puma/json.rb +96 -0
  54. data/lib/puma/launcher.rb +182 -70
  55. data/lib/puma/minissl/context_builder.rb +79 -0
  56. data/lib/puma/minissl.rb +149 -48
  57. data/lib/puma/null_io.rb +15 -1
  58. data/lib/puma/plugin/tmp_restart.rb +2 -0
  59. data/lib/puma/plugin.rb +8 -12
  60. data/lib/puma/queue_close.rb +26 -0
  61. data/lib/puma/rack/builder.rb +4 -5
  62. data/lib/puma/rack/urlmap.rb +2 -0
  63. data/lib/puma/rack_default.rb +2 -0
  64. data/lib/puma/reactor.rb +87 -316
  65. data/lib/puma/request.rb +456 -0
  66. data/lib/puma/runner.rb +33 -52
  67. data/lib/puma/server.rb +288 -679
  68. data/lib/puma/single.rb +13 -67
  69. data/lib/puma/state_file.rb +10 -3
  70. data/lib/puma/systemd.rb +46 -0
  71. data/lib/puma/thread_pool.rb +131 -81
  72. data/lib/puma/util.rb +14 -6
  73. data/lib/puma.rb +54 -0
  74. data/lib/rack/handler/puma.rb +8 -6
  75. data/tools/Dockerfile +16 -0
  76. data/tools/trickletest.rb +0 -1
  77. metadata +45 -29
  78. data/ext/puma_http11/io_buffer.c +0 -155
  79. data/lib/puma/accept_nonblock.rb +0 -23
  80. data/lib/puma/compat.rb +0 -14
  81. data/lib/puma/convenient.rb +0 -23
  82. data/lib/puma/daemon_ext.rb +0 -31
  83. data/lib/puma/delegation.rb +0 -11
  84. data/lib/puma/java_io_buffer.rb +0 -45
  85. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  86. data/lib/puma/tcp_logger.rb +0 -39
  87. data/tools/jungle/README.md +0 -19
  88. data/tools/jungle/init.d/README.md +0 -61
  89. data/tools/jungle/init.d/puma +0 -421
  90. data/tools/jungle/init.d/run-puma +0 -18
  91. data/tools/jungle/upstart/README.md +0 -61
  92. data/tools/jungle/upstart/puma-manager.conf +0 -31
  93. data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/minissl.rb CHANGED
@@ -1,10 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
4
  require 'io/wait'
3
- rescue LoadError
5
+ rescue LoadError
4
6
  end
5
7
 
8
+ # need for Puma::MiniSSL::OPENSSL constants used in `HAS_TLS1_3`
9
+ require 'puma/puma_http11'
10
+
6
11
  module Puma
7
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
+
8
20
  class Socket
9
21
  def initialize(socket, engine)
10
22
  @socket = socket
@@ -12,6 +24,7 @@ module Puma
12
24
  @peercert = nil
13
25
  end
14
26
 
27
+ # @!attribute [r] to_io
15
28
  def to_io
16
29
  @socket
17
30
  end
@@ -20,6 +33,27 @@ module Puma
20
33
  @socket.closed?
21
34
  end
22
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
+
23
57
  def readpartial(size)
24
58
  while true
25
59
  output = @engine.read
@@ -52,22 +86,22 @@ module Puma
52
86
  output = engine_read_all
53
87
  return output if output
54
88
 
55
- begin
56
- data = @socket.read_nonblock(size, exception: false)
57
- if data == :wait_readable || data == :wait_writable
58
- if @socket.to_io.respond_to?(data)
59
- @socket.to_io.__send__(data)
60
- elsif data == :wait_readable
61
- IO.select([@socket.to_io])
62
- else
63
- IO.select(nil, [@socket.to_io])
64
- end
65
- elsif !data
66
- return nil
67
- else
68
- break
69
- end
70
- end while true
89
+ data = @socket.read_nonblock(size, exception: false)
90
+ if data == :wait_readable || data == :wait_writable
91
+ # It would make more sense to let @socket.read_nonblock raise
92
+ # EAGAIN if necessary but it seems like it'll misbehave on Windows.
93
+ # I don't have a Windows machine to debug this so I can't explain
94
+ # exactly whats happening in that OS. Please let me know if you
95
+ # find out!
96
+ #
97
+ # In the meantime, we can emulate the correct behavior by
98
+ # capturing :wait_readable & :wait_writable and raising EAGAIN
99
+ # ourselves.
100
+ raise IO::EAGAINWaitReadable
101
+ elsif data.nil?
102
+ raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
103
+ return nil
104
+ end
71
105
 
72
106
  @engine.inject(data)
73
107
  output = engine_read_all
@@ -83,22 +117,23 @@ module Puma
83
117
  def write(data)
84
118
  return 0 if data.empty?
85
119
 
86
- need = data.bytesize
120
+ data_size = data.bytesize
121
+ need = data_size
87
122
 
88
123
  while true
89
124
  wrote = @engine.write data
90
- enc = @engine.extract
91
125
 
92
- while enc
93
- @socket.write enc
94
- enc = @engine.extract
126
+ enc_wr = ''.dup
127
+ while (enc = @engine.extract)
128
+ enc_wr << enc
95
129
  end
130
+ @socket.write enc_wr unless enc_wr.empty?
96
131
 
97
132
  need -= wrote
98
133
 
99
- return data.bytesize if need == 0
134
+ return data_size if need == 0
100
135
 
101
- data = data[wrote..-1]
136
+ data = data.byteslice(wrote..-1)
102
137
  end
103
138
  end
104
139
 
@@ -106,14 +141,18 @@ module Puma
106
141
  alias_method :<<, :write
107
142
 
108
143
  # This is a temporary fix to deal with websockets code using
109
- # write_nonblock. The problem with implementing it properly
144
+ # write_nonblock.
145
+
146
+ # The problem with implementing it properly
110
147
  # is that it means we'd have to have the ability to rewind
111
148
  # an engine because after we write+extract, the socket
112
149
  # write_nonblock call might raise an exception and later
113
150
  # code would pass the same data in, but the engine would think
114
- # it had already written the data in. So for the time being
115
- # (and since write blocking is quite rare), go ahead and actually
116
- # block in write_nonblock.
151
+ # it had already written the data in.
152
+ #
153
+ # So for the time being (and since write blocking is quite rare),
154
+ # go ahead and actually block in write_nonblock.
155
+ #
117
156
  def write_nonblock(data, *_)
118
157
  write data
119
158
  end
@@ -124,11 +163,14 @@ module Puma
124
163
 
125
164
  def read_and_drop(timeout = 1)
126
165
  return :timeout unless IO.select([@socket], nil, nil, timeout)
127
- return :eof unless read_nonblock(1024)
128
- :drop
129
- rescue Errno::EAGAIN
130
- # do nothing
131
- :eagain
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
132
174
  end
133
175
 
134
176
  def should_drop_bytes?
@@ -140,9 +182,7 @@ module Puma
140
182
  # Read any drop any partially initialized sockets and any received bytes during shutdown.
141
183
  # Don't let this socket hold this loop forever.
142
184
  # If it can't send more packets within 1s, then give up.
143
- while should_drop_bytes?
144
- return if [:timeout, :eof].include?(read_and_drop(1))
145
- end
185
+ return if [:timeout, :eof].include?(read_and_drop(1)) while should_drop_bytes?
146
186
  rescue IOError, SystemCallError
147
187
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
148
188
  # nothing
@@ -151,10 +191,12 @@ module Puma
151
191
  end
152
192
  end
153
193
 
194
+ # @!attribute [r] peeraddr
154
195
  def peeraddr
155
196
  @socket.peeraddr
156
197
  end
157
198
 
199
+ # @!attribute [r] peercert
158
200
  def peercert
159
201
  return @peercert if @peercert
160
202
 
@@ -165,18 +207,25 @@ module Puma
165
207
  end
166
208
  end
167
209
 
168
- if defined?(JRUBY_VERSION)
210
+ if IS_JRUBY
211
+ OPENSSL_NO_SSL3 = false
212
+ OPENSSL_NO_TLS1 = false
213
+
169
214
  class SSLError < StandardError
170
215
  # Define this for jruby even though it isn't used.
171
216
  end
172
-
173
- def self.check; end
174
217
  end
175
218
 
176
219
  class Context
177
220
  attr_accessor :verify_mode
221
+ attr_reader :no_tlsv1, :no_tlsv1_1
222
+
223
+ def initialize
224
+ @no_tlsv1 = false
225
+ @no_tlsv1_1 = false
226
+ end
178
227
 
179
- if defined?(JRUBY_VERSION)
228
+ if IS_JRUBY
180
229
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
181
230
  attr_reader :keystore
182
231
  attr_accessor :keystore_pass
@@ -197,6 +246,7 @@ module Puma
197
246
  attr_reader :cert
198
247
  attr_reader :ca
199
248
  attr_accessor :ssl_cipher_filter
249
+ attr_accessor :verification_flags
200
250
 
201
251
  def key=(key)
202
252
  raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
@@ -218,41 +268,92 @@ module Puma
218
268
  raise "Cert not configured" unless @cert
219
269
  end
220
270
  end
271
+
272
+ # disables TLSv1
273
+ # @!attribute [w] no_tlsv1=
274
+ def no_tlsv1=(tlsv1)
275
+ raise ArgumentError, "Invalid value of no_tlsv1=" unless ['true', 'false', true, false].include?(tlsv1)
276
+ @no_tlsv1 = tlsv1
277
+ end
278
+
279
+ # disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
280
+ # @!attribute [w] no_tlsv1_1=
281
+ def no_tlsv1_1=(tlsv1_1)
282
+ raise ArgumentError, "Invalid value of no_tlsv1_1=" unless ['true', 'false', true, false].include?(tlsv1_1)
283
+ @no_tlsv1_1 = tlsv1_1
284
+ end
285
+
221
286
  end
222
287
 
223
288
  VERIFY_NONE = 0
224
289
  VERIFY_PEER = 1
225
290
  VERIFY_FAIL_IF_NO_PEER_CERT = 2
226
291
 
292
+ # https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in
293
+ # /* Certificate verify flags */
294
+ VERIFICATION_FLAGS = {
295
+ "USE_CHECK_TIME" => 0x2,
296
+ "CRL_CHECK" => 0x4,
297
+ "CRL_CHECK_ALL" => 0x8,
298
+ "IGNORE_CRITICAL" => 0x10,
299
+ "X509_STRICT" => 0x20,
300
+ "ALLOW_PROXY_CERTS" => 0x40,
301
+ "POLICY_CHECK" => 0x80,
302
+ "EXPLICIT_POLICY" => 0x100,
303
+ "INHIBIT_ANY" => 0x200,
304
+ "INHIBIT_MAP" => 0x400,
305
+ "NOTIFY_POLICY" => 0x800,
306
+ "EXTENDED_CRL_SUPPORT" => 0x1000,
307
+ "USE_DELTAS" => 0x2000,
308
+ "CHECK_SS_SIGNATURE" => 0x4000,
309
+ "TRUSTED_FIRST" => 0x8000,
310
+ "SUITEB_128_LOS_ONLY" => 0x10000,
311
+ "SUITEB_192_LOS" => 0x20000,
312
+ "SUITEB_128_LOS" => 0x30000,
313
+ "PARTIAL_CHAIN" => 0x80000,
314
+ "NO_ALT_CHAINS" => 0x100000,
315
+ "NO_CHECK_TIME" => 0x200000
316
+ }.freeze
317
+
227
318
  class Server
228
319
  def initialize(socket, ctx)
229
320
  @socket = socket
230
321
  @ctx = ctx
231
- end
232
-
233
- def to_io
234
- @socket
322
+ @eng_ctx = IS_JRUBY ? @ctx : SSLContext.new(ctx)
235
323
  end
236
324
 
237
325
  def accept
238
326
  @ctx.check
239
327
  io = @socket.accept
240
- engine = Engine.server @ctx
241
-
328
+ engine = Engine.server @eng_ctx
242
329
  Socket.new io, engine
243
330
  end
244
331
 
245
332
  def accept_nonblock
246
333
  @ctx.check
247
334
  io = @socket.accept_nonblock
248
- engine = Engine.server @ctx
249
-
335
+ engine = Engine.server @eng_ctx
250
336
  Socket.new io, engine
251
337
  end
252
338
 
339
+ # @!attribute [r] to_io
340
+ def to_io
341
+ @socket
342
+ end
343
+
344
+ # @!attribute [r] addr
345
+ # @version 5.0.0
346
+ def addr
347
+ @socket.addr
348
+ end
349
+
253
350
  def close
254
351
  @socket.close unless @socket.closed? # closed? call is for Windows
255
352
  end
353
+
354
+ def closed?
355
+ @socket.closed?
356
+ end
256
357
  end
257
358
  end
258
359
  end
data/lib/puma/null_io.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  # Provides an IO-like object that always appears to contain no data.
3
5
  # Used as the value for rack.input when the request has no body.
@@ -7,13 +9,17 @@ module Puma
7
9
  nil
8
10
  end
9
11
 
12
+ def string
13
+ ""
14
+ end
15
+
10
16
  def each
11
17
  end
12
18
 
13
19
  # Mimics IO#read with no data.
14
20
  #
15
21
  def read(count = nil, _buffer = nil)
16
- (count && count > 0) ? nil : ""
22
+ count && count > 0 ? nil : ""
17
23
  end
18
24
 
19
25
  def rewind
@@ -30,6 +36,10 @@ module Puma
30
36
  true
31
37
  end
32
38
 
39
+ def sync
40
+ true
41
+ end
42
+
33
43
  def sync=(v)
34
44
  end
35
45
 
@@ -38,5 +48,9 @@ module Puma
38
48
 
39
49
  def write(*ary)
40
50
  end
51
+
52
+ def flush
53
+ self
54
+ end
41
55
  end
42
56
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/plugin'
2
4
 
3
5
  Puma::Plugin.create do
data/lib/puma/plugin.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  class UnknownPlugin < RuntimeError; end
3
5
 
@@ -8,7 +10,7 @@ module Puma
8
10
 
9
11
  def create(name)
10
12
  if cls = Plugins.find(name)
11
- plugin = cls.new(Plugin)
13
+ plugin = cls.new
12
14
  @instances << plugin
13
15
  return plugin
14
16
  end
@@ -60,8 +62,11 @@ module Puma
60
62
  end
61
63
 
62
64
  def fire_background
63
- @background.each do |b|
64
- 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
65
70
  end
66
71
  end
67
72
  end
@@ -99,17 +104,8 @@ module Puma
99
104
  Plugins.register name, cls
100
105
  end
101
106
 
102
- def initialize(loader)
103
- @loader = loader
104
- end
105
-
106
107
  def in_background(&blk)
107
108
  Plugins.add_background blk
108
109
  end
109
-
110
- def workers_supported?
111
- return false if Puma.jruby? || Puma.windows?
112
- true
113
- end
114
110
  end
115
111
  end
@@ -0,0 +1,26 @@
1
+ class ClosedQueueError < StandardError; end
2
+ module Puma
3
+
4
+ # Queue#close was added in Ruby 2.3.
5
+ # Add a simple implementation for earlier Ruby versions.
6
+ #
7
+ module QueueClose
8
+ def close
9
+ num_waiting.times {push nil}
10
+ @closed = true
11
+ end
12
+ def closed?
13
+ @closed ||= false
14
+ end
15
+ def push(object)
16
+ raise ClosedQueueError if closed?
17
+ super
18
+ end
19
+ alias << push
20
+ def pop(non_block=false)
21
+ return nil if !non_block && closed? && empty?
22
+ super
23
+ end
24
+ end
25
+ ::Queue.prepend QueueClose
26
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  end
3
5
 
@@ -65,10 +67,6 @@ module Puma::Rack
65
67
  options[:environment] = e
66
68
  }
67
69
 
68
- opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
69
- options[:daemonize] = d ? true : false
70
- }
71
-
72
70
  opts.on("-P", "--pid FILE", "file to store PID") { |f|
73
71
  options[:pid] = ::File.expand_path(f)
74
72
  }
@@ -110,7 +108,8 @@ module Puma::Rack
110
108
 
111
109
  has_options = false
112
110
  server.valid_options.each do |name, description|
113
- next if name.to_s.match(/^(Host|Port)[^a-zA-Z]/) # ignore handler's host and port options, we do our own.
111
+ next if name.to_s =~ /^(Host|Port)[^a-zA-Z]/ # ignore handler's host and port options, we do our own.
112
+
114
113
  info << " -O %-21s %s" % [name, description]
115
114
  has_options = true
116
115
  end
@@ -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