puma 4.3.12 → 5.6.4
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 +1461 -524
- data/LICENSE +23 -20
- data/README.md +120 -36
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +63 -26
- data/docs/compile_options.md +21 -0
- data/docs/deployment.md +60 -69
- data/docs/fork_worker.md +33 -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 +1 -1
- 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 +85 -128
- data/ext/puma_http11/PumaHttp11Service.java +2 -4
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +38 -9
- data/ext/puma_http11/http11_parser.c +45 -47
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +1 -1
- data/ext/puma_http11/http11_parser.rl +1 -1
- data/ext/puma_http11/mini_ssl.c +204 -86
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +3 -5
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +105 -61
- data/ext/puma_http11/puma_http11.c +32 -51
- data/lib/puma/app/status.rb +47 -36
- data/lib/puma/binder.rb +225 -106
- data/lib/puma/cli.rb +24 -18
- data/lib/puma/client.rb +104 -76
- data/lib/puma/cluster/worker.rb +173 -0
- data/lib/puma/cluster/worker_handle.rb +94 -0
- data/lib/puma/cluster.rb +212 -220
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +58 -49
- data/lib/puma/const.rb +13 -6
- data/lib/puma/control_cli.rb +93 -76
- data/lib/puma/detect.rb +29 -2
- data/lib/puma/dsl.rb +364 -96
- data/lib/puma/error_logger.rb +104 -0
- data/lib/puma/events.rb +55 -34
- data/lib/puma/io_buffer.rb +9 -2
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher.rb +117 -46
- data/lib/puma/minissl/context_builder.rb +14 -9
- data/lib/puma/minissl.rb +128 -46
- data/lib/puma/null_io.rb +13 -1
- data/lib/puma/plugin.rb +3 -12
- data/lib/puma/queue_close.rb +26 -0
- data/lib/puma/rack/builder.rb +1 -5
- data/lib/puma/reactor.rb +85 -369
- data/lib/puma/request.rb +472 -0
- data/lib/puma/runner.rb +46 -61
- data/lib/puma/server.rb +290 -763
- data/lib/puma/single.rb +9 -65
- data/lib/puma/state_file.rb +47 -8
- data/lib/puma/systemd.rb +46 -0
- data/lib/puma/thread_pool.rb +125 -57
- data/lib/puma/util.rb +20 -1
- data/lib/puma.rb +46 -0
- data/lib/rack/handler/puma.rb +2 -3
- data/tools/{docker/Dockerfile → Dockerfile} +1 -1
- metadata +26 -22
- 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
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
|
-
|
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
|
-
|
94
|
-
|
95
|
-
|
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
|
134
|
+
return data_size if need == 0
|
101
135
|
|
102
|
-
data = data
|
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.
|
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.
|
116
|
-
#
|
117
|
-
#
|
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
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|
-
|
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
|
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,9 +208,13 @@ 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
|
217
|
+
if IS_JRUBY
|
187
218
|
# jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
|
188
219
|
attr_reader :keystore
|
189
220
|
attr_accessor :keystore_pass
|
@@ -203,7 +234,10 @@ module Puma
|
|
203
234
|
attr_reader :key
|
204
235
|
attr_reader :cert
|
205
236
|
attr_reader :ca
|
237
|
+
attr_reader :cert_pem
|
238
|
+
attr_reader :key_pem
|
206
239
|
attr_accessor :ssl_cipher_filter
|
240
|
+
attr_accessor :verification_flags
|
207
241
|
|
208
242
|
def key=(key)
|
209
243
|
raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
|
@@ -220,21 +254,33 @@ module Puma
|
|
220
254
|
@ca = ca
|
221
255
|
end
|
222
256
|
|
257
|
+
def cert_pem=(cert_pem)
|
258
|
+
raise ArgumentError, "'cert_pem' is not a String" unless cert_pem.is_a? String
|
259
|
+
@cert_pem = cert_pem
|
260
|
+
end
|
261
|
+
|
262
|
+
def key_pem=(key_pem)
|
263
|
+
raise ArgumentError, "'key_pem' is not a String" unless key_pem.is_a? String
|
264
|
+
@key_pem = key_pem
|
265
|
+
end
|
266
|
+
|
223
267
|
def check
|
224
|
-
raise "Key not configured"
|
225
|
-
raise "Cert not configured"
|
268
|
+
raise "Key not configured" if @key.nil? && @key_pem.nil?
|
269
|
+
raise "Cert not configured" if @cert.nil? && @cert_pem.nil?
|
226
270
|
end
|
227
271
|
end
|
228
272
|
|
229
273
|
# disables TLSv1
|
274
|
+
# @!attribute [w] no_tlsv1=
|
230
275
|
def no_tlsv1=(tlsv1)
|
231
|
-
raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
|
276
|
+
raise ArgumentError, "Invalid value of no_tlsv1=" unless ['true', 'false', true, false].include?(tlsv1)
|
232
277
|
@no_tlsv1 = tlsv1
|
233
278
|
end
|
234
279
|
|
235
280
|
# disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
|
281
|
+
# @!attribute [w] no_tlsv1_1=
|
236
282
|
def no_tlsv1_1=(tlsv1_1)
|
237
|
-
raise ArgumentError, "Invalid value of
|
283
|
+
raise ArgumentError, "Invalid value of no_tlsv1_1=" unless ['true', 'false', true, false].include?(tlsv1_1)
|
238
284
|
@no_tlsv1_1 = tlsv1_1
|
239
285
|
end
|
240
286
|
|
@@ -244,35 +290,71 @@ module Puma
|
|
244
290
|
VERIFY_PEER = 1
|
245
291
|
VERIFY_FAIL_IF_NO_PEER_CERT = 2
|
246
292
|
|
293
|
+
# https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in
|
294
|
+
# /* Certificate verify flags */
|
295
|
+
VERIFICATION_FLAGS = {
|
296
|
+
"USE_CHECK_TIME" => 0x2,
|
297
|
+
"CRL_CHECK" => 0x4,
|
298
|
+
"CRL_CHECK_ALL" => 0x8,
|
299
|
+
"IGNORE_CRITICAL" => 0x10,
|
300
|
+
"X509_STRICT" => 0x20,
|
301
|
+
"ALLOW_PROXY_CERTS" => 0x40,
|
302
|
+
"POLICY_CHECK" => 0x80,
|
303
|
+
"EXPLICIT_POLICY" => 0x100,
|
304
|
+
"INHIBIT_ANY" => 0x200,
|
305
|
+
"INHIBIT_MAP" => 0x400,
|
306
|
+
"NOTIFY_POLICY" => 0x800,
|
307
|
+
"EXTENDED_CRL_SUPPORT" => 0x1000,
|
308
|
+
"USE_DELTAS" => 0x2000,
|
309
|
+
"CHECK_SS_SIGNATURE" => 0x4000,
|
310
|
+
"TRUSTED_FIRST" => 0x8000,
|
311
|
+
"SUITEB_128_LOS_ONLY" => 0x10000,
|
312
|
+
"SUITEB_192_LOS" => 0x20000,
|
313
|
+
"SUITEB_128_LOS" => 0x30000,
|
314
|
+
"PARTIAL_CHAIN" => 0x80000,
|
315
|
+
"NO_ALT_CHAINS" => 0x100000,
|
316
|
+
"NO_CHECK_TIME" => 0x200000
|
317
|
+
}.freeze
|
318
|
+
|
247
319
|
class Server
|
248
320
|
def initialize(socket, ctx)
|
249
321
|
@socket = socket
|
250
322
|
@ctx = ctx
|
251
|
-
|
252
|
-
|
253
|
-
def to_io
|
254
|
-
@socket
|
323
|
+
@eng_ctx = IS_JRUBY ? @ctx : SSLContext.new(ctx)
|
255
324
|
end
|
256
325
|
|
257
326
|
def accept
|
258
327
|
@ctx.check
|
259
328
|
io = @socket.accept
|
260
|
-
engine = Engine.server @
|
261
|
-
|
329
|
+
engine = Engine.server @eng_ctx
|
262
330
|
Socket.new io, engine
|
263
331
|
end
|
264
332
|
|
265
333
|
def accept_nonblock
|
266
334
|
@ctx.check
|
267
335
|
io = @socket.accept_nonblock
|
268
|
-
engine = Engine.server @
|
269
|
-
|
336
|
+
engine = Engine.server @eng_ctx
|
270
337
|
Socket.new io, engine
|
271
338
|
end
|
272
339
|
|
340
|
+
# @!attribute [r] to_io
|
341
|
+
def to_io
|
342
|
+
@socket
|
343
|
+
end
|
344
|
+
|
345
|
+
# @!attribute [r] addr
|
346
|
+
# @version 5.0.0
|
347
|
+
def addr
|
348
|
+
@socket.addr
|
349
|
+
end
|
350
|
+
|
273
351
|
def close
|
274
352
|
@socket.close unless @socket.closed? # closed? call is for Windows
|
275
353
|
end
|
354
|
+
|
355
|
+
def closed?
|
356
|
+
@socket.closed?
|
357
|
+
end
|
276
358
|
end
|
277
359
|
end
|
278
360
|
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,9 @@ module Puma
|
|
40
48
|
|
41
49
|
def write(*ary)
|
42
50
|
end
|
51
|
+
|
52
|
+
def flush
|
53
|
+
self
|
54
|
+
end
|
43
55
|
end
|
44
56
|
end
|
data/lib/puma/plugin.rb
CHANGED
@@ -10,7 +10,7 @@ module Puma
|
|
10
10
|
|
11
11
|
def create(name)
|
12
12
|
if cls = Plugins.find(name)
|
13
|
-
plugin = cls.new
|
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 "
|
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
|
-
|
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
|
data/lib/puma/rack/builder.rb
CHANGED
@@ -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
|
-
|
168
|
+
[app, options]
|
173
169
|
end
|
174
170
|
|
175
171
|
def self.new_from_string(builder_script, file="(rackup)")
|