puma 3.12.6 → 5.3.2

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 +1400 -451
  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/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 +20 -10
  20. data/docs/rails_dev_mode.md +29 -0
  21. data/docs/restart.md +47 -22
  22. data/docs/signals.md +7 -6
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +48 -70
  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 +27 -0
  28. data/ext/puma_http11/http11_parser.c +81 -108
  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 +1 -1
  32. data/ext/puma_http11/http11_parser_common.rl +3 -3
  33. data/ext/puma_http11/mini_ssl.c +254 -91
  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 +89 -106
  37. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +92 -22
  38. data/ext/puma_http11/puma_http11.c +34 -50
  39. data/lib/puma.rb +54 -0
  40. data/lib/puma/app/status.rb +68 -49
  41. data/lib/puma/binder.rb +191 -139
  42. data/lib/puma/cli.rb +15 -15
  43. data/lib/puma/client.rb +247 -226
  44. data/lib/puma/cluster.rb +221 -212
  45. data/lib/puma/cluster/worker.rb +183 -0
  46. data/lib/puma/cluster/worker_handle.rb +90 -0
  47. data/lib/puma/commonlogger.rb +2 -2
  48. data/lib/puma/configuration.rb +58 -51
  49. data/lib/puma/const.rb +32 -20
  50. data/lib/puma/control_cli.rb +109 -67
  51. data/lib/puma/detect.rb +24 -3
  52. data/lib/puma/dsl.rb +519 -121
  53. data/lib/puma/error_logger.rb +104 -0
  54. data/lib/puma/events.rb +55 -31
  55. data/lib/puma/io_buffer.rb +7 -5
  56. data/lib/puma/jruby_restart.rb +0 -58
  57. data/lib/puma/json.rb +96 -0
  58. data/lib/puma/launcher.rb +178 -68
  59. data/lib/puma/minissl.rb +147 -48
  60. data/lib/puma/minissl/context_builder.rb +79 -0
  61. data/lib/puma/null_io.rb +13 -1
  62. data/lib/puma/plugin.rb +6 -12
  63. data/lib/puma/plugin/tmp_restart.rb +2 -0
  64. data/lib/puma/queue_close.rb +26 -0
  65. data/lib/puma/rack/builder.rb +2 -4
  66. data/lib/puma/rack/urlmap.rb +2 -0
  67. data/lib/puma/rack_default.rb +2 -0
  68. data/lib/puma/reactor.rb +85 -316
  69. data/lib/puma/request.rb +467 -0
  70. data/lib/puma/runner.rb +31 -52
  71. data/lib/puma/server.rb +275 -726
  72. data/lib/puma/single.rb +11 -67
  73. data/lib/puma/state_file.rb +8 -3
  74. data/lib/puma/systemd.rb +46 -0
  75. data/lib/puma/thread_pool.rb +129 -81
  76. data/lib/puma/util.rb +13 -6
  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 -28
  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
@@ -126,11 +163,14 @@ module Puma
126
163
 
127
164
  def read_and_drop(timeout = 1)
128
165
  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
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
134
174
  end
135
175
 
136
176
  def should_drop_bytes?
@@ -142,9 +182,7 @@ module Puma
142
182
  # Read any drop any partially initialized sockets and any received bytes during shutdown.
143
183
  # Don't let this socket hold this loop forever.
144
184
  # 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))
147
- end
185
+ return if [:timeout, :eof].include?(read_and_drop(1)) while should_drop_bytes?
148
186
  rescue IOError, SystemCallError
149
187
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
150
188
  # nothing
@@ -153,10 +191,12 @@ module Puma
153
191
  end
154
192
  end
155
193
 
194
+ # @!attribute [r] peeraddr
156
195
  def peeraddr
157
196
  @socket.peeraddr
158
197
  end
159
198
 
199
+ # @!attribute [r] peercert
160
200
  def peercert
161
201
  return @peercert if @peercert
162
202
 
@@ -167,18 +207,25 @@ module Puma
167
207
  end
168
208
  end
169
209
 
170
- if defined?(JRUBY_VERSION)
210
+ if IS_JRUBY
211
+ OPENSSL_NO_SSL3 = false
212
+ OPENSSL_NO_TLS1 = false
213
+
171
214
  class SSLError < StandardError
172
215
  # Define this for jruby even though it isn't used.
173
216
  end
174
-
175
- def self.check; end
176
217
  end
177
218
 
178
219
  class Context
179
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
180
227
 
181
- if defined?(JRUBY_VERSION)
228
+ if IS_JRUBY
182
229
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
183
230
  attr_reader :keystore
184
231
  attr_accessor :keystore_pass
@@ -199,6 +246,7 @@ module Puma
199
246
  attr_reader :cert
200
247
  attr_reader :ca
201
248
  attr_accessor :ssl_cipher_filter
249
+ attr_accessor :verification_flags
202
250
 
203
251
  def key=(key)
204
252
  raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
@@ -220,41 +268,92 @@ module Puma
220
268
  raise "Cert not configured" unless @cert
221
269
  end
222
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
+
223
286
  end
224
287
 
225
288
  VERIFY_NONE = 0
226
289
  VERIFY_PEER = 1
227
290
  VERIFY_FAIL_IF_NO_PEER_CERT = 2
228
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
+
229
318
  class Server
230
319
  def initialize(socket, ctx)
231
320
  @socket = socket
232
321
  @ctx = ctx
233
- end
234
-
235
- def to_io
236
- @socket
322
+ @eng_ctx = IS_JRUBY ? @ctx : SSLContext.new(ctx)
237
323
  end
238
324
 
239
325
  def accept
240
326
  @ctx.check
241
327
  io = @socket.accept
242
- engine = Engine.server @ctx
243
-
328
+ engine = Engine.server @eng_ctx
244
329
  Socket.new io, engine
245
330
  end
246
331
 
247
332
  def accept_nonblock
248
333
  @ctx.check
249
334
  io = @socket.accept_nonblock
250
- engine = Engine.server @ctx
251
-
335
+ engine = Engine.server @eng_ctx
252
336
  Socket.new io, engine
253
337
  end
254
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
+
255
350
  def close
256
351
  @socket.close unless @socket.closed? # closed? call is for Windows
257
352
  end
353
+
354
+ def closed?
355
+ @socket.closed?
356
+ end
258
357
  end
259
358
  end
260
359
  end
@@ -0,0 +1,79 @@
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
+ if params['verification_flags']
66
+ ctx.verification_flags = params['verification_flags'].split(',').
67
+ map { |flag| MiniSSL::VERIFICATION_FLAGS.fetch(flag) }.
68
+ inject { |sum, flag| sum ? sum | flag : flag }
69
+ end
70
+
71
+ ctx
72
+ end
73
+
74
+ private
75
+
76
+ attr_reader :params, :events
77
+ end
78
+ end
79
+ 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
@@ -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