puma 4.3.12 → 6.3.1

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 +1729 -521
  3. data/LICENSE +23 -20
  4. data/README.md +169 -45
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +63 -26
  7. data/docs/compile_options.md +55 -0
  8. data/docs/deployment.md +60 -69
  9. data/docs/fork_worker.md +31 -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 +2 -2
  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 +84 -128
  25. data/docs/testing_benchmarks_local_files.md +150 -0
  26. data/docs/testing_test_rackup_ci_files.md +36 -0
  27. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  28. data/ext/puma_http11/ext_help.h +1 -1
  29. data/ext/puma_http11/extconf.rb +49 -12
  30. data/ext/puma_http11/http11_parser.c +46 -48
  31. data/ext/puma_http11/http11_parser.h +2 -2
  32. data/ext/puma_http11/http11_parser.java.rl +3 -3
  33. data/ext/puma_http11/http11_parser.rl +3 -3
  34. data/ext/puma_http11/http11_parser_common.rl +2 -2
  35. data/ext/puma_http11/mini_ssl.c +278 -93
  36. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11.java +6 -6
  38. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +4 -6
  39. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +241 -96
  40. data/ext/puma_http11/puma_http11.c +46 -57
  41. data/lib/puma/app/status.rb +53 -39
  42. data/lib/puma/binder.rb +237 -121
  43. data/lib/puma/cli.rb +34 -34
  44. data/lib/puma/client.rb +172 -98
  45. data/lib/puma/cluster/worker.rb +180 -0
  46. data/lib/puma/cluster/worker_handle.rb +97 -0
  47. data/lib/puma/cluster.rb +226 -231
  48. data/lib/puma/commonlogger.rb +21 -14
  49. data/lib/puma/configuration.rb +114 -87
  50. data/lib/puma/const.rb +139 -95
  51. data/lib/puma/control_cli.rb +99 -79
  52. data/lib/puma/detect.rb +33 -2
  53. data/lib/puma/dsl.rb +516 -110
  54. data/lib/puma/error_logger.rb +113 -0
  55. data/lib/puma/events.rb +16 -115
  56. data/lib/puma/io_buffer.rb +44 -2
  57. data/lib/puma/jruby_restart.rb +2 -59
  58. data/lib/puma/json_serialization.rb +96 -0
  59. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  60. data/lib/puma/launcher.rb +164 -155
  61. data/lib/puma/log_writer.rb +147 -0
  62. data/lib/puma/minissl/context_builder.rb +36 -19
  63. data/lib/puma/minissl.rb +230 -55
  64. data/lib/puma/null_io.rb +18 -1
  65. data/lib/puma/plugin/systemd.rb +90 -0
  66. data/lib/puma/plugin/tmp_restart.rb +1 -1
  67. data/lib/puma/plugin.rb +3 -12
  68. data/lib/puma/rack/builder.rb +7 -11
  69. data/lib/puma/rack/urlmap.rb +0 -0
  70. data/lib/puma/rack_default.rb +19 -4
  71. data/lib/puma/reactor.rb +93 -368
  72. data/lib/puma/request.rb +671 -0
  73. data/lib/puma/runner.rb +92 -75
  74. data/lib/puma/sd_notify.rb +149 -0
  75. data/lib/puma/server.rb +321 -794
  76. data/lib/puma/single.rb +20 -74
  77. data/lib/puma/state_file.rb +45 -8
  78. data/lib/puma/thread_pool.rb +140 -68
  79. data/lib/puma/util.rb +21 -4
  80. data/lib/puma.rb +54 -7
  81. data/lib/rack/handler/puma.rb +113 -87
  82. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  83. data/tools/trickletest.rb +0 -0
  84. metadata +33 -24
  85. data/docs/tcp_mode.md +0 -96
  86. data/ext/puma_http11/io_buffer.c +0 -155
  87. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  88. data/lib/puma/accept_nonblock.rb +0 -29
  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
