puma 4.3.12 → 5.6.6

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 +1511 -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/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 +15 -15
  20. data/docs/rails_dev_mode.md +28 -0
  21. data/docs/restart.md +46 -23
  22. data/docs/signals.md +13 -11
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +85 -128
  25. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  26. data/ext/puma_http11/ext_help.h +1 -1
  27. data/ext/puma_http11/extconf.rb +44 -10
  28. data/ext/puma_http11/http11_parser.c +45 -47
  29. data/ext/puma_http11/http11_parser.h +1 -1
  30. data/ext/puma_http11/http11_parser.java.rl +1 -1
  31. data/ext/puma_http11/http11_parser.rl +1 -1
  32. data/ext/puma_http11/http11_parser_common.rl +0 -0
  33. data/ext/puma_http11/mini_ssl.c +225 -89
  34. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  36. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +3 -5
  37. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +109 -67
  38. data/ext/puma_http11/puma_http11.c +32 -51
  39. data/lib/puma/app/status.rb +50 -36
  40. data/lib/puma/binder.rb +225 -106
  41. data/lib/puma/cli.rb +24 -18
  42. data/lib/puma/client.rb +104 -76
  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 +212 -220
  46. data/lib/puma/commonlogger.rb +2 -2
  47. data/lib/puma/configuration.rb +58 -49
  48. data/lib/puma/const.rb +13 -6
  49. data/lib/puma/control_cli.rb +99 -76
  50. data/lib/puma/detect.rb +29 -2
  51. data/lib/puma/dsl.rb +368 -96
  52. data/lib/puma/error_logger.rb +104 -0
  53. data/lib/puma/events.rb +55 -34
  54. data/lib/puma/io_buffer.rb +9 -2
  55. data/lib/puma/jruby_restart.rb +0 -58
  56. data/lib/puma/json_serialization.rb +96 -0
  57. data/lib/puma/launcher.rb +128 -46
  58. data/lib/puma/minissl/context_builder.rb +14 -9
  59. data/lib/puma/minissl.rb +137 -50
  60. data/lib/puma/null_io.rb +18 -1
  61. data/lib/puma/plugin/tmp_restart.rb +0 -0
  62. data/lib/puma/plugin.rb +3 -12
  63. data/lib/puma/queue_close.rb +26 -0
  64. data/lib/puma/rack/builder.rb +1 -5
  65. data/lib/puma/rack/urlmap.rb +0 -0
  66. data/lib/puma/rack_default.rb +0 -0
  67. data/lib/puma/reactor.rb +85 -369
  68. data/lib/puma/request.rb +476 -0
  69. data/lib/puma/runner.rb +46 -61
  70. data/lib/puma/server.rb +292 -763
  71. data/lib/puma/single.rb +9 -65
  72. data/lib/puma/state_file.rb +48 -8
  73. data/lib/puma/systemd.rb +46 -0
  74. data/lib/puma/thread_pool.rb +125 -57
  75. data/lib/puma/util.rb +32 -4
  76. data/lib/puma.rb +48 -0
  77. data/lib/rack/handler/puma.rb +2 -3
  78. data/lib/rack/version_restriction.rb +15 -0
  79. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  80. data/tools/trickletest.rb +0 -0
  81. metadata +28 -23
  82. data/docs/tcp_mode.md +0 -96
  83. data/ext/puma_http11/io_buffer.c +0 -155
  84. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  85. data/lib/puma/accept_nonblock.rb +0 -29
  86. data/lib/puma/tcp_logger.rb +0 -41
  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
@@ -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,16 +208,25 @@ 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
+ def check_file(file, desc)
218
+ raise ArgumentError, "#{desc} file '#{file}' does not exist" unless File.exist? file
219
+ raise ArgumentError, "#{desc} file '#{file}' is not readable" unless File.readable? file
220
+ end
221
+
222
+ if IS_JRUBY
187
223
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
188
224
  attr_reader :keystore
189
225
  attr_accessor :keystore_pass
190
226
  attr_accessor :ssl_cipher_list
191
227
 
192
228
  def keystore=(keystore)
193
- raise ArgumentError, "No such keystore file '#{keystore}'" unless File.exist? keystore
229
+ check_file keystore, 'Keystore'
194
230
  @keystore = keystore
195
231
  end
196
232
 
@@ -203,38 +239,53 @@ module Puma
203
239
  attr_reader :key
204
240
  attr_reader :cert
205
241
  attr_reader :ca
242
+ attr_reader :cert_pem
243
+ attr_reader :key_pem
206
244
  attr_accessor :ssl_cipher_filter
245
+ attr_accessor :verification_flags
207
246
 
208
247
  def key=(key)
209
- raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
248
+ check_file key, 'Key'
210
249
  @key = key
211
250
  end
212
251
 
213
252
  def cert=(cert)
214
- raise ArgumentError, "No such cert file '#{cert}'" unless File.exist? cert
253
+ check_file cert, 'Cert'
215
254
  @cert = cert
216
255
  end
217
256
 
218
257
  def ca=(ca)
