puma 3.12.1 → 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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1553 -447
  3. data/LICENSE +23 -20
  4. data/README.md +175 -63
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +59 -21
  7. data/docs/compile_options.md +21 -0
  8. data/docs/deployment.md +69 -58
  9. data/docs/fork_worker.md +33 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +9 -0
  14. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  15. data/{tools → docs}/jungle/rc.d/puma +2 -2
  16. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  17. data/docs/kubernetes.md +66 -0
  18. data/docs/nginx.md +1 -1
  19. data/docs/plugins.md +22 -12
  20. data/docs/rails_dev_mode.md +28 -0
  21. data/docs/restart.md +47 -22
  22. data/docs/signals.md +13 -11
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +95 -120
  25. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  26. data/ext/puma_http11/ext_help.h +1 -1
  27. data/ext/puma_http11/extconf.rb +51 -1
  28. data/ext/puma_http11/http11_parser.c +105 -117
  29. data/ext/puma_http11/http11_parser.h +1 -1
  30. data/ext/puma_http11/http11_parser.java.rl +22 -38
  31. data/ext/puma_http11/http11_parser.rl +4 -2
  32. data/ext/puma_http11/http11_parser_common.rl +4 -4
  33. data/ext/puma_http11/mini_ssl.c +319 -96
  34. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  36. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
  37. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +120 -65
  38. data/ext/puma_http11/puma_http11.c +35 -51
  39. data/lib/puma/app/status.rb +68 -49
  40. data/lib/puma/binder.rb +234 -137
  41. data/lib/puma/cli.rb +28 -18
  42. data/lib/puma/client.rb +343 -230
  43. data/lib/puma/cluster/worker.rb +173 -0
  44. data/lib/puma/cluster/worker_handle.rb +94 -0
  45. data/lib/puma/cluster.rb +247 -232
  46. data/lib/puma/commonlogger.rb +2 -2
  47. data/lib/puma/configuration.rb +61 -51
  48. data/lib/puma/const.rb +42 -21
  49. data/lib/puma/control_cli.rb +109 -67
  50. data/lib/puma/detect.rb +29 -2
  51. data/lib/puma/dsl.rb +615 -123
  52. data/lib/puma/error_logger.rb +104 -0
  53. data/lib/puma/events.rb +55 -31
  54. data/lib/puma/io_buffer.rb +7 -5
  55. data/lib/puma/jruby_restart.rb +0 -58
  56. data/lib/puma/json_serialization.rb +96 -0
  57. data/lib/puma/launcher.rb +182 -69
  58. data/lib/puma/minissl/context_builder.rb +81 -0
  59. data/lib/puma/minissl.rb +161 -61
  60. data/lib/puma/null_io.rb +13 -1
  61. data/lib/puma/plugin/tmp_restart.rb +2 -0
  62. data/lib/puma/plugin.rb +7 -13
  63. data/lib/puma/queue_close.rb +26 -0
  64. data/lib/puma/rack/builder.rb +3 -5
  65. data/lib/puma/rack/urlmap.rb +2 -0
  66. data/lib/puma/rack_default.rb +2 -0
  67. data/lib/puma/reactor.rb +85 -316
  68. data/lib/puma/request.rb +472 -0
  69. data/lib/puma/runner.rb +48 -55
  70. data/lib/puma/server.rb +303 -695
  71. data/lib/puma/single.rb +11 -67
  72. data/lib/puma/state_file.rb +47 -8
  73. data/lib/puma/systemd.rb +46 -0
  74. data/lib/puma/thread_pool.rb +132 -82
  75. data/lib/puma/util.rb +21 -7
  76. data/lib/puma.rb +54 -0
  77. data/lib/rack/handler/puma.rb +5 -6
  78. data/tools/Dockerfile +16 -0
  79. data/tools/trickletest.rb +0 -1
  80. metadata +45 -29
  81. data/ext/puma_http11/io_buffer.c +0 -155
  82. data/lib/puma/accept_nonblock.rb +0 -23
  83. data/lib/puma/compat.rb +0 -14
  84. data/lib/puma/convenient.rb +0 -25
  85. data/lib/puma/daemon_ext.rb +0 -33
  86. data/lib/puma/delegation.rb +0 -13
  87. data/lib/puma/java_io_buffer.rb +0 -47
  88. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  89. data/lib/puma/tcp_logger.rb +0 -41
  90. data/tools/jungle/README.md +0 -19
  91. data/tools/jungle/init.d/README.md +0 -61
  92. data/tools/jungle/init.d/puma +0 -421
  93. data/tools/jungle/init.d/run-puma +0 -18
  94. data/tools/jungle/upstart/README.md +0 -61
  95. data/tools/jungle/upstart/puma-manager.conf +0 -31
  96. data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/minissl.rb CHANGED