@@ -1,12 +1,9 @@
1
1
  module Puma
2
2
  module MiniSSL
3
3
  class ContextBuilder
4
- def initialize(params, events)
5
- require 'puma/minissl'
6
- MiniSSL.check
7
-
4
+ def initialize(params, log_writer)
8
5
  @params = params
9
- @events = events
6
+ @log_writer = log_writer
10
7
  end
11
8
 
12
9
  def context
@@ -14,42 +11,56 @@ module Puma
14
11
 
15
12
  if defined?(JRUBY_VERSION)
16
13
  unless params['keystore']
17
- events.error "Please specify the Java keystore via 'keystore='"
14
+ log_writer.error "Please specify the Java keystore via 'keystore='"
18
15
  end
19
16
 
20
17
  ctx.keystore = params['keystore']
21
18
 
22
19
  unless params['keystore-pass']
23
- events.error "Please specify the Java keystore password via 'keystore-pass='"
20
+ log_writer.error "Please specify the Java keystore password via 'keystore-pass='"
24
21
  end
25
22
 
26
23
  ctx.keystore_pass = params['keystore-pass']
27
- ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
24
+ ctx.keystore_type = params['keystore-type']
25
+
26
+ if truststore = params['truststore']
27
+ ctx.truststore = truststore.eql?('default') ? :default : truststore
28
+ ctx.truststore_pass = params['truststore-pass']
29
+ ctx.truststore_type = params['truststore-type']
30
+ end
31
+
32
+ ctx.cipher_suites = params['cipher_suites'] || params['ssl_cipher_list']
33
+ ctx.protocols = params['protocols'] if params['protocols']
28
34
  else
29
- unless params['key']
30
- events.error "Please specify the SSL key via 'key='"
35
+ if params['key'].nil? && params['key_pem'].nil?
36
+ log_writer.error "Please specify the SSL key via 'key=' or 'key_pem='"
31
37
  end
32
38
 
33
- ctx.key = params['key']
39
+ ctx.key = params['key'] if params['key']
40
+ ctx.key_pem = params['key_pem'] if params['key_pem']
41
+ ctx.key_password_command = params['key_password_command'] if params['key_password_command']
34
42
 
35
- unless params['cert']
36
- events.error "Please specify the SSL cert via 'cert='"
43
+ if params['cert'].nil? && params['cert_pem'].nil?
44
+ log_writer.error "Please specify the SSL cert via 'cert=' or 'cert_pem='"
37
45
  end
38
46
 
39
- ctx.cert = params['cert']
47
+ ctx.cert = params['cert'] if params['cert']
48
+ ctx.cert_pem = params['cert_pem'] if params['cert_pem']
40
49
 
41
50
  if ['peer', 'force_peer'].include?(params['verify_mode'])
42
51
  unless params['ca']
43
- events.error "Please specify the SSL ca via 'ca='"
52
+ log_writer.error "Please specify the SSL ca via 'ca='"
44
53
  end
45
54
  end
46
55
 
47
56
  ctx.ca = params['ca'] if params['ca']
48
57
  ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
58
+
59
+ ctx.reuse = params['reuse'] if params['reuse']
49
60
  end
50
61
 
51
- ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
52
- ctx.no_tlsv1_1 = true if params['no_tlsv1_1'] == 'true'
62
+ ctx.no_tlsv1 = params['no_tlsv1'] == 'true'
63
+ ctx.no_tlsv1_1 = params['no_tlsv1_1'] == 'true'
53
64
 
54
65
  if params['verify_mode']
55
66
  ctx.verify_mode = case params['verify_mode']
@@ -60,17 +71,23 @@ module Puma
60
71
  when "none"
61
72
  MiniSSL::VERIFY_NONE
62
73
  else
63
- events.error "Please specify a valid verify_mode="
74
+ log_writer.error "Please specify a valid verify_mode="
64
75
  MiniSSL::VERIFY_NONE
65
76
  end
66
77
  end
67
78
 