219
- raise ArgumentError, "No such ca file '#{ca}'" unless File.exist? ca
258
+ check_file ca, 'ca'
220
259
  @ca = ca
221
260
  end
222
261
 
262
+ def cert_pem=(cert_pem)
263
+ raise ArgumentError, "'cert_pem' is not a String" unless cert_pem.is_a? String
264
+ @cert_pem = cert_pem
265
+ end
266
+
267
+ def key_pem=(key_pem)
268
+ raise ArgumentError, "'key_pem' is not a String" unless key_pem.is_a? String
269
+ @key_pem = key_pem
270
+ end
271
+
223
272
  def check
224
- raise "Key not configured" unless @key
225
- raise "Cert not configured" unless @cert
273
+ raise "Key not configured" if @key.nil? && @key_pem.nil?
274
+ raise "Cert not configured" if @cert.nil? && @cert_pem.nil?
226
275
  end
227
276
  end
228
277
 
229
278
  # disables TLSv1
279
+ # @!attribute [w] no_tlsv1=
230
280
  def no_tlsv1=(tlsv1)
231
- raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
281
+ raise ArgumentError, "Invalid value of no_tlsv1=" unless ['true', 'false', true, false].include?(tlsv1)
232
282
  @no_tlsv1 = tlsv1
233
283
  end
234
284
 
235
285
  # disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
286
+ # @!attribute [w] no_tlsv1_1=
236
287
  def no_tlsv1_1=(tlsv1_1)
237
- raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1_1)
288
+ raise ArgumentError, "Invalid value of no_tlsv1_1=" unless ['true', 'false', true, false].include?(tlsv1_1)
238
289
  @no_tlsv1_1 = tlsv1_1
239
290
  end
240
291
 
@@ -244,35 +295,71 @@ module Puma
244
295
  VERIFY_PEER = 1
245
296
  VERIFY_FAIL_IF_NO_PEER_CERT = 2
246
297
 
298
+ # https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in
299
+ # /* Certificate verify flags */
300
+ VERIFICATION_FLAGS = {
301
+ "USE_CHECK_TIME" => 0x2,
302
+ "CRL_CHECK" => 0x4,
303
+ "CRL_CHECK_ALL" => 0x8,
304
+ "IGNORE_CRITICAL" => 0x10,
305
+ "X509_STRICT" => 0x20,
306
+ "ALLOW_PROXY_CERTS" => 0x40,
307
+ "POLICY_CHECK" => 0x80,
308
+ "EXPLICIT_POLICY" => 0x100,
309
+ "INHIBIT_ANY" => 0x200,
310
+ "INHIBIT_MAP" => 0x400,
311
+ "NOTIFY_POLICY" => 0x800,
312
+ "EXTENDED_CRL_SUPPORT" => 0x1000,
313
+ "USE_DELTAS" => 0x2000,
314
+ "CHECK_SS_SIGNATURE" => 0x4000,
315
+ "TRUSTED_FIRST" => 0x8000,
316
+ "SUITEB_128_LOS_ONLY" => 0x10000,
317
+ "SUITEB_192_LOS" => 0x20000,
318
+ "SUITEB_128_LOS" => 0x30000,
319
+ "PARTIAL_CHAIN" => 0x80000,
320
+ "NO_ALT_CHAINS" => 0x100000,
321
+ "NO_CHECK_TIME" => 0x200000
322
+ }.freeze
323
+
247
324
  class Server
248
325
  def initialize(socket, ctx)
249
326
  @socket = socket
250
327
  @ctx = ctx
251
- end
252
-
253
- def to_io
254
- @socket
328
+ @eng_ctx = IS_JRUBY ? @ctx : SSLContext.new(ctx)
255
329
  end
256
330
 
257
331
  def accept
258
332
  @ctx.check
259
333
  io = @socket.accept
260
- engine = Engine.server @ctx
261
-
334
+ engine = Engine.server @eng_ctx
262
335
  Socket.new io, engine
263
336
  end
264
337
 
265
338
  def accept_nonblock
266
339
  @ctx.check
267
340
  io = @socket.accept_nonblock
268
- engine = Engine.server @ctx
269
-
341
+ engine = Engine.server @eng_ctx
270
342
  Socket.new io, engine
271
343
  end
272
344
 
345
+ # @!attribute [r] to_io
346
+ def to_io
347
+ @socket
348
+ end
349
+
350
+ # @!attribute [r] addr
351
+ # @version 5.0.0
352
+ def addr
353
+ @socket.addr
354
+ end
355
+
273
356
  def close
274
357
  @socket.close unless @socket.closed? # closed? call is for Windows
275
358
  end
359
+
360
+ def closed?
361
+ @socket.closed?
362
+ end
276
363
  end
277
364
  end
278
365
  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,14 @@ module Puma
40
48
 
41
49
  def write(*ary)
42
50
  end
51
+
52
+ def flush
53
+ self
54
+ end
55
+
56
+ # This is used as singleton class, so can't have state.
57
+ def closed?
58
+ false
59
+ end
43
60
  end
44
61
  end
File without changes
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)")
File without changes
File without changes