puma 4.3.12 → 5.6.4

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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1461 -524
  3. data/LICENSE +23 -20
  4. data/README.md +120 -36
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +63 -26
  7. data/docs/compile_options.md +21 -0
  8. data/docs/deployment.md +60 -69
  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 +15 -15
  17. data/docs/rails_dev_mode.md +28 -0
  18. data/docs/restart.md +46 -23
  19. data/docs/signals.md +13 -11
  20. data/docs/stats.md +142 -0
  21. data/docs/systemd.md +85 -128
  22. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  23. data/ext/puma_http11/ext_help.h +1 -1
  24. data/ext/puma_http11/extconf.rb +38 -9
  25. data/ext/puma_http11/http11_parser.c +45 -47
  26. data/ext/puma_http11/http11_parser.h +1 -1
  27. data/ext/puma_http11/http11_parser.java.rl +1 -1
  28. data/ext/puma_http11/http11_parser.rl +1 -1
  29. data/ext/puma_http11/mini_ssl.c +204 -86
  30. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  31. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  32. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +3 -5
  33. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +105 -61
  34. data/ext/puma_http11/puma_http11.c +32 -51
  35. data/lib/puma/app/status.rb +47 -36
  36. data/lib/puma/binder.rb +225 -106
  37. data/lib/puma/cli.rb +24 -18
  38. data/lib/puma/client.rb +104 -76
  39. data/lib/puma/cluster/worker.rb +173 -0
  40. data/lib/puma/cluster/worker_handle.rb +94 -0
  41. data/lib/puma/cluster.rb +212 -220
  42. data/lib/puma/commonlogger.rb +2 -2
  43. data/lib/puma/configuration.rb +58 -49
  44. data/lib/puma/const.rb +13 -6
  45. data/lib/puma/control_cli.rb +93 -76
  46. data/lib/puma/detect.rb +29 -2
  47. data/lib/puma/dsl.rb +364 -96
  48. data/lib/puma/error_logger.rb +104 -0
  49. data/lib/puma/events.rb +55 -34
  50. data/lib/puma/io_buffer.rb +9 -2
  51. data/lib/puma/jruby_restart.rb +0 -58
  52. data/lib/puma/json_serialization.rb +96 -0
  53. data/lib/puma/launcher.rb +117 -46
  54. data/lib/puma/minissl/context_builder.rb +14 -9
  55. data/lib/puma/minissl.rb +128 -46
  56. data/lib/puma/null_io.rb +13 -1
  57. data/lib/puma/plugin.rb +3 -12
  58. data/lib/puma/queue_close.rb +26 -0
  59. data/lib/puma/rack/builder.rb +1 -5
  60. data/lib/puma/reactor.rb +85 -369
  61. data/lib/puma/request.rb +472 -0
  62. data/lib/puma/runner.rb +46 -61
  63. data/lib/puma/server.rb +290 -763
  64. data/lib/puma/single.rb +9 -65
  65. data/lib/puma/state_file.rb +47 -8
  66. data/lib/puma/systemd.rb +46 -0
  67. data/lib/puma/thread_pool.rb +125 -57
  68. data/lib/puma/util.rb +20 -1
  69. data/lib/puma.rb +46 -0
  70. data/lib/rack/handler/puma.rb +2 -3
  71. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  72. metadata +26 -22
  73. data/docs/tcp_mode.md +0 -96
  74. data/ext/puma_http11/io_buffer.c +0 -155
  75. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  76. data/lib/puma/accept_nonblock.rb +0 -29
  77. data/lib/puma/tcp_logger.rb +0 -41
  78. data/tools/jungle/README.md +0 -19
  79. data/tools/jungle/init.d/README.md +0 -61
  80. data/tools/jungle/init.d/puma +0 -421
  81. data/tools/jungle/init.d/run-puma +0 -18
  82. data/tools/jungle/upstart/README.md +0 -61
  83. data/tools/jungle/upstart/puma-manager.conf +0 -31
  84. data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/minissl.rb CHANGED