79
+ if params['verification_flags']
80
+ ctx.verification_flags = params['verification_flags'].split(',').
81
+ map { |flag| MiniSSL::VERIFICATION_FLAGS.fetch(flag) }.
82
+ inject { |sum, flag| sum ? sum | flag : flag }
83
+ end
84
+
68
85
  ctx
69
86
  end
70
87
 
71
88
  private
72
89
 
73
- attr_reader :params, :events
90
+ attr_reader :params, :log_writer
74
91
  end
75
92
  end
76
93
  end
data/lib/puma/minissl.rb CHANGED
@@ -1,19 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  begin
4
- require 'io/wait'
4
+ require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
5
5
  rescue LoadError
6
6
  end
7
7
 
8
+ require 'open3'
9
+ # need for Puma::MiniSSL::OPENSSL constants used in `HAS_TLS1_3`
10
+ # use require, see https://github.com/puma/puma/pull/2381
11
+ require 'puma/puma_http11'
12
+
8
13
  module Puma
9
14
  module MiniSSL
15
+ # Define constant at runtime, as it's easy to determine at built time,
16
+ # but Puma could (it shouldn't) be loaded with an older OpenSSL version
17
+ # @version 5.0.0
18
+ HAS_TLS1_3 = IS_JRUBY ||
19
+ ((OPENSSL_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) != -1 &&
20
+ (OPENSSL_LIBRARY_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) !=-1)
21
+
10
22
  class Socket
11
23
  def initialize(socket, engine)
12
24
  @socket = socket
13
25
  @engine = engine
14
26
  @peercert = nil
27
+ @reuse = nil
15
28
  end
16
29
 
30
+ # @!attribute [r] to_io
17
31
  def to_io
18
32
  @socket
19
33
  end
@@ -22,6 +36,27 @@ module Puma
22
36
  @socket.closed?
23
37
  end
24
38
 
39
+ # Returns a two element array,
40
+ # first is protocol version (SSL_get_version),
41
+ # second is 'handshake' state (SSL_state_string)
42
+ #
43
+ # Used for dropping tcp connections to ssl.
44
+ # See OpenSSL ssl/ssl_stat.c SSL_state_string for info
45
+ # @!attribute [r] ssl_version_state
46
+ # @version 5.0.0
47
+ #
48
+ def ssl_version_state
49
+ IS_JRUBY ? [nil, nil] : @engine.ssl_vers_st
50
+ end
51
+
52
+ # Used to check the handshake status, in particular when a TCP connection
53
+ # is made with TLSv1.3 as an available protocol
54
+ # @version 5.0.0
55
+ def bad_tlsv1_3?
56
+ HAS_TLS1_3 && ssl_version_state == ['TLSv1.3', 'SSLERR']
57
+ end
58
+ private :bad_tlsv1_3?
59
+
25
60
  def readpartial(size)
26
61
  while true
27
62
  output = @engine.read
@@ -67,6 +102,7 @@ module Puma
67
102
  # ourselves.
68
103
  raise IO::EAGAINWaitReadable
69
104
  elsif data.nil?
105
+ raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
70
106
  return nil
71
107
  end
72
108
 
@@ -84,22 +120,23 @@ module Puma
84
120
  def write(data)
85
121
  return 0 if data.empty?
86
122
 
87
- need = data.bytesize
123
+ data_size = data.bytesize
124
+ need = data_size
88
125
 
89
126
  while true
90
127
  wrote = @engine.write data
91
- enc = @engine.extract
92
128
 
93
- while enc
94
- @socket.write enc
95
- enc = @engine.extract
129
+ enc_wr = +''
130
+ while (enc = @engine.extract)
131
+ enc_wr << enc
96
132
  end
133
+ @socket.write enc_wr unless enc_wr.empty?
97
134
 
98
135
  need -= wrote
99
136
 
100
- return data.bytesize if need == 0
137
+ return data_size if need == 0
101
138
 