@@ -2,11 +2,21 @@
2
2
 
3
3
  begin
4
4
  require 'io/wait'
5
- rescue LoadError
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
@@ -54,22 +86,22 @@ module Puma
54
86
  output = engine_read_all
55
87
  return output if output
56
88
 
57
- begin
58
- data = @socket.read_nonblock(size, exception: false)
59
- if data == :wait_readable || data == :wait_writable
60
- if @socket.to_io.respond_to?(data)
61
- @socket.to_io.__send__(data)
62
- elsif data == :wait_readable
63
- IO.select([@socket.to_io])
64
- else
65
- IO.select(nil, [@socket.to_io])
66
- end
67
- elsif !data
68
- return nil
69
- else
70
- break
71
- end
72
- 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
73
105
 
74
106
  @engine.inject(data)
75
107
  output = engine_read_all
@@ -85,22 +117,23 @@ module Puma
85
117
  def write(data)
86
118
  return 0 if data.empty?
87
119
 
88
- need = data.bytesize
120
+ data_size = data.bytesize
121
+ need = data_size
89
122
 
90
123
  while true
91
124
  wrote = @engine.write data
92
- enc = @engine.extract
93
125
 
94
- while enc
95
- @socket.write enc
96
- enc = @engine.extract
126
+ enc_wr = ''.dup
127
+ while (enc = @engine.extract)
128
+ enc_wr << enc
97
129
  end
130
+ @socket.write enc_wr unless enc_wr.empty?
98
131
 
99
132
  need -= wrote
100
133
 
101
- return data.bytesize if need == 0
134
+ return data_size if need == 0
102
135
 
103
- data = data[wrote..-1]
136
+ data = data.byteslice(wrote..-1)
104
137
  end
105
138
  end
106
139
 
@@ -108,14 +141,18 @@ module Puma
108
141
  alias_method :<<, :write
109
142
 
110
143
  # This is a temporary fix to deal with websockets code using
111
- # write_nonblock. The problem with implementing it properly
144
+ # write_nonblock.
145
+
146
+ # The problem with implementing it properly
112
147
  # is that it means we'd have to have the ability to rewind
113
148
  # an engine because after we write+extract, the socket
114
149
  # write_nonblock call might raise an exception and later
115
150
  # code would pass the same data in, but the engine would think
116
- # it had already written the data in. So for the time being
117
- # (and since write blocking is quite rare), go ahead and actually
118
- # 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
+ #
119
156
  def write_nonblock(data, *_)
120
157
  write data
121
158
  end
@@ -124,39 +161,27 @@ module Puma
124
161
  @socket.flush
125
162
  end
126
163
 
127
- def read_and_drop(timeout = 1)
128
- return :timeout unless IO.select([@socket], nil, nil, timeout)
129
- return :eof unless read_nonblock(1024)
130
- :drop
131
- rescue Errno::EAGAIN
132
- # do nothing
133
- :eagain
134
- end
135
-
136
- def should_drop_bytes?
137
- @engine.init? || !@engine.shutdown
138
- end
139
-
140
164
  def close
141
165
  begin
142
- # Read any drop any partially initialized sockets and any received bytes during shutdown.
143
- # Don't let this socket hold this loop forever.
144
- # If it can't send more packets within 1s, then give up.
145
- while should_drop_bytes?
146
- 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
147
170
  end
148
171
  rescue IOError, SystemCallError
149
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
172
+ Puma::Util.purge_interrupt_queue
150
173
  # nothing
151
174
  ensure
152
175
  @socket.close
153
176
  end
154
177
  end
155
178
 
179
+ # @!attribute [r] peeraddr
156
180
  def peeraddr
157
181
  @socket.peeraddr
158
182
  end
159
183
 
184
+ # @!attribute [r] peercert
160
185
  def peercert
161
186
  return @peercert if @peercert
162
187
 
@@ -167,18 +192,29 @@ module Puma
167
192
  end
168
193
  end
169
194
 
