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