102
- data = data[wrote..-1]
139
+ data = data.byteslice(wrote..-1)
103
140
  end
104
141
  end
105
142
 
@@ -107,14 +144,18 @@ module Puma
107
144
  alias_method :<<, :write
108
145
 
109
146
  # This is a temporary fix to deal with websockets code using
110
- # write_nonblock. The problem with implementing it properly
147
+ # write_nonblock.
148
+
149
+ # The problem with implementing it properly
111
150
  # is that it means we'd have to have the ability to rewind
112
151
  # an engine because after we write+extract, the socket
113
152
  # write_nonblock call might raise an exception and later
114
153
  # 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.
154
+ # it had already written the data in.
155
+ #
156
+ # So for the time being (and since write blocking is quite rare),
157
+ # go ahead and actually block in write_nonblock.
158
+ #
118
159
  def write_nonblock(data, *_)
119
160
  write data
120
161
  end
@@ -123,39 +164,27 @@ module Puma
123
164
  @socket.flush
124
165
  end
125
166
 
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
167
  def close
140
168
  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))
169
+ unless @engine.shutdown
170
+ while alert_data = @engine.extract
171
+ @socket.write alert_data
172
+ end
146
173
  end
147
174
  rescue IOError, SystemCallError
148
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
175
+ Puma::Util.purge_interrupt_queue
149
176
  # nothing
150
177
  ensure
151
178
  @socket.close
152
179
  end
153
180
  end
154
181
 
182
+ # @!attribute [r] peeraddr
155
183
  def peeraddr
156
184
  @socket.peeraddr
157
185
  end
158
186
 
187
+ # @!attribute [r] peercert
159
188
  def peercert
160
189
  return @peercert if @peercert
161
190
 
@@ -166,12 +195,9 @@ module Puma
166
195
  end
167
196
  end
168
197
 
169
- if defined?(JRUBY_VERSION)
170
- class SSLError < StandardError
171
- # Define this for jruby even though it isn't used.
172
- end
173
-
174
- def self.check; end
198
+ if IS_JRUBY
199
+ OPENSSL_NO_SSL3 = false
200
+ OPENSSL_NO_TLS1 = false
175
201
  end
176
202
 
177
203
  class Context
@@ -181,60 +207,173 @@ module Puma
181
207
  def initialize
182
208
  @no_tlsv1 = false
183
209
  @no_tlsv1_1 = false
210
+ @key = nil
211
+ @cert = nil
212
+ @key_pem = nil
213
+ @cert_pem = nil
214
+ @reuse = nil
215
+ @reuse_cache_size = nil
216
+ @reuse_timeout = nil
217
+ end
218
+
219
+ def check_file(file, desc)
220
+ raise ArgumentError, "#{desc} file '#{file}' does not exist" unless File.exist? file
221
+ raise ArgumentError, "#{desc} file '#{file}' is not readable" unless File.readable? file
184
222
  end
185
223
 
186
- if defined?(JRUBY_VERSION)
224
+ if IS_JRUBY
187
225
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
188
226
  attr_reader :keystore
227
+ attr_reader :keystore_type
189
228
  attr_accessor :keystore_pass
190
- attr_accessor :ssl_cipher_list
229
+ attr_reader :truststore
230
+ attr_reader :truststore_type
231
+ attr_accessor :truststore_pass
232
+ attr_reader :cipher_suites
233
+ attr_reader :protocols
191
234
 
192
235
  def keystore=(keystore)
193
- raise ArgumentError, "No such keystore file '#{keystore}'" unless File.exist? keystore
236
+ check_file keystore, 'Keystore'
194
237
  @keystore = keystore
195
238
  end
196
239
 
