puma 5.3.2 → 5.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/History.md +216 -11
- data/README.md +47 -6
- data/docs/architecture.md +49 -16
- data/docs/compile_options.md +4 -2
- data/docs/deployment.md +53 -67
- data/docs/plugins.md +15 -15
- data/docs/rails_dev_mode.md +2 -3
- data/docs/restart.md +6 -6
- data/docs/signals.md +11 -10
- data/docs/stats.md +8 -8
- data/docs/systemd.md +64 -67
- data/ext/puma_http11/extconf.rb +34 -6
- data/ext/puma_http11/http11_parser.c +23 -10
- data/ext/puma_http11/http11_parser_common.rl +1 -1
- data/ext/puma_http11/mini_ssl.c +90 -12
- data/ext/puma_http11/org/jruby/puma/Http11.java +2 -0
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +49 -47
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +38 -55
- data/ext/puma_http11/puma_http11.c +1 -1
- data/lib/puma/app/status.rb +7 -4
- data/lib/puma/binder.rb +51 -6
- data/lib/puma/cli.rb +14 -4
- data/lib/puma/client.rb +143 -25
- data/lib/puma/cluster/worker.rb +8 -18
- data/lib/puma/cluster/worker_handle.rb +4 -0
- data/lib/puma/cluster.rb +30 -24
- data/lib/puma/configuration.rb +4 -1
- data/lib/puma/const.rb +17 -8
- data/lib/puma/control_cli.rb +19 -13
- data/lib/puma/detect.rb +8 -2
- data/lib/puma/dsl.rb +111 -13
- data/lib/puma/{json.rb → json_serialization.rb} +1 -1
- data/lib/puma/launcher.rb +15 -1
- data/lib/puma/minissl/context_builder.rb +8 -6
- data/lib/puma/minissl.rb +33 -27
- data/lib/puma/null_io.rb +5 -0
- data/lib/puma/plugin.rb +2 -2
- data/lib/puma/rack/builder.rb +1 -1
- data/lib/puma/request.rb +35 -13
- data/lib/puma/runner.rb +22 -8
- data/lib/puma/server.rb +37 -29
- data/lib/puma/state_file.rb +42 -7
- data/lib/puma/thread_pool.rb +7 -5
- data/lib/puma/util.rb +20 -4
- data/lib/puma.rb +6 -4
- data/lib/rack/version_restriction.rb +15 -0
- data/tools/Dockerfile +1 -1
- metadata +8 -7
@@ -6,6 +6,7 @@ import org.jruby.RubyModule;
|
|
6
6
|
import org.jruby.RubyObject;
|
7
7
|
import org.jruby.RubyString;
|
8
8
|
import org.jruby.anno.JRubyMethod;
|
9
|
+
import org.jruby.exceptions.RaiseException;
|
9
10
|
import org.jruby.javasupport.JavaEmbedUtils;
|
10
11
|
import org.jruby.runtime.Block;
|
11
12
|
import org.jruby.runtime.ObjectAllocator;
|
@@ -80,11 +81,11 @@ public class MiniSSL extends RubyObject {
|
|
80
81
|
/**
|
81
82
|
* Writes bytes to the buffer after ensuring there's room
|
82
83
|
*/
|
83
|
-
|
84
|
-
if (buffer.remaining() <
|
85
|
-
resize(buffer.limit() +
|
84
|
+
private void put(byte[] bytes, final int offset, final int length) {
|
85
|
+
if (buffer.remaining() < length) {
|
86
|
+
resize(buffer.limit() + length);
|
86
87
|
}
|
87
|
-
buffer.put(bytes);
|
88
|
+
buffer.put(bytes, offset, length);
|
88
89
|
}
|
89
90
|
|
90
91
|
/**
|
@@ -115,7 +116,7 @@ public class MiniSSL extends RubyObject {
|
|
115
116
|
|
116
117
|
buffer.get(bss);
|
117
118
|
buffer.clear();
|
118
|
-
return new ByteList(bss);
|
119
|
+
return new ByteList(bss, false);
|
119
120
|
}
|
120
121
|
|
121
122
|
@Override
|
@@ -174,8 +175,6 @@ public class MiniSSL extends RubyObject {
|
|
174
175
|
@JRubyMethod
|
175
176
|
public IRubyObject initialize(ThreadContext threadContext, IRubyObject miniSSLContext)
|
176
177
|
throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
|
177
|
-
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
|
178
|
-
KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
|
179
178
|
|
180
179
|
String keystoreFile = miniSSLContext.callMethod(threadContext, "keystore").convertToString().asJavaString();
|
181
180
|
KeyManagerFactory kmf = keyManagerFactoryMap.get(keystoreFile);
|
@@ -230,14 +229,9 @@ public class MiniSSL extends RubyObject {
|
|
230
229
|
|
231
230
|
@JRubyMethod
|
232
231
|
public IRubyObject inject(IRubyObject arg) {
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
return this;
|
237
|
-
} catch (Exception e) {
|
238
|
-
e.printStackTrace();
|
239
|
-
throw new RuntimeException(e);
|
240
|
-
}
|
232
|
+
ByteList bytes = arg.convertToString().getByteList();
|
233
|
+
inboundNetData.put(bytes.unsafeBytes(), bytes.getBegin(), bytes.getRealSize());
|
234
|
+
return this;
|
241
235
|
}
|
242
236
|
|
243
237
|
private enum SSLOperation {
|
@@ -285,19 +279,11 @@ public class MiniSSL extends RubyObject {
|
|
285
279
|
}
|
286
280
|
}
|
287
281
|
|
288
|
-
// after each op, run any delegated tasks if needed
|
289
|
-
if(res.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
|
290
|
-
Runnable runnable;
|
291
|
-
while ((runnable = engine.getDelegatedTask()) != null) {
|
292
|
-
runnable.run();
|
293
|
-
}
|
294
|
-
}
|
295
|
-
|
296
282
|
return res;
|
297
283
|
}
|
298
284
|
|
299
285
|
@JRubyMethod
|
300
|
-
public IRubyObject read()
|
286
|
+
public IRubyObject read() {
|
301
287
|
try {
|
302
288
|
inboundNetData.flip();
|
303
289
|
|
@@ -310,11 +296,12 @@ public class MiniSSL extends RubyObject {
|
|
310
296
|
|
311
297
|
HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
|
312
298
|
boolean done = false;
|
313
|
-
SSLEngineResult res = null;
|
314
299
|
while (!done) {
|
300
|
+
SSLEngineResult res;
|
315
301
|
switch (handshakeStatus) {
|
316
302
|
case NEED_WRAP:
|
317
303
|
res = doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
|
304
|
+
handshakeStatus = res.getHandshakeStatus();
|
318
305
|
break;
|
319
306
|
case NEED_UNWRAP:
|
320
307
|
res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
|
@@ -322,13 +309,18 @@ public class MiniSSL extends RubyObject {
|
|
322
309
|
// need more data before we can shake more hands
|
323
310
|
done = true;
|
324
311
|
}
|
312
|
+
handshakeStatus = res.getHandshakeStatus();
|
313
|
+
break;
|
314
|
+
case NEED_TASK:
|
315
|
+
Runnable runnable;
|
316
|
+
while ((runnable = engine.getDelegatedTask()) != null) {
|
317
|
+
runnable.run();
|
318
|
+
}
|
319
|
+
handshakeStatus = engine.getHandshakeStatus();
|
325
320
|
break;
|
326
321
|
default:
|
327
322
|
done = true;
|
328
323
|
}
|
329
|
-
if (!done) {
|
330
|
-
handshakeStatus = res.getHandshakeStatus();
|
331
|
-
}
|
332
324
|
}
|
333
325
|
|
334
326
|
if (inboundNetData.hasRemaining()) {
|
@@ -342,55 +334,46 @@ public class MiniSSL extends RubyObject {
|
|
342
334
|
return getRuntime().getNil();
|
343
335
|
}
|
344
336
|
|
345
|
-
RubyString
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
throw
|
337
|
+
return RubyString.newString(getRuntime(), appDataByteList);
|
338
|
+
} catch (SSLException e) {
|
339
|
+
RaiseException re = getRuntime().newEOFError(e.getMessage());
|
340
|
+
re.initCause(e);
|
341
|
+
throw re;
|
350
342
|
}
|
351
343
|
}
|
352
344
|
|
353
345
|
@JRubyMethod
|
354
346
|
public IRubyObject write(IRubyObject arg) {
|
355
|
-
|
356
|
-
|
357
|
-
outboundAppData = new MiniSSLBuffer(bls);
|
347
|
+
byte[] bls = arg.convertToString().getBytes();
|
348
|
+
outboundAppData = new MiniSSLBuffer(bls);
|
358
349
|
|
359
|
-
|
360
|
-
} catch (Exception e) {
|
361
|
-
e.printStackTrace();
|
362
|
-
throw new RuntimeException(e);
|
363
|
-
}
|
350
|
+
return getRuntime().newFixnum(bls.length);
|
364
351
|
}
|
365
352
|
|
366
353
|
@JRubyMethod
|
367
|
-
public IRubyObject extract()
|
354
|
+
public IRubyObject extract(ThreadContext context) {
|
368
355
|
try {
|
369
356
|
ByteList dataByteList = outboundNetData.asByteList();
|
370
357
|
if (dataByteList != null) {
|
371
|
-
RubyString
|
372
|
-
str.setValue(dataByteList);
|
373
|
-
return str;
|
358
|
+
return RubyString.newString(context.runtime, dataByteList);
|
374
359
|
}
|
375
360
|
|
376
361
|
if (!outboundAppData.hasRemaining()) {
|
377
|
-
return
|
362
|
+
return context.nil;
|
378
363
|
}
|
379
364
|
|
380
365
|
outboundNetData.clear();
|
381
366
|
doOp(SSLOperation.WRAP, outboundAppData, outboundNetData);
|
382
367
|
dataByteList = outboundNetData.asByteList();
|
383
368
|
if (dataByteList == null) {
|
384
|
-
return
|
369
|
+
return context.nil;
|
385
370
|
}
|
386
371
|
|
387
|
-
RubyString
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
e.printStackTrace();
|
393
|
-
throw new RuntimeException(e);
|
372
|
+
return RubyString.newString(context.runtime, dataByteList);
|
373
|
+
} catch (SSLException e) {
|
374
|
+
RaiseException ex = context.runtime.newRuntimeError(e.toString());
|
375
|
+
ex.initCause(e);
|
376
|
+
throw ex;
|
394
377
|
}
|
395
378
|
}
|
396
379
|
|
@@ -398,7 +381,7 @@ public class MiniSSL extends RubyObject {
|
|
398
381
|
public IRubyObject peercert() throws CertificateEncodingException {
|
399
382
|
try {
|
400
383
|
return JavaEmbedUtils.javaToRuby(getRuntime(), engine.getSession().getPeerCertificates()[0].getEncoded());
|
401
|
-
} catch (SSLPeerUnverifiedException
|
384
|
+
} catch (SSLPeerUnverifiedException e) {
|
402
385
|
return getRuntime().getNil();
|
403
386
|
}
|
404
387
|
}
|
data/lib/puma/app/status.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require 'puma/
|
2
|
+
require 'puma/json_serialization'
|
3
3
|
|
4
4
|
module Puma
|
5
5
|
module App
|
@@ -39,6 +39,9 @@ module Puma
|
|
39
39
|
when 'phased-restart'
|
40
40
|
@launcher.phased_restart ? 200 : 404
|
41
41
|
|
42
|
+
when 'refork'
|
43
|
+
@launcher.refork ? 200 : 404
|
44
|
+
|
42
45
|
when 'reload-worker-directory'
|
43
46
|
@launcher.send(:reload_worker_directory) ? 200 : 404
|
44
47
|
|
@@ -46,17 +49,17 @@ module Puma
|
|
46
49
|
GC.start ; 200
|
47
50
|
|
48
51
|
when 'gc-stats'
|
49
|
-
Puma::
|
52
|
+
Puma::JSONSerialization.generate GC.stat
|
50
53
|
|
51
54
|
when 'stats'
|
52
|
-
Puma::
|
55
|
+
Puma::JSONSerialization.generate @launcher.stats
|
53
56
|
|
54
57
|
when 'thread-backtraces'
|
55
58
|
backtraces = []
|
56
59
|
@launcher.thread_status do |name, backtrace|
|
57
60
|
backtraces << { name: name, backtrace: backtrace }
|
58
61
|
end
|
59
|
-
Puma::
|
62
|
+
Puma::JSONSerialization.generate backtraces
|
60
63
|
|
61
64
|
else
|
62
65
|
return rack_response(404, "Unsupported action", 'text/plain')
|
data/lib/puma/binder.rb
CHANGED
@@ -30,6 +30,7 @@ module Puma
|
|
30
30
|
|
31
31
|
def initialize(events, conf = Configuration.new)
|
32
32
|
@events = events
|
33
|
+
@conf = conf
|
33
34
|
@listeners = []
|
34
35
|
@inherited_fds = {}
|
35
36
|
@activated_sockets = {}
|
@@ -41,6 +42,7 @@ module Puma
|
|
41
42
|
"rack.multithread".freeze => conf.options[:max_threads] > 1,
|
42
43
|
"rack.multiprocess".freeze => conf.options[:workers] >= 1,
|
43
44
|
"rack.run_once".freeze => false,
|
45
|
+
RACK_URL_SCHEME => conf.options[:rack_url_scheme],
|
44
46
|
"SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
|
45
47
|
|
46
48
|
# I'd like to set a default CONTENT_TYPE here but some things
|
@@ -56,6 +58,7 @@ module Puma
|
|
56
58
|
|
57
59
|
@envs = {}
|
58
60
|
@ios = []
|
61
|
+
localhost_authority
|
59
62
|
end
|
60
63
|
|
61
64
|
attr_reader :ios
|
@@ -95,6 +98,7 @@ module Puma
|
|
95
98
|
# @version 5.0.0
|
96
99
|
#
|
97
100
|
def create_activated_fds(env_hash)
|
101
|
+
@events.debug "ENV['LISTEN_FDS'] #{ENV['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
|
98
102
|
return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
|
99
103
|
env_hash['LISTEN_FDS'].to_i.times do |index|
|
100
104
|
sock = TCPServer.for_fd(socket_activation_fd(index))
|
@@ -164,9 +168,9 @@ module Puma
|
|
164
168
|
params = Util.parse_query uri.query
|
165
169
|
|
166
170
|
opt = params.key?('low_latency') && params['low_latency'] != 'false'
|
167
|
-
|
171
|
+
backlog = params.fetch('backlog', 1024).to_i
|
168
172
|
|
169
|
-
io = add_tcp_listener uri.host, uri.port, opt,
|
173
|
+
io = add_tcp_listener uri.host, uri.port, opt, backlog
|
170
174
|
|
171
175
|
@ios[ios_len..-1].each do |i|
|
172
176
|
addr = loc_addr_str i
|
@@ -185,10 +189,11 @@ module Puma
|
|
185
189
|
end
|
186
190
|
|
187
191
|
if fd = @inherited_fds.delete(str)
|
188
|
-
@unix_paths << path unless abstract
|
192
|
+
@unix_paths << path unless abstract || File.exist?(path)
|
189
193
|
io = inherit_unix_listener path, fd
|
190
194
|
logger.log "* Inherited #{str}"
|
191
|
-
elsif sock = @activated_sockets.delete([ :unix, path ])
|
195
|
+
elsif sock = @activated_sockets.delete([ :unix, path ]) ||
|
196
|
+
@activated_sockets.delete([ :unix, File.realdirpath(path) ])
|
192
197
|
@unix_paths << path unless abstract || File.exist?(path)
|
193
198
|
io = inherit_unix_listener path, sock
|
194
199
|
logger.log "* Activated #{str}"
|
@@ -224,7 +229,25 @@ module Puma
|
|
224
229
|
raise "Puma compiled without SSL support" unless HAS_SSL
|
225
230
|
|
226
231
|
params = Util.parse_query uri.query
|
227
|
-
|
232
|
+
|
233
|
+
# If key and certs are not defined and localhost gem is required.
|
234
|
+
# localhost gem will be used for self signed
|
235
|
+
# Load localhost authority if not loaded.
|
236
|
+
if params.values_at('cert', 'key').all? { |v| v.to_s.empty? }
|
237
|
+
ctx = localhost_authority && localhost_authority_context
|
238
|
+
end
|
239
|
+
|
240
|
+
ctx ||=
|
241
|
+
begin
|
242
|
+
# Extract cert_pem and key_pem from options[:store] if present
|
243
|
+
['cert', 'key'].each do |v|
|
244
|
+
if params[v] && params[v].start_with?('store:')
|
245
|
+
index = Integer(params.delete(v).split('store:').last)
|
246
|
+
params["#{v}_pem"] = @conf.options[:store][index]
|
247
|
+
end
|
248
|
+
end
|
249
|
+
MiniSSL::ContextBuilder.new(params, @events).context
|
250
|
+
end
|
228
251
|
|
229
252
|
if fd = @inherited_fds.delete(str)
|
230
253
|
logger.log "* Inherited #{str}"
|
@@ -234,7 +257,8 @@ module Puma
|
|
234
257
|
logger.log "* Activated #{str}"
|
235
258
|
else
|
236
259
|
ios_len = @ios.length
|
237
|
-
|
260
|
+
backlog = params.fetch('backlog', 1024).to_i
|
261
|
+
io = add_ssl_listener uri.host, uri.port, ctx, optimize_for_latency = true, backlog
|
238
262
|
|
239
263
|
@ios[ios_len..-1].each do |i|
|
240
264
|
addr = loc_addr_str i
|
@@ -282,6 +306,22 @@ module Puma
|
|
282
306
|
end
|
283
307
|
end
|
284
308
|
|
309
|
+
def localhost_authority
|
310
|
+
@localhost_authority ||= Localhost::Authority.fetch if defined?(Localhost::Authority) && !Puma::IS_JRUBY
|
311
|
+
end
|
312
|
+
|
313
|
+
def localhost_authority_context
|
314
|
+
return unless localhost_authority
|
315
|
+
|
316
|
+
key_path, crt_path = if [:key_path, :certificate_path].all? { |m| localhost_authority.respond_to?(m) }
|
317
|
+
[localhost_authority.key_path, localhost_authority.certificate_path]
|
318
|
+
else
|
319
|
+
local_certificates_path = File.expand_path("~/.localhost")
|
320
|
+
[File.join(local_certificates_path, "localhost.key"), File.join(local_certificates_path, "localhost.crt")]
|
321
|
+
end
|
322
|
+
MiniSSL::ContextBuilder.new({ "key" => key_path, "cert" => crt_path }, @events).context
|
323
|
+
end
|
324
|
+
|
285
325
|
# Tell the server to listen on host +host+, port +port+.
|
286
326
|
# If +optimize_for_latency+ is true (the default) then clients connecting
|
287
327
|
# will be optimized for latency over throughput.
|
@@ -299,6 +339,7 @@ module Puma
|
|
299
339
|
|
300
340
|
host = host[1..-2] if host and host[0..0] == '['
|
301
341
|
tcp_server = TCPServer.new(host, port)
|
342
|
+
|
302
343
|
if optimize_for_latency
|
303
344
|
tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
304
345
|
end
|
@@ -320,6 +361,8 @@ module Puma
|
|
320
361
|
optimize_for_latency=true, backlog=1024)
|
321
362
|
|
322
363
|
raise "Puma compiled without SSL support" unless HAS_SSL
|
364
|
+
# Puma will try to use local authority context if context is supplied nil
|
365
|
+
ctx ||= localhost_authority_context
|
323
366
|
|
324
367
|
if host == "localhost"
|
325
368
|
loopback_addresses.each do |addr|
|
@@ -347,6 +390,8 @@ module Puma
|
|
347
390
|
|
348
391
|
def inherit_ssl_listener(fd, ctx)
|
349
392
|
raise "Puma compiled without SSL support" unless HAS_SSL
|
393
|
+
# Puma will try to use local authority context if context is supplied nil
|
394
|
+
ctx ||= localhost_authority_context
|
350
395
|
|
351
396
|
s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
|
352
397
|
|
data/lib/puma/cli.rb
CHANGED
@@ -11,16 +11,17 @@ require 'puma/events'
|
|
11
11
|
|
12
12
|
module Puma
|
13
13
|
class << self
|
14
|
-
# The CLI exports
|
15
|
-
# apps to pick it up. An app
|
16
|
-
#
|
17
|
-
#
|
14
|
+
# The CLI exports a Puma::Configuration instance here to allow
|
15
|
+
# apps to pick it up. An app must load this object conditionally
|
16
|
+
# because it is not set if the app is launched via any mechanism
|
17
|
+
# other than the CLI class.
|
18
18
|
attr_accessor :cli_config
|
19
19
|
end
|
20
20
|
|
21
21
|
# Handles invoke a Puma::Server in a command line style.
|
22
22
|
#
|
23
23
|
class CLI
|
24
|
+
# @deprecated 6.0.0
|
24
25
|
KEYS_NOT_TO_PERSIST_IN_STATE = Launcher::KEYS_NOT_TO_PERSIST_IN_STATE
|
25
26
|
|
26
27
|
# Create a new CLI object using +argv+ as the command line
|
@@ -112,6 +113,11 @@ module Puma
|
|
112
113
|
file_config.load arg
|
113
114
|
end
|
114
115
|
|
116
|
+
# Identical to supplying --config "-", but more semantic
|
117
|
+
o.on "--no-config", "Prevent Puma from searching for a config file" do |arg|
|
118
|
+
file_config.load "-"
|
119
|
+
end
|
120
|
+
|
115
121
|
o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg|
|
116
122
|
configure_control_url(arg)
|
117
123
|
end
|
@@ -179,6 +185,10 @@ module Puma
|
|
179
185
|
user_config.restart_command cmd
|
180
186
|
end
|
181
187
|
|
188
|
+
o.on "-s", "--silent", "Do not log prompt messages other than errors" do
|
189
|
+
@events = Events.new NullIO.new, $stderr
|
190
|
+
end
|
191
|
+
|
182
192
|
o.on "-S", "--state PATH", "Where to store the state details" do |arg|
|
183
193
|
user_config.state_path arg
|
184
194
|
end
|