puma 4.3.12 → 5.6.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1526 -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 +5 -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 +146 -84
  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 +22 -7
  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 +489 -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 +29 -24
  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