240
+ def truststore=(truststore)
241
+ # NOTE: historically truststore was assumed the same as keystore, this is kept for backwards
242
+ # compatibility, to rely on JVM's trust defaults we allow setting `truststore = :default`
243
+ unless truststore.eql?(:default)
244
+ raise ArgumentError, "No such truststore file '#{truststore}'" unless File.exist?(truststore)
245
+ end
246
+ @truststore = truststore
247
+ end
248
+
249
+ def keystore_type=(type)
250
+ raise ArgumentError, "Invalid keystore type: #{type.inspect}" unless ['pkcs12', 'jks', nil].include?(type)
251
+ @keystore_type = type
252
+ end
253
+
254
+ def truststore_type=(type)
255
+ raise ArgumentError, "Invalid truststore type: #{type.inspect}" unless ['pkcs12', 'jks', nil].include?(type)
256
+ @truststore_type = type
257
+ end
258
+
259
+ def cipher_suites=(list)
260
+ list = list.split(',').map(&:strip) if list.is_a?(String)
261
+ @cipher_suites = list
262
+ end
263
+
264
+ # aliases for backwards compatibility
265
+ alias_method :ssl_cipher_list, :cipher_suites
266
+ alias_method :ssl_cipher_list=, :cipher_suites=
267
+
268
+ def protocols=(list)
269
+ list = list.split(',').map(&:strip) if list.is_a?(String)
270
+ @protocols = list
271
+ end
272
+
197
273
  def check
198
274
  raise "Keystore not configured" unless @keystore
275
+ # @truststore defaults to @keystore due backwards compatibility
199
276
  end
200
277
 
201
278
  else
202
279
  # non-jruby Context properties
203
280
  attr_reader :key
281
+ attr_reader :key_password_command
204
282
  attr_reader :cert
205
283
  attr_reader :ca
284
+ attr_reader :cert_pem
285
+ attr_reader :key_pem
206
286
  attr_accessor :ssl_cipher_filter
287
+ attr_accessor :verification_flags
288
+
289
+ attr_reader :reuse, :reuse_cache_size, :reuse_timeout
207
290
 
208
291
  def key=(key)
209
- raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
292
+ check_file key, 'Key'
210
293
  @key = key
211
294
  end
212
295
 
296
+ def key_password_command=(key_password_command)
297
+ @key_password_command = key_password_command
298
+ end
299
+
213
300
  def cert=(cert)
214
- raise ArgumentError, "No such cert file '#{cert}'" unless File.exist? cert
301
+ check_file cert, 'Cert'
215
302
  @cert = cert
216
303
  end
217
304
 
218
305
  def ca=(ca)
219
- raise ArgumentError, "No such ca file '#{ca}'" unless File.exist? ca
306
+ check_file ca, 'ca'
220
307
  @ca = ca
221
308
  end
222
309
 
310
+ def cert_pem=(cert_pem)
311
+ raise ArgumentError, "'cert_pem' is not a String" unless cert_pem.is_a? String
312
+ @cert_pem = cert_pem
313
+ end
314
+
315
+ def key_pem=(key_pem)
316
+ raise ArgumentError, "'key_pem' is not a String" unless key_pem.is_a? String
317
+ @key_pem = key_pem
318
+ end
319
+
223
320
  def check