170
- if defined?(JRUBY_VERSION)
195
+ if IS_JRUBY
196
+ OPENSSL_NO_SSL3 = false
197
+ OPENSSL_NO_TLS1 = false
198
+
171
199
  class SSLError < StandardError
172
200
  # Define this for jruby even though it isn't used.
173
201
  end
174
-
175
- def self.check; end
176
202
  end
177
203
 
178
204
  class Context
179
205
  attr_accessor :verify_mode
206
+ attr_reader :no_tlsv1, :no_tlsv1_1
207
+
208
+ def initialize
209
+ @no_tlsv1 = false
210
+ @no_tlsv1_1 = false
211
+ @key = nil
212
+ @cert = nil
213
+ @key_pem = nil
214
+ @cert_pem = nil
215
+ end
180
216
 
181
- if defined?(JRUBY_VERSION)
217
+ if IS_JRUBY
182
218
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
183
219
  attr_reader :keystore
184
220
  attr_accessor :keystore_pass
@@ -198,7 +234,10 @@ module Puma
198
234
  attr_reader :key
199
235
  attr_reader :cert
200
236
  attr_reader :ca
237
+ attr_reader :cert_pem
238
+ attr_reader :key_pem
201
239
  attr_accessor :ssl_cipher_filter
240
+ attr_accessor :verification_flags
202
241
 
203
242
  def key=(key)
204
243
  raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
@@ -215,46 +254,107 @@ module Puma
215
254
  @ca = ca
216
255
  end
217
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
+
218
267
  def check
219
- raise "Key not configured" unless @key
220
- 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?
221
270
  end
222
271
  end
272
+
273
+ # disables TLSv1
274
+ # @!attribute [w] no_tlsv1=
275
+ def no_tlsv1=(tlsv1)
276
+ raise ArgumentError, "Invalid value of no_tlsv1=" unless ['true', 'false', true, false].include?(tlsv1)
277
+ @no_tlsv1 = tlsv1
278
+ end
279
+
280
+ # disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
281
+ # @!attribute [w] no_tlsv1_1=
282
+ def no_tlsv1_1=(tlsv1_1)
283
+ raise ArgumentError, "Invalid value of no_tlsv1_1=" unless ['true', 'false', true, false].include?(tlsv1_1)
284
+ @no_tlsv1_1 = tlsv1_1
285
+ end
286
+
223
287
  end
224
288
 
225
289
  VERIFY_NONE = 0
226
290
  VERIFY_PEER = 1
227
291
  VERIFY_FAIL_IF_NO_PEER_CERT = 2
228
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
+
229
319
  class Server
230
320
  def initialize(socket, ctx)
231
321
  @socket = socket
232
322
  @ctx = ctx
233
- end
234
-
235
- def to_io
236
- @socket
323
+ @eng_ctx = IS_JRUBY ? @ctx : SSLContext.new(ctx)
237
324
  end
238
325
 
239
326
  def accept
240
327
  @ctx.check
241
328
  io = @socket.accept
242
- engine = Engine.server @ctx
243
-
329
+ engine = Engine.server @eng_ctx
244
330
  Socket.new io, engine
245
331
  end
246
332
 
247
333
  def accept_nonblock
248
334
  @ctx.check
249
335
  io = @socket.accept_nonblock
250
- engine = Engine.server @ctx
251
-
336
+ engine = Engine.server @eng_ctx
252
337
  Socket.new io, engine
253
338
  end
254
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
+
255
351
  def close
256
352
  @socket.close unless @socket.closed? # closed? call is for Windows
257
353
  end
354
+
355
+ def closed?
356
+ @socket.closed?
357
+ end
258
358
  end
259
359
  end
260
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
@@ -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
@@ -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 "plgn bg #{i}"
68
+ b.call
69
+ end
67
70
  end
68
71
  end
69
72
  end
@@ -88,7 +91,7 @@ module Puma
88
91
  path = ary.first[CALLER_FILE]
89
92
 
90
93
  m = %r!puma/plugin/([^/]*)\.rb$!.match(path)
91
- return m[1]
94
+ m[1]
92
95
  end
93
96
 
94
97
  def self.create(&blk)
@@ -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
@@ -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
  }
@@ -167,7 +165,7 @@ module Puma::Rack
167
165
  require config
168
166
  app = Object.const_get(::File.basename(config, '.rb').capitalize)
169
167
  end
170
- return app, options
168
+ [app, options]
171
169
  end
172
170
 
173
171
  def self.new_from_string(builder_script, file="(rackup)")
@@ -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