@@ -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
@@ -67,6 +99,7 @@ module Puma
67
99
  # ourselves.
68
100
  raise IO::EAGAINWaitReadable
69
101
  elsif data.nil?
102
+ raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
70
103
  return nil
71
104
  end
72
105
 
@@ -84,22 +117,23 @@ module Puma
84
117
  def write(data)
85
118
  return 0 if data.empty?
86
119
 
87
- need = data.bytesize
120
+ data_size = data.bytesize
121
+ need = data_size
88
122
 
89
123
  while true
90
124
  wrote = @engine.write data
91
- enc = @engine.extract
92
125
 
93
- while enc
94
- @socket.write enc
95
- enc = @engine.extract
126
+ enc_wr = ''.dup
127
+ while (enc = @engine.extract)
128
+ enc_wr << enc
96
129
  end
130
+ @socket.write enc_wr unless enc_wr.empty?
97
131
 
98
132
  need -= wrote
99
133
 
100
- return data.bytesize if need == 0
134
+ return data_size if need == 0
101
135
 
102
- data = data[wrote..-1]
136
+ data = data.byteslice(wrote..-1)
103
137
  end
104
138
  end
105
139
 
@@ -107,14 +141,18 @@ module Puma
107
141
  alias_method :<<, :write
108
142
 
109
143
  # This is a temporary fix to deal with websockets code using
110
- # write_nonblock. The problem with implementing it properly
144
+ # write_nonblock.
145
+
146
+ # The problem with implementing it properly
111
147
  # is that it means we'd have to have the ability to rewind
112
148
  # an engine because after we write+extract, the socket
113
149
  # write_nonblock call might raise an exception and later
114
150
  # 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.
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
+ #
118
156
  def write_nonblock(data, *_)
119
157
  write data
120
158
  end
@@ -123,39 +161,27 @@ module Puma
123
161
  @socket.flush
124
162
  end
125
163
 
126
- def read_and_drop(timeout = 1)
127
- 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
133
- end
134
-
135
- def should_drop_bytes?
136
- @engine.init? || !@engine.shutdown
137
- end
138
-
139
164
  def close
140
165
  begin
141
- # Read any drop any partially initialized sockets and any received bytes during shutdown.
142
- # Don't let this socket hold this loop forever.
143
- # 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))
166
+ unless @engine.shutdown
167
+ while alert_data = @engine.extract
168
+ @socket.write alert_data
169
+ end
146
170
  end
147
171
  rescue IOError, SystemCallError
148
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
172
+ Puma::Util.purge_interrupt_queue
149
173
  # nothing
150
174
  ensure
151
175
  @socket.close
152
176
  end
153
177
  end
154
178
 
179
+ # @!attribute [r] peeraddr
155
180
  def peeraddr
156
181
  @socket.peeraddr
157
182
  end
158
183
 
184
+ # @!attribute [r] peercert
159
185
  def peercert
160
186
  return @peercert if @peercert
161
187
 
@@ -166,12 +192,13 @@ module Puma
166
192
  end
167
193
  end
168
194
 
169
- if defined?(JRUBY_VERSION)
195
+ if IS_JRUBY
196
+ OPENSSL_NO_SSL3 = false
197
+ OPENSSL_NO_TLS1 = false
198
+
170
199
  class SSLError < StandardError
171
200
  # Define this for jruby even though it isn't used.
172
201
  end
173
-
174
- def self.check; end
175
202
  end
176
203
 
177
204
  class Context
@@ -181,9 +208,13 @@ module Puma
181
208
  def initialize
182
209
  @no_tlsv1 = false
183
210
  @no_tlsv1_1 = false
211
+ @key = nil
212
+ @cert = nil
213
+ @key_pem = nil
214
+ @cert_pem = nil
184
215
  end
185
216
 