224
- raise "Key not configured" unless @key
225
- raise "Cert not configured" unless @cert
321
+ raise "Key not configured" if @key.nil? && @key_pem.nil?
322
+ raise "Cert not configured" if @cert.nil? && @cert_pem.nil?
323
+ end
324
+
325
+ # Executes the command to return the password needed to decrypt the key.
326
+ def key_password
327
+ raise "Key password command not configured" if @key_password_command.nil?
328
+
329
+ stdout_str, stderr_str, status = Open3.capture3(@key_password_command)
330
+
331
+ return stdout_str.chomp if status.success?
332
+
333
+ raise "Key password failed with code #{status.exitstatus}: #{stderr_str}"
334
+ end
335
+
336
+ # Controls session reuse. Allowed values are as follows:
337
+ # * 'off' - matches the behavior of Puma 5.6 and earlier. This is included
338
+ # in case reuse 'on' is made the default in future Puma versions.
339
+ # * 'dflt' - sets session reuse on, with OpenSSL default cache size of
340
+ # 20k and default timeout of 300 seconds.
341
+ # * 's,t' - where s and t are integer strings, for size and timeout.
342
+ # * 's' - where s is an integer strings for size.
343
+ # * ',t' - where t is an integer strings for timeout.
344
+ #
345
+ def reuse=(reuse_str)
346
+ case reuse_str
347
+ when 'off'
348
+ @reuse = nil
349
+ when 'dflt'
350
+ @reuse = true
351
+ when /\A\d+\z/
352
+ @reuse = true
353
+ @reuse_cache_size = reuse_str.to_i
354
+ when /\A\d+,\d+\z/
355
+ @reuse = true
356
+ size, time = reuse_str.split ','
357
+ @reuse_cache_size = size.to_i
358
+ @reuse_timeout = time.to_i
359
+ when /\A,\d+\z/
360
+ @reuse = true
361
+ @reuse_timeout = reuse_str.delete(',').to_i
362
+ end
226
363
  end
227
364
  end
228
365
 
229
366
  # disables TLSv1
367
+ # @!attribute [w] no_tlsv1=
230
368
  def no_tlsv1=(tlsv1)
231
- raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
369
+ raise ArgumentError, "Invalid value of no_tlsv1=" unless ['true', 'false', true, false].include?(tlsv1)
232
370
  @no_tlsv1 = tlsv1
233
371
  end
234
372
 
235
373
  # disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
374
+ # @!attribute [w] no_tlsv1_1=
236
375
  def no_tlsv1_1=(tlsv1_1)
237
- raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1_1)
376
+ raise ArgumentError, "Invalid value of no_tlsv1_1=" unless ['true', 'false', true, false].include?(tlsv1_1)
238
377
  @no_tlsv1_1 = tlsv1_1
239
378
  end
240
379
 
@@ -244,35 +383,71 @@ module Puma
244
383
  VERIFY_PEER = 1
245
384
  VERIFY_FAIL_IF_NO_PEER_CERT = 2
246
385
 
386
+ # https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in
387
+ # /* Certificate verify flags */
388
+ VERIFICATION_FLAGS = {
389
+ "USE_CHECK_TIME" => 0x2,
390
+ "CRL_CHECK" => 0x4,
391
+ "CRL_CHECK_ALL" => 0x8,
392
+ "IGNORE_CRITICAL" => 0x10,
393
+ "X509_STRICT" => 0x20,
394
+ "ALLOW_PROXY_CERTS" => 0x40,
395
+ "POLICY_CHECK" => 0x80,
396
+ "EXPLICIT_POLICY" => 0x100,
397
+ "INHIBIT_ANY" => 0x200,
398
+ "INHIBIT_MAP" => 0x400,
399
+ "NOTIFY_POLICY" => 0x800,
400
+ "EXTENDED_CRL_SUPPORT" => 0x1000,
401
+ "USE_DELTAS" => 0x2000,
402
+ "CHECK_SS_SIGNATURE" => 0x4000,
403
+ "TRUSTED_FIRST" => 0x8000,
404
+ "SUITEB_128_LOS_ONLY" => 0x10000,
405
+ "SUITEB_192_LOS" => 0x20000,
406
+ "SUITEB_128_LOS" => 0x30000,
407
+ "PARTIAL_CHAIN" => 0x80000,
408
+ "NO_ALT_CHAINS" => 0x100000,
409
+ "NO_CHECK_TIME" => 0x200000
410
+ }.freeze
411
+
247
412
  class Server
248
413
  def initialize(socket, ctx)
249
414
  @socket = socket
250
415
  @ctx = ctx
251
- end
252
-
253
- def to_io
254
- @socket
416
+ @eng_ctx = IS_JRUBY ? @ctx : SSLContext.new(ctx)
255
417
  end
256
418
 
257
419
  def accept
258
420
  @ctx.check
259
421
  io = @socket.accept
