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.
- checksums.yaml +4 -4
- data/History.md +1729 -521
- data/LICENSE +23 -20
- data/README.md +169 -45
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +63 -26
- data/docs/compile_options.md +55 -0
- data/docs/deployment.md +60 -69
- data/docs/fork_worker.md +31 -0
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/jungle/README.md +9 -0
- data/{tools → docs}/jungle/rc.d/README.md +1 -1
- data/{tools → docs}/jungle/rc.d/puma +2 -2
- data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +66 -0
- data/docs/nginx.md +2 -2
- data/docs/plugins.md +15 -15
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +46 -23
- data/docs/signals.md +13 -11
- data/docs/stats.md +142 -0
- data/docs/systemd.md +84 -128
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/PumaHttp11Service.java +2 -4
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +49 -12
- data/ext/puma_http11/http11_parser.c +46 -48
- data/ext/puma_http11/http11_parser.h +2 -2
- data/ext/puma_http11/http11_parser.java.rl +3 -3
- data/ext/puma_http11/http11_parser.rl +3 -3
- data/ext/puma_http11/http11_parser_common.rl +2 -2
- data/ext/puma_http11/mini_ssl.c +278 -93
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +6 -6
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +4 -6
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +241 -96
- data/ext/puma_http11/puma_http11.c +46 -57
- data/lib/puma/app/status.rb +53 -39
- data/lib/puma/binder.rb +237 -121
- data/lib/puma/cli.rb +34 -34
- data/lib/puma/client.rb +172 -98
- data/lib/puma/cluster/worker.rb +180 -0
- data/lib/puma/cluster/worker_handle.rb +97 -0
- data/lib/puma/cluster.rb +226 -231
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +114 -87
- data/lib/puma/const.rb +139 -95
- data/lib/puma/control_cli.rb +99 -79
- data/lib/puma/detect.rb +33 -2
- data/lib/puma/dsl.rb +516 -110
- data/lib/puma/error_logger.rb +113 -0
- data/lib/puma/events.rb +16 -115
- data/lib/puma/io_buffer.rb +44 -2
- data/lib/puma/jruby_restart.rb +2 -59
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +164 -155
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +36 -19
- data/lib/puma/minissl.rb +230 -55
- data/lib/puma/null_io.rb +18 -1
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/plugin.rb +3 -12
- data/lib/puma/rack/builder.rb +7 -11
- data/lib/puma/rack/urlmap.rb +0 -0
- data/lib/puma/rack_default.rb +19 -4
- data/lib/puma/reactor.rb +93 -368
- data/lib/puma/request.rb +671 -0
- data/lib/puma/runner.rb +92 -75
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +321 -794
- data/lib/puma/single.rb +20 -74
- data/lib/puma/state_file.rb +45 -8
- data/lib/puma/thread_pool.rb +140 -68
- data/lib/puma/util.rb +21 -4
- data/lib/puma.rb +54 -7
- data/lib/rack/handler/puma.rb +113 -87
- data/tools/{docker/Dockerfile → Dockerfile} +1 -1
- data/tools/trickletest.rb +0 -0
- metadata +33 -24
- data/docs/tcp_mode.md +0 -96
- data/ext/puma_http11/io_buffer.c +0 -155
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
- data/lib/puma/accept_nonblock.rb +0 -29
- data/lib/puma/tcp_logger.rb +0 -41
- data/tools/jungle/README.md +0 -19
- data/tools/jungle/init.d/README.md +0 -61
- data/tools/jungle/init.d/puma +0 -421
- data/tools/jungle/init.d/run-puma +0 -18
- data/tools/jungle/upstart/README.md +0 -61
- data/tools/jungle/upstart/puma-manager.conf +0 -31
- 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,
|
5
|
-
require 'puma/minissl'
|
6
|
-
MiniSSL.check
|
7
|
-
|
4
|
+
def initialize(params, log_writer)
|
8
5
|
@params = params
|
9
|
-
@
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
|
30
|
-
|
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
|
-
|
36
|
-
|
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
|
-
|
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
|
52
|
-
ctx.no_tlsv1_1 =
|
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
|
-
|
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, :
|
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
|
-
|
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
|
-
|
94
|
-
|
95
|
-
|
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
|
137
|
+
return data_size if need == 0
|
101
138
|
|
102
|
-
data = data
|
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.
|
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.
|
116
|
-
#
|
117
|
-
#
|
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
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|
-
|
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
|
170
|
-
|
171
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
301
|
+
check_file cert, 'Cert'
|
215
302
|
@cert = cert
|
216
303
|
end
|
217
304
|
|
218
305
|
def ca=(ca)
|
219
|
-
|
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"
|
225
|
-
raise "Cert not configured"
|
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
|
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
|
-
|
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 @
|
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 @
|
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
|
-
|
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
|