186
- if defined?(JRUBY_VERSION)
217
+ if IS_JRUBY
187
218
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
188
219
  attr_reader :keystore
189
220
  attr_accessor :keystore_pass
@@ -203,7 +234,10 @@ module Puma
203
234
  attr_reader :key
204
235
  attr_reader :cert
205
236
  attr_reader :ca
237
+ attr_reader :cert_pem
238
+ attr_reader :key_pem
206
239
  attr_accessor :ssl_cipher_filter
240
+ attr_accessor :verification_flags
207
241
 
208
242
  def key=(key)
209
243
  raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
@@ -220,21 +254,33 @@ module Puma
220
254
  @ca = ca
221
255
  end
222
256
 
257
+ def cert_pem=(cert_pem)
258
+ raise ArgumentError, "'cert_pem' is not a String" unless cert_pem.is_a? String
259
+ @cert_pem = cert_pem
260
+ end
261
+
262
+ def key_pem=(key_pem)
263
+ raise ArgumentError, "'key_pem' is not a String" unless key_pem.is_a? String
264
+ @key_pem = key_pem
265
+ end
266
+
223
267
  def check
224
- raise "Key not configured" unless @key
225
- raise "Cert not configured" unless @cert
268
+ raise "Key not configured" if @key.nil? && @key_pem.nil?
269
+ raise "Cert not configured" if @cert.nil? && @cert_pem.nil?
226
270
  end
227
271
  end
228
272
 
229
273
  # disables TLSv1
274
+ # @!attribute [w] no_tlsv1=
230
275
  def no_tlsv1=(tlsv1)
231
- raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
276
+ raise ArgumentError, "Invalid value of no_tlsv1=" unless ['true', 'false', true, false].include?(tlsv1)
232
277
  @no_tlsv1 = tlsv1
233
278
  end
234
279
 
235
280
  # disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
281
+ # @!attribute [w] no_tlsv1_1=
236
282
  def no_tlsv1_1=(tlsv1_1)
237
- raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1_1)
283
+ raise ArgumentError, "Invalid value of no_tlsv1_1=" unless ['true', 'false', true, false].include?(tlsv1_1)
238
284
  @no_tlsv1_1 = tlsv1_1
239
285
  end
240
286
 
@@ -244,35 +290,71 @@ module Puma
244
290
  VERIFY_PEER = 1
245
291
  VERIFY_FAIL_IF_NO_PEER_CERT = 2
246
292
 
293
+ # https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in
294
+ # /* Certificate verify flags */
295
+ VERIFICATION_FLAGS = {
296
+ "USE_CHECK_TIME" => 0x2,
297
+ "CRL_CHECK" => 0x4,
298
+ "CRL_CHECK_ALL" => 0x8,
299
+ "IGNORE_CRITICAL" => 0x10,
300
+ "X509_STRICT" => 0x20,
301
+ "ALLOW_PROXY_CERTS" => 0x40,
302
+ "POLICY_CHECK" => 0x80,
303
+ "EXPLICIT_POLICY" => 0x100,
304
+ "INHIBIT_ANY" => 0x200,
305
+ "INHIBIT_MAP" => 0x400,
306
+ "NOTIFY_POLICY" => 0x800,
307
+ "EXTENDED_CRL_SUPPORT" => 0x1000,
308
+ "USE_DELTAS" => 0x2000,
309
+ "CHECK_SS_SIGNATURE" => 0x4000,
310
+ "TRUSTED_FIRST" => 0x8000,
311
+ "SUITEB_128_LOS_ONLY" => 0x10000,
312
+ "SUITEB_192_LOS" => 0x20000,
313
+ "SUITEB_128_LOS" => 0x30000,
314
+ "PARTIAL_CHAIN" => 0x80000,
315
+ "NO_ALT_CHAINS" => 0x100000,
316
+ "NO_CHECK_TIME" => 0x200000
317
+ }.freeze
318
+
247
319
  class Server