260
- engine = Engine.server @ctx
261
-
422
+ engine = Engine.server @eng_ctx
262
423
  Socket.new io, engine
263
424
  end
264
425
 
265
426
  def accept_nonblock
266
427
  @ctx.check
267
428
  io = @socket.accept_nonblock
268
- engine = Engine.server @ctx
269
-
429
+ engine = Engine.server @eng_ctx
270
430
  Socket.new io, engine
271
431
  end
272
432
 
433
+ # @!attribute [r] to_io
434
+ def to_io
435
+ @socket
436
+ end
437
+
438
+ # @!attribute [r] addr
439
+ # @version 5.0.0
440
+ def addr
441
+ @socket.addr
442
+ end
443
+
273
444
  def close
274
445
  @socket.close unless @socket.closed? # closed? call is for Windows
275
446
  end
447
+
448
+ def closed?
449
+ @socket.closed?
450
+ end
276
451
  end
277
452
  end
278
453
  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
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../plugin'
4
+
5
+ # Puma's systemd integration allows Puma to inform systemd:
6
+ # 1. when it has successfully started
7
+ # 2. when it is starting shutdown
8
+ # 3. periodically for a liveness check with a watchdog thread
9
+ # 4. periodically set the status
10
+ Puma::Plugin.create do
11
+ def start(launcher)
12
+ require_relative '../sd_notify'
13
+
14
+ launcher.log_writer.log "* Enabling systemd notification integration"
15
+
16
+ # hook_events
17
+ launcher.events.on_booted { Puma::SdNotify.ready }
18
+ launcher.events.on_stopped { Puma::SdNotify.stopping }
19
+ launcher.events.on_restart { Puma::SdNotify.reloading }
20
+
21
+ # start watchdog
22
+ if Puma::SdNotify.watchdog?
23
+ ping_f = watchdog_sleep_time
24
+
25
+ in_background do
26
+ launcher.log_writer.log "Pinging systemd watchdog every #{ping_f.round(1)} sec"
27
+ loop do
28
+ sleep ping_f
29
+ Puma::SdNotify.watchdog
30
+ end
31
+ end
32
+ end
33
+
34
+ # start status loop
35
+ instance = self
36
+ sleep_time = 1.0
37
+ in_background do
38
+ launcher.log_writer.log "Sending status to systemd every #{sleep_time.round(1)} sec"
39
+
40
+ loop do
41
+ sleep sleep_time
42
+ # TODO: error handling?
43
+ Puma::SdNotify.status(instance.status)
44
+ end
45
+ end
46
+ end
47
+
48
+ def status
49
+ if clustered?
50
+ messages = stats[:worker_status].map do |worker|
51
+ common_message(worker[:last_status])
52
+ end.join(',')
53
+
54
+ "Puma #{Puma::Const::VERSION}: cluster: #{booted_workers}/#{workers}, worker_status: [#{messages}]"
55
+ else
56
+ "Puma #{Puma::Const::VERSION}: worker: #{common_message(stats)}"
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def watchdog_sleep_time
63
+ usec = Integer(ENV["WATCHDOG_USEC"])
64
+
65
+ sec_f = usec / 1_000_000.0
66
+ # "It is recommended that a daemon sends a keep-alive notification message
67
+ # to the service manager every half of the time returned here."
68
+ sec_f / 2
69
+ end
70
+
71
+ def stats
72
+ Puma.stats_hash
73
+ end
74
+
75
+ def clustered?
76
+ stats.has_key?(:workers)
77
+ end
78
+
79
+ def workers
80
+ stats.fetch(:workers, 1)
81
+ end
82
+
83
+ def booted_workers
84
+ stats.fetch(:booted_workers, 1)
85
+ end
86
+
87
+ def common_message(stats)
88
+ "{ #{stats[:running]}/#{stats[:max_threads]} threads, #{stats[:pool_capacity]} available, #{stats[:backlog]} backlog }"
89
+ end
90
+ end