puma 4.1.1 → 5.0.0
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 +149 -10
- data/LICENSE +23 -20
- data/README.md +30 -46
- data/docs/architecture.md +3 -3
- data/docs/deployment.md +9 -3
- data/docs/fork_worker.md +31 -0
- data/docs/jungle/README.md +13 -0
- data/{tools → docs}/jungle/rc.d/README.md +0 -0
- data/{tools → docs}/jungle/rc.d/puma +0 -0
- data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
- data/{tools → docs}/jungle/upstart/README.md +0 -0
- data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
- data/{tools → docs}/jungle/upstart/puma.conf +0 -0
- data/docs/plugins.md +20 -10
- data/docs/signals.md +7 -6
- data/docs/systemd.md +1 -63
- data/ext/puma_http11/PumaHttp11Service.java +2 -4
- data/ext/puma_http11/extconf.rb +6 -0
- data/ext/puma_http11/http11_parser.c +40 -63
- data/ext/puma_http11/http11_parser.java.rl +21 -37
- data/ext/puma_http11/http11_parser.rl +3 -1
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/ext/puma_http11/mini_ssl.c +15 -2
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +91 -106
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
- data/ext/puma_http11/puma_http11.c +9 -38
- data/lib/puma.rb +23 -0
- data/lib/puma/app/status.rb +46 -30
- data/lib/puma/binder.rb +112 -124
- data/lib/puma/cli.rb +11 -15
- data/lib/puma/client.rb +250 -209
- data/lib/puma/cluster.rb +203 -85
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +31 -42
- data/lib/puma/const.rb +24 -19
- data/lib/puma/control_cli.rb +46 -17
- data/lib/puma/detect.rb +17 -0
- data/lib/puma/dsl.rb +162 -70
- data/lib/puma/error_logger.rb +97 -0
- data/lib/puma/events.rb +35 -31
- data/lib/puma/io_buffer.rb +9 -2
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/launcher.rb +117 -58
- data/lib/puma/minissl.rb +60 -18
- data/lib/puma/minissl/context_builder.rb +73 -0
- data/lib/puma/null_io.rb +1 -1
- data/lib/puma/plugin.rb +6 -12
- data/lib/puma/rack/builder.rb +0 -4
- data/lib/puma/reactor.rb +16 -9
- data/lib/puma/runner.rb +11 -32
- data/lib/puma/server.rb +173 -193
- data/lib/puma/single.rb +7 -64
- data/lib/puma/state_file.rb +6 -3
- data/lib/puma/thread_pool.rb +104 -81
- data/lib/rack/handler/puma.rb +1 -5
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +23 -24
- data/ext/puma_http11/io_buffer.c +0 -155
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
- data/lib/puma/convenient.rb +0 -25
- data/lib/puma/daemon_ext.rb +0 -33
- data/lib/puma/delegation.rb +0 -13
- 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/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
|
@@ -22,6 +32,26 @@ module Puma
|
|
22
32
|
@socket.closed?
|
23
33
|
end
|
24
34
|
|
35
|
+
# Returns a two element array,
|
36
|
+
# first is protocol version (SSL_get_version),
|
37
|
+
# second is 'handshake' state (SSL_state_string)
|
38
|
+
#
|
39
|
+
# Used for dropping tcp connections to ssl.
|
40
|
+
# See OpenSSL ssl/ssl_stat.c SSL_state_string for info
|
41
|
+
# @version 5.0.0
|
42
|
+
#
|
43
|
+
def ssl_version_state
|
44
|
+
IS_JRUBY ? [nil, nil] : @engine.ssl_vers_st
|
45
|
+
end
|
46
|
+
|
47
|
+
# Used to check the handshake status, in particular when a TCP connection
|
48
|
+
# is made with TLSv1.3 as an available protocol
|
49
|
+
# @version 5.0.0
|
50
|
+
def bad_tlsv1_3?
|
51
|
+
HAS_TLS1_3 && @engine.ssl_vers_st == ['TLSv1.3', 'SSLERR']
|
52
|
+
end
|
53
|
+
private :bad_tlsv1_3?
|
54
|
+
|
25
55
|
def readpartial(size)
|
26
56
|
while true
|
27
57
|
output = @engine.read
|
@@ -41,6 +71,7 @@ module Puma
|
|
41
71
|
|
42
72
|
def engine_read_all
|
43
73
|
output = @engine.read
|
74
|
+
raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
|
44
75
|
while output and additional_output = @engine.read
|
45
76
|
output << additional_output
|
46
77
|
end
|
@@ -107,14 +138,18 @@ module Puma
|
|
107
138
|
alias_method :<<, :write
|
108
139
|
|
109
140
|
# This is a temporary fix to deal with websockets code using
|
110
|
-
# write_nonblock.
|
141
|
+
# write_nonblock.
|
142
|
+
|
143
|
+
# The problem with implementing it properly
|
111
144
|
# is that it means we'd have to have the ability to rewind
|
112
145
|
# an engine because after we write+extract, the socket
|
113
146
|
# write_nonblock call might raise an exception and later
|
114
147
|
# code would pass the same data in, but the engine would think
|
115
|
-
# it had already written the data in.
|
116
|
-
#
|
117
|
-
#
|
148
|
+
# it had already written the data in.
|
149
|
+
#
|
150
|
+
# So for the time being (and since write blocking is quite rare),
|
151
|
+
# go ahead and actually block in write_nonblock.
|
152
|
+
#
|
118
153
|
def write_nonblock(data, *_)
|
119
154
|
write data
|
120
155
|
end
|
@@ -125,11 +160,14 @@ module Puma
|
|
125
160
|
|
126
161
|
def read_and_drop(timeout = 1)
|
127
162
|
return :timeout unless IO.select([@socket], nil, nil, timeout)
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
163
|
+
case @socket.read_nonblock(1024, exception: false)
|
164
|
+
when nil
|
165
|
+
:eof
|
166
|
+
when :wait_readable
|
167
|
+
:eagain
|
168
|
+
else
|
169
|
+
:drop
|
170
|
+
end
|
133
171
|
end
|
134
172
|
|
135
173
|
def should_drop_bytes?
|
@@ -141,9 +179,7 @@ module Puma
|
|
141
179
|
# Read any drop any partially initialized sockets and any received bytes during shutdown.
|
142
180
|
# Don't let this socket hold this loop forever.
|
143
181
|
# 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))
|
146
|
-
end
|
182
|
+
return if [:timeout, :eof].include?(read_and_drop(1)) while should_drop_bytes?
|
147
183
|
rescue IOError, SystemCallError
|
148
184
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
149
185
|
# nothing
|
@@ -166,12 +202,13 @@ module Puma
|
|
166
202
|
end
|
167
203
|
end
|
168
204
|
|
169
|
-
if
|
205
|
+
if IS_JRUBY
|
206
|
+
OPENSSL_NO_SSL3 = false
|
207
|
+
OPENSSL_NO_TLS1 = false
|
208
|
+
|
170
209
|
class SSLError < StandardError
|
171
210
|
# Define this for jruby even though it isn't used.
|
172
211
|
end
|
173
|
-
|
174
|
-
def self.check; end
|
175
212
|
end
|
176
213
|
|
177
214
|
class Context
|
@@ -183,7 +220,7 @@ module Puma
|
|
183
220
|
@no_tlsv1_1 = false
|
184
221
|
end
|
185
222
|
|
186
|
-
if
|
223
|
+
if IS_JRUBY
|
187
224
|
# jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
|
188
225
|
attr_reader :keystore
|
189
226
|
attr_accessor :keystore_pass
|
@@ -228,13 +265,13 @@ module Puma
|
|
228
265
|
|
229
266
|
# disables TLSv1
|
230
267
|
def no_tlsv1=(tlsv1)
|
231
|
-
raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
|
268
|
+
raise ArgumentError, "Invalid value of no_tlsv1=" unless ['true', 'false', true, false].include?(tlsv1)
|
232
269
|
@no_tlsv1 = tlsv1
|
233
270
|
end
|
234
271
|
|
235
272
|
# disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
|
236
273
|
def no_tlsv1_1=(tlsv1_1)
|
237
|
-
raise ArgumentError, "Invalid value of
|
274
|
+
raise ArgumentError, "Invalid value of no_tlsv1_1=" unless ['true', 'false', true, false].include?(tlsv1_1)
|
238
275
|
@no_tlsv1_1 = tlsv1_1
|
239
276
|
end
|
240
277
|
|
@@ -270,6 +307,11 @@ module Puma
|
|
270
307
|
Socket.new io, engine
|
271
308
|
end
|
272
309
|
|
310
|
+
# @version 5.0.0
|
311
|
+
def addr
|
312
|
+
@socket.addr
|
313
|
+
end
|
314
|
+
|
273
315
|
def close
|
274
316
|
@socket.close unless @socket.closed? # closed? call is for Windows
|
275
317
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Puma
|
2
|
+
module MiniSSL
|
3
|
+
class ContextBuilder
|
4
|
+
def initialize(params, events)
|
5
|
+
@params = params
|
6
|
+
@events = events
|
7
|
+
end
|
8
|
+
|
9
|
+
def context
|
10
|
+
ctx = MiniSSL::Context.new
|
11
|
+
|
12
|
+
if defined?(JRUBY_VERSION)
|
13
|
+
unless params['keystore']
|
14
|
+
events.error "Please specify the Java keystore via 'keystore='"
|
15
|
+
end
|
16
|
+
|
17
|
+
ctx.keystore = params['keystore']
|
18
|
+
|
19
|
+
unless params['keystore-pass']
|
20
|
+
events.error "Please specify the Java keystore password via 'keystore-pass='"
|
21
|
+
end
|
22
|
+
|
23
|
+
ctx.keystore_pass = params['keystore-pass']
|
24
|
+
ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
|
25
|
+
else
|
26
|
+
unless params['key']
|
27
|
+
events.error "Please specify the SSL key via 'key='"
|
28
|
+
end
|
29
|
+
|
30
|
+
ctx.key = params['key']
|
31
|
+
|
32
|
+
unless params['cert']
|
33
|
+
events.error "Please specify the SSL cert via 'cert='"
|
34
|
+
end
|
35
|
+
|
36
|
+
ctx.cert = params['cert']
|
37
|
+
|
38
|
+
if ['peer', 'force_peer'].include?(params['verify_mode'])
|
39
|
+
unless params['ca']
|
40
|
+
events.error "Please specify the SSL ca via 'ca='"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
ctx.ca = params['ca'] if params['ca']
|
45
|
+
ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
|
46
|
+
end
|
47
|
+
|
48
|
+
ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
|
49
|
+
ctx.no_tlsv1_1 = true if params['no_tlsv1_1'] == 'true'
|
50
|
+
|
51
|
+
if params['verify_mode']
|
52
|
+
ctx.verify_mode = case params['verify_mode']
|
53
|
+
when "peer"
|
54
|
+
MiniSSL::VERIFY_PEER
|
55
|
+
when "force_peer"
|
56
|
+
MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
57
|
+
when "none"
|
58
|
+
MiniSSL::VERIFY_NONE
|
59
|
+
else
|
60
|
+
events.error "Please specify a valid verify_mode="
|
61
|
+
MiniSSL::VERIFY_NONE
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
ctx
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
attr_reader :params, :events
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/puma/null_io.rb
CHANGED
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
|
@@ -62,8 +62,11 @@ module Puma
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def fire_background
|
65
|
-
@background.
|
66
|
-
Thread.new
|
65
|
+
@background.each_with_index do |b, i|
|
66
|
+
Thread.new do
|
67
|
+
Puma.set_thread_name "plugin background #{i}"
|
68
|
+
b.call
|
69
|
+
end
|
67
70
|
end
|
68
71
|
end
|
69
72
|
end
|
@@ -101,17 +104,8 @@ module Puma
|
|
101
104
|
Plugins.register name, cls
|
102
105
|
end
|
103
106
|
|
104
|
-
def initialize(loader)
|
105
|
-
@loader = loader
|
106
|
-
end
|
107
|
-
|
108
107
|
def in_background(&blk)
|
109
108
|
Plugins.add_background blk
|
110
109
|
end
|
111
|
-
|
112
|
-
def workers_supported?
|
113
|
-
return false if Puma.jruby? || Puma.windows?
|
114
|
-
true
|
115
|
-
end
|
116
110
|
end
|
117
111
|
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
|
}
|
data/lib/puma/reactor.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'puma/util'
|
4
|
-
require 'puma/minissl'
|
4
|
+
require 'puma/minissl' if ::Puma::HAS_SSL
|
5
5
|
|
6
6
|
require 'nio'
|
7
7
|
|
@@ -189,7 +189,12 @@ module Puma
|
|
189
189
|
if submon.value == @ready
|
190
190
|
false
|
191
191
|
else
|
192
|
-
submon.value.
|
192
|
+
if submon.value.can_close?
|
193
|
+
submon.value.close
|
194
|
+
else
|
195
|
+
# Pass remaining open client connections to the thread pool.
|
196
|
+
@app_pool << submon.value
|
197
|
+
end
|
193
198
|
begin
|
194
199
|
selector.deregister submon.value
|
195
200
|
rescue IOError
|
@@ -225,7 +230,7 @@ module Puma
|
|
225
230
|
# will be flooding them with errors when persistent connections
|
226
231
|
# are closed.
|
227
232
|
rescue ConnectionError
|
228
|
-
c.
|
233
|
+
c.write_error(500)
|
229
234
|
c.close
|
230
235
|
|
231
236
|
clear_monitor mon
|
@@ -237,7 +242,8 @@ module Puma
|
|
237
242
|
ssl_socket = c.io
|
238
243
|
begin
|
239
244
|
addr = ssl_socket.peeraddr.last
|
240
|
-
|
245
|
+
# EINVAL can happen when browser closes socket w/security exception
|
246
|
+
rescue IOError, Errno::EINVAL
|
241
247
|
addr = "<unknown>"
|
242
248
|
end
|
243
249
|
|
@@ -246,22 +252,22 @@ module Puma
|
|
246
252
|
c.close
|
247
253
|
clear_monitor mon
|
248
254
|
|
249
|
-
@events.ssl_error
|
255
|
+
@events.ssl_error e, addr, cert
|
250
256
|
|
251
257
|
# The client doesn't know HTTP well
|
252
258
|
rescue HttpParserError => e
|
253
259
|
@server.lowlevel_error(e, c.env)
|
254
260
|
|
255
|
-
c.
|
261
|
+
c.write_error(400)
|
256
262
|
c.close
|
257
263
|
|
258
264
|
clear_monitor mon
|
259
265
|
|
260
|
-
@events.parse_error
|
266
|
+
@events.parse_error e, c
|
261
267
|
rescue StandardError => e
|
262
268
|
@server.lowlevel_error(e, c.env)
|
263
269
|
|
264
|
-
c.
|
270
|
+
c.write_error(500)
|
265
271
|
c.close
|
266
272
|
|
267
273
|
clear_monitor mon
|
@@ -277,7 +283,7 @@ module Puma
|
|
277
283
|
while @timeouts.first.value.timeout_at < now
|
278
284
|
mon = @timeouts.shift
|
279
285
|
c = mon.value
|
280
|
-
c.
|
286
|
+
c.write_error(408) if c.in_data_phase
|
281
287
|
c.close
|
282
288
|
|
283
289
|
clear_monitor mon
|
@@ -307,6 +313,7 @@ module Puma
|
|
307
313
|
|
308
314
|
def run_in_thread
|
309
315
|
@thread = Thread.new do
|
316
|
+
Puma.set_thread_name "reactor"
|
310
317
|
begin
|
311
318
|
run_internal
|
312
319
|
rescue StandardError => e
|
data/lib/puma/runner.rb
CHANGED
@@ -17,10 +17,6 @@ module Puma
|
|
17
17
|
@started_at = Time.now
|
18
18
|
end
|
19
19
|
|
20
|
-
def daemon?
|
21
|
-
@options[:daemon]
|
22
|
-
end
|
23
|
-
|
24
20
|
def development?
|
25
21
|
@options[:environment] == "development"
|
26
22
|
end
|
@@ -33,7 +29,8 @@ module Puma
|
|
33
29
|
@events.log str
|
34
30
|
end
|
35
31
|
|
36
|
-
|
32
|
+
# @version 5.0.0
|
33
|
+
def stop_control
|
37
34
|
@control.stop(true) if @control
|
38
35
|
end
|
39
36
|
|
@@ -51,36 +48,27 @@ module Puma
|
|
51
48
|
|
52
49
|
require 'puma/app/status'
|
53
50
|
|
54
|
-
uri = URI.parse str
|
55
|
-
|
56
|
-
app = Puma::App::Status.new @launcher
|
57
|
-
|
58
51
|
if token = @options[:control_auth_token]
|
59
|
-
|
52
|
+
token = nil if token.empty? || token == 'none'
|
60
53
|
end
|
61
54
|
|
55
|
+
app = Puma::App::Status.new @launcher, token
|
56
|
+
|
62
57
|
control = Puma::Server.new app, @launcher.events
|
63
58
|
control.min_threads = 0
|
64
59
|
control.max_threads = 1
|
65
60
|
|
66
|
-
|
67
|
-
when "tcp"
|
68
|
-
log "* Starting control server on #{str}"
|
69
|
-
control.add_tcp_listener uri.host, uri.port
|
70
|
-
when "unix"
|
71
|
-
log "* Starting control server on #{str}"
|
72
|
-
path = "#{uri.host}#{uri.path}"
|
73
|
-
mask = @options[:control_url_umask]
|
74
|
-
|
75
|
-
control.add_unix_listener path, mask
|
76
|
-
else
|
77
|
-
error "Invalid control URI: #{str}"
|
78
|
-
end
|
61
|
+
control.binder.parse [str], self, 'Starting control server'
|
79
62
|
|
80
63
|
control.run
|
81
64
|
@control = control
|
82
65
|
end
|
83
66
|
|
67
|
+
# @version 5.0.0
|
68
|
+
def close_control_listeners
|
69
|
+
@control.binder.close_listeners if @control
|
70
|
+
end
|
71
|
+
|
84
72
|
def ruby_engine
|
85
73
|
if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
|
86
74
|
"ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
|
@@ -101,10 +89,6 @@ module Puma
|
|
101
89
|
log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
|
102
90
|
log "* Min threads: #{min_t}, max threads: #{max_t}"
|
103
91
|
log "* Environment: #{ENV['RACK_ENV']}"
|
104
|
-
|
105
|
-
if @options[:mode] == :tcp
|
106
|
-
log "* Mode: Lopez Express (tcp)"
|
107
|
-
end
|
108
92
|
end
|
109
93
|
|
110
94
|
def redirected_io?
|
@@ -143,7 +127,6 @@ module Puma
|
|
143
127
|
exit 1
|
144
128
|
end
|
145
129
|
|
146
|
-
# Load the app before we daemonize.
|
147
130
|
begin
|
148
131
|
@app = @launcher.config.app
|
149
132
|
rescue Exception => e
|
@@ -167,10 +150,6 @@ module Puma
|
|
167
150
|
server.max_threads = max_t
|
168
151
|
server.inherit_binder @launcher.binder
|
169
152
|
|
170
|
-
if @options[:mode] == :tcp
|
171
|
-
server.tcp_mode!
|
172
|
-
end
|
173
|
-
|
174
153
|
if @options[:early_hints]
|
175
154
|
server.early_hints = true
|
176
155
|
end
|