248
320
  def initialize(socket, ctx)
249
321
  @socket = socket
250
322
  @ctx = ctx
251
- end
252
-
253
- def to_io
254
- @socket
323
+ @eng_ctx = IS_JRUBY ? @ctx : SSLContext.new(ctx)
255
324
  end
256
325
 
257
326
  def accept
258
327
  @ctx.check
259
328
  io = @socket.accept
260
- engine = Engine.server @ctx
261
-
329
+ engine = Engine.server @eng_ctx
262
330
  Socket.new io, engine
263
331
  end
264
332
 
265
333
  def accept_nonblock
266
334
  @ctx.check
267
335
  io = @socket.accept_nonblock
268
- engine = Engine.server @ctx
269
-
336
+ engine = Engine.server @eng_ctx
270
337
  Socket.new io, engine
271
338
  end
272
339
 
340
+ # @!attribute [r] to_io
341
+ def to_io
342
+ @socket
343
+ end
344
+
345
+ # @!attribute [r] addr
346
+ # @version 5.0.0
347
+ def addr
348
+ @socket.addr
349
+ end
350
+
273
351
  def close
274
352
  @socket.close unless @socket.closed? # closed? call is for Windows
275
353
  end
354
+
355
+ def closed?
356
+ @socket.closed?
357
+ end
276
358
  end
277
359
  end
278
360
  end
data/lib/puma/null_io.rb CHANGED
@@ -9,13 +9,17 @@ module Puma
9
9
  nil
10
10
  end
11
11
 
12
+ def string
13
+ ""
14
+ end
15
+
12
16
  def each
13
17
  end
14
18
 
15
19
  # Mimics IO#read with no data.
16
20
  #
17
21
  def read(count = nil, _buffer = nil)
18
- (count && count > 0) ? nil : ""
22
+ count && count > 0 ? nil : ""
19
23
  end
20
24
 
21
25
  def rewind
@@ -32,6 +36,10 @@ module Puma
32
36
  true
33
37
  end
34
38
 
39
+ def sync
40
+ true
41
+ end
42
+
35
43
  def sync=(v)
36
44
  end
37
45
 
@@ -40,5 +48,9 @@ module Puma
40
48
 
41
49
  def write(*ary)
42
50
  end
51
+
52
+ def flush
53
+ self
54
+ end
43
55
  end
44
56
  end
data/lib/puma/plugin.rb CHANGED
@@ -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
@@ -64,7 +64,7 @@ module Puma
64
64
  def fire_background
65
65
  @background.each_with_index do |b, i|
66
66
  Thread.new do
67
- Puma.set_thread_name "plugin background #{i}"
67
+ Puma.set_thread_name "plgn bg #{i}"
68
68
  b.call
69
69
  end
70
70
  end
@@ -91,7 +91,7 @@ module Puma
91
91
  path = ary.first[CALLER_FILE]
92
92
 
93
93
  m = %r!puma/plugin/([^/]*)\.rb$!.match(path)
94
- return m[1]
94
+ m[1]
95
95
  end
96
96
 
97
97
  def self.create(&blk)
@@ -104,17 +104,8 @@ module Puma
104
104
  Plugins.register name, cls
105
105
  end
106
106
 
107
- def initialize(loader)
108
- @loader = loader
109
- end
110
-
111
107
  def in_background(&blk)
112
108
  Plugins.add_background blk
113
109
  end
114
-
115
- def workers_supported?
116
- return false if Puma.jruby? || Puma.windows?
117
- true
118
- end
119
110
  end
120
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
@@ -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
  }
@@ -169,7 +165,7 @@ module Puma::Rack
169
165
  require config
170
166
  app = Object.const_get(::File.basename(config, '.rb').capitalize)
171
167
  end
172
- return app, options
168
+ [app, options]
173
169
  end
174
170
 
175
171
  def self.new_from_string(builder_script, file="(rackup)")