puma 3.0.0.rc1 → 5.0.0.beta1
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.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/{History.txt → History.md} +703 -70
- data/LICENSE +23 -20
- data/README.md +173 -163
- data/docs/architecture.md +37 -0
- data/{DEPLOYMENT.md → docs/deployment.md} +28 -6
- 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 +13 -0
- data/docs/jungle/rc.d/README.md +74 -0
- data/docs/jungle/rc.d/puma +61 -0
- data/docs/jungle/rc.d/puma.conf +10 -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 +1 -1
- data/docs/nginx.md +2 -2
- data/docs/plugins.md +38 -0
- data/docs/restart.md +41 -0
- data/docs/signals.md +57 -3
- data/docs/systemd.md +228 -0
- data/ext/puma_http11/PumaHttp11Service.java +2 -2
- data/ext/puma_http11/extconf.rb +16 -0
- data/ext/puma_http11/http11_parser.c +287 -468
- data/ext/puma_http11/http11_parser.h +1 -0
- data/ext/puma_http11/http11_parser.java.rl +21 -37
- data/ext/puma_http11/http11_parser.rl +10 -9
- data/ext/puma_http11/http11_parser_common.rl +4 -4
- data/ext/puma_http11/mini_ssl.c +159 -10
- data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +99 -132
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +30 -6
- data/ext/puma_http11/puma_http11.c +6 -38
- data/lib/puma.rb +25 -5
- data/lib/puma/accept_nonblock.rb +7 -1
- data/lib/puma/app/status.rb +53 -26
- data/lib/puma/binder.rb +150 -119
- data/lib/puma/cli.rb +56 -38
- data/lib/puma/client.rb +277 -80
- data/lib/puma/cluster.rb +326 -130
- data/lib/puma/commonlogger.rb +21 -20
- data/lib/puma/configuration.rb +160 -161
- data/lib/puma/const.rb +50 -47
- data/lib/puma/control_cli.rb +104 -63
- data/lib/puma/detect.rb +13 -1
- data/lib/puma/dsl.rb +463 -114
- data/lib/puma/events.rb +22 -13
- data/lib/puma/io_buffer.rb +9 -5
- data/lib/puma/jruby_restart.rb +2 -59
- data/lib/puma/launcher.rb +195 -105
- data/lib/puma/minissl.rb +110 -4
- data/lib/puma/minissl/context_builder.rb +76 -0
- data/lib/puma/null_io.rb +9 -14
- data/lib/puma/plugin.rb +32 -12
- data/lib/puma/plugin/tmp_restart.rb +19 -6
- data/lib/puma/rack/builder.rb +7 -5
- data/lib/puma/rack/urlmap.rb +11 -8
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +242 -32
- data/lib/puma/runner.rb +41 -30
- data/lib/puma/server.rb +265 -183
- data/lib/puma/single.rb +22 -63
- data/lib/puma/state_file.rb +9 -2
- data/lib/puma/thread_pool.rb +179 -68
- data/lib/puma/util.rb +3 -11
- data/lib/rack/handler/puma.rb +60 -11
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +1 -2
- metadata +35 -99
- data/COPYING +0 -55
- data/Gemfile +0 -13
- data/Manifest.txt +0 -79
- data/Rakefile +0 -158
- data/docs/config.md +0 -0
- data/ext/puma_http11/io_buffer.c +0 -155
- data/lib/puma/capistrano.rb +0 -94
- data/lib/puma/compat.rb +0 -18
- data/lib/puma/convenient.rb +0 -23
- data/lib/puma/daemon_ext.rb +0 -31
- data/lib/puma/delegation.rb +0 -11
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_18.rb +0 -56
- data/lib/puma/rack/backports/uri/common_192.rb +0 -52
- data/lib/puma/rack/backports/uri/common_193.rb +0 -29
- data/lib/puma/tcp_logger.rb +0 -32
- data/puma.gemspec +0 -52
- data/tools/jungle/README.md +0 -9
- data/tools/jungle/init.d/README.md +0 -54
- data/tools/jungle/init.d/puma +0 -394
- data/tools/jungle/init.d/run-puma +0 -3
@@ -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.javasupport.JavaEmbedUtils;
|
9
10
|
import org.jruby.runtime.Block;
|
10
11
|
import org.jruby.runtime.ObjectAllocator;
|
11
12
|
import org.jruby.runtime.ThreadContext;
|
@@ -18,15 +19,18 @@ import javax.net.ssl.SSLContext;
|
|
18
19
|
import javax.net.ssl.SSLEngine;
|
19
20
|
import javax.net.ssl.SSLEngineResult;
|
20
21
|
import javax.net.ssl.SSLException;
|
22
|
+
import javax.net.ssl.SSLPeerUnverifiedException;
|
21
23
|
import javax.net.ssl.SSLSession;
|
22
24
|
import java.io.FileInputStream;
|
23
25
|
import java.io.IOException;
|
26
|
+
import java.nio.Buffer;
|
24
27
|
import java.nio.ByteBuffer;
|
25
28
|
import java.security.KeyManagementException;
|
26
29
|
import java.security.KeyStore;
|
27
30
|
import java.security.KeyStoreException;
|
28
31
|
import java.security.NoSuchAlgorithmException;
|
29
32
|
import java.security.UnrecoverableKeyException;
|
33
|
+
import java.security.cert.CertificateEncodingException;
|
30
34
|
import java.security.cert.CertificateException;
|
31
35
|
|
32
36
|
import static javax.net.ssl.SSLEngineResult.Status;
|
@@ -62,7 +66,7 @@ public class MiniSSL extends RubyObject {
|
|
62
66
|
|
63
67
|
public void clear() { buffer.clear(); }
|
64
68
|
public void compact() { buffer.compact(); }
|
65
|
-
public void flip() { buffer.flip(); }
|
69
|
+
public void flip() { ((Buffer) buffer).flip(); }
|
66
70
|
public boolean hasRemaining() { return buffer.hasRemaining(); }
|
67
71
|
public int position() { return buffer.position(); }
|
68
72
|
|
@@ -86,7 +90,7 @@ public class MiniSSL extends RubyObject {
|
|
86
90
|
public void resize(int newCapacity) {
|
87
91
|
if (newCapacity > buffer.capacity()) {
|
88
92
|
ByteBuffer dstTmp = ByteBuffer.allocate(newCapacity);
|
89
|
-
|
93
|
+
flip();
|
90
94
|
dstTmp.put(buffer);
|
91
95
|
buffer = dstTmp;
|
92
96
|
} else {
|
@@ -98,7 +102,7 @@ public class MiniSSL extends RubyObject {
|
|
98
102
|
* Drains the buffer to a ByteList, or returns null for an empty buffer
|
99
103
|
*/
|
100
104
|
public ByteList asByteList() {
|
101
|
-
|
105
|
+
flip();
|
102
106
|
if (!buffer.hasRemaining()) {
|
103
107
|
buffer.clear();
|
104
108
|
return null;
|
@@ -155,7 +159,17 @@ public class MiniSSL extends RubyObject {
|
|
155
159
|
sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
|
156
160
|
engine = sslCtx.createSSLEngine();
|
157
161
|
|
158
|
-
String[] protocols
|
162
|
+
String[] protocols;
|
163
|
+
if(miniSSLContext.callMethod(threadContext, "no_tlsv1").isTrue()) {
|
164
|
+
protocols = new String[] { "TLSv1.1", "TLSv1.2" };
|
165
|
+
} else {
|
166
|
+
protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" };
|
167
|
+
}
|
168
|
+
|
169
|
+
if(miniSSLContext.callMethod(threadContext, "no_tlsv1_1").isTrue()) {
|
170
|
+
protocols = new String[] { "TLSv1.2" };
|
171
|
+
}
|
172
|
+
|
159
173
|
engine.setEnabledProtocols(protocols);
|
160
174
|
engine.setUseClientMode(false);
|
161
175
|
|
@@ -167,6 +181,12 @@ public class MiniSSL extends RubyObject {
|
|
167
181
|
engine.setNeedClientAuth(true);
|
168
182
|
}
|
169
183
|
|
184
|
+
IRubyObject sslCipherListObject = miniSSLContext.callMethod(threadContext, "ssl_cipher_list");
|
185
|
+
if (!sslCipherListObject.isNil()) {
|
186
|
+
String[] sslCipherList = sslCipherListObject.convertToString().asJavaString().split(",");
|
187
|
+
engine.setEnabledCipherSuites(sslCipherList);
|
188
|
+
}
|
189
|
+
|
170
190
|
SSLSession session = engine.getSession();
|
171
191
|
inboundNetData = new MiniSSLBuffer(session.getPacketBufferSize());
|
172
192
|
outboundAppData = new MiniSSLBuffer(session.getApplicationBufferSize());
|
@@ -333,7 +353,11 @@ public class MiniSSL extends RubyObject {
|
|
333
353
|
}
|
334
354
|
|
335
355
|
@JRubyMethod
|
336
|
-
public IRubyObject peercert() {
|
337
|
-
|
356
|
+
public IRubyObject peercert() throws CertificateEncodingException {
|
357
|
+
try {
|
358
|
+
return JavaEmbedUtils.javaToRuby(getRuntime(), engine.getSession().getPeerCertificates()[0].getEncoded());
|
359
|
+
} catch (SSLPeerUnverifiedException ex) {
|
360
|
+
return getRuntime().getNil();
|
361
|
+
}
|
338
362
|
}
|
339
363
|
}
|
@@ -1,6 +1,7 @@
|
|
1
1
|
/**
|
2
2
|
* Copyright (c) 2005 Zed A. Shaw
|
3
3
|
* You can redistribute it and/or modify it under the same terms as Ruby.
|
4
|
+
* License 3-clause BSD
|
4
5
|
*/
|
5
6
|
|
6
7
|
#define RSTRING_NOT_MODIFIED 1
|
@@ -9,6 +10,7 @@
|
|
9
10
|
#include "ext_help.h"
|
10
11
|
#include <assert.h>
|
11
12
|
#include <string.h>
|
13
|
+
#include <ctype.h>
|
12
14
|
#include "http11_parser.h"
|
13
15
|
|
14
16
|
#ifndef MANAGED_STRINGS
|
@@ -52,7 +54,7 @@ DEF_MAX_LENGTH(FIELD_NAME, 256);
|
|
52
54
|
DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
|
53
55
|
DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
|
54
56
|
DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
|
55
|
-
DEF_MAX_LENGTH(REQUEST_PATH,
|
57
|
+
DEF_MAX_LENGTH(REQUEST_PATH, 8196);
|
56
58
|
DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
|
57
59
|
DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
|
58
60
|
|
@@ -110,21 +112,6 @@ static struct common_field common_http_fields[] = {
|
|
110
112
|
# undef f
|
111
113
|
};
|
112
114
|
|
113
|
-
/*
|
114
|
-
* qsort(3) and bsearch(3) improve average performance slightly, but may
|
115
|
-
* not be worth it for lack of portability to certain platforms...
|
116
|
-
*/
|
117
|
-
#if defined(HAVE_QSORT_BSEARCH)
|
118
|
-
/* sort by length, then by name if there's a tie */
|
119
|
-
static int common_field_cmp(const void *a, const void *b)
|
120
|
-
{
|
121
|
-
struct common_field *cfa = (struct common_field *)a;
|
122
|
-
struct common_field *cfb = (struct common_field *)b;
|
123
|
-
signed long diff = cfa->len - cfb->len;
|
124
|
-
return diff ? diff : memcmp(cfa->name, cfb->name, cfa->len);
|
125
|
-
}
|
126
|
-
#endif /* HAVE_QSORT_BSEARCH */
|
127
|
-
|
128
115
|
static void init_common_fields(void)
|
129
116
|
{
|
130
117
|
unsigned i;
|
@@ -141,28 +128,10 @@ static void init_common_fields(void)
|
|
141
128
|
}
|
142
129
|
rb_global_variable(&cf->value);
|
143
130
|
}
|
144
|
-
|
145
|
-
#if defined(HAVE_QSORT_BSEARCH)
|
146
|
-
qsort(common_http_fields,
|
147
|
-
ARRAY_SIZE(common_http_fields),
|
148
|
-
sizeof(struct common_field),
|
149
|
-
common_field_cmp);
|
150
|
-
#endif /* HAVE_QSORT_BSEARCH */
|
151
131
|
}
|
152
132
|
|
153
133
|
static VALUE find_common_field_value(const char *field, size_t flen)
|
154
134
|
{
|
155
|
-
#if defined(HAVE_QSORT_BSEARCH)
|
156
|
-
struct common_field key;
|
157
|
-
struct common_field *found;
|
158
|
-
key.name = field;
|
159
|
-
key.len = (signed long)flen;
|
160
|
-
found = (struct common_field *)bsearch(&key, common_http_fields,
|
161
|
-
ARRAY_SIZE(common_http_fields),
|
162
|
-
sizeof(struct common_field),
|
163
|
-
common_field_cmp);
|
164
|
-
return found ? found->value : Qnil;
|
165
|
-
#else /* !HAVE_QSORT_BSEARCH */
|
166
135
|
unsigned i;
|
167
136
|
struct common_field *cf = common_http_fields;
|
168
137
|
for(i = 0; i < ARRAY_SIZE(common_http_fields); i++, cf++) {
|
@@ -170,7 +139,6 @@ static VALUE find_common_field_value(const char *field, size_t flen)
|
|
170
139
|
return cf->value;
|
171
140
|
}
|
172
141
|
return Qnil;
|
173
|
-
#endif /* !HAVE_QSORT_BSEARCH */
|
174
142
|
}
|
175
143
|
|
176
144
|
void http_field(puma_parser* hp, const char *field, size_t flen,
|
@@ -199,6 +167,8 @@ void http_field(puma_parser* hp, const char *field, size_t flen,
|
|
199
167
|
f = rb_str_new(hp->buf, new_size);
|
200
168
|
}
|
201
169
|
|
170
|
+
while (vlen > 0 && isspace(value[vlen - 1])) vlen--;
|
171
|
+
|
202
172
|
/* check for duplicate header */
|
203
173
|
v = rb_hash_aref(hp->request, f);
|
204
174
|
|
@@ -397,7 +367,7 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
|
|
397
367
|
VALIDATE_MAX_LENGTH(puma_parser_nread(http), HEADER);
|
398
368
|
|
399
369
|
if(puma_parser_has_error(http)) {
|
400
|
-
rb_raise(eHttpParserError, "%s", "Invalid HTTP format, parsing fails.");
|
370
|
+
rb_raise(eHttpParserError, "%s", "Invalid HTTP format, parsing fails. Are you trying to open an SSL connection to a non-SSL Puma?");
|
401
371
|
} else {
|
402
372
|
return INT2FIX(puma_parser_nread(http));
|
403
373
|
}
|
@@ -464,7 +434,6 @@ VALUE HttpParser_body(VALUE self) {
|
|
464
434
|
return http->body;
|
465
435
|
}
|
466
436
|
|
467
|
-
void Init_io_buffer(VALUE puma);
|
468
437
|
void Init_mini_ssl(VALUE mod);
|
469
438
|
|
470
439
|
void Init_puma_http11()
|
@@ -494,6 +463,5 @@ void Init_puma_http11()
|
|
494
463
|
rb_define_method(cHttpParser, "body", HttpParser_body, 0);
|
495
464
|
init_common_fields();
|
496
465
|
|
497
|
-
Init_io_buffer(mPuma);
|
498
466
|
Init_mini_ssl(mPuma);
|
499
467
|
}
|
data/lib/puma.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Standard libraries
|
2
4
|
require 'socket'
|
3
5
|
require 'tempfile'
|
4
|
-
require 'yaml'
|
5
6
|
require 'time'
|
6
7
|
require 'etc'
|
7
8
|
require 'uri'
|
@@ -9,7 +10,26 @@ require 'stringio'
|
|
9
10
|
|
10
11
|
require 'thread'
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
module Puma
|
14
|
+
autoload :Const, 'puma/const'
|
15
|
+
autoload :Server, 'puma/server'
|
16
|
+
autoload :Launcher, 'puma/launcher'
|
17
|
+
|
18
|
+
def self.stats_object=(val)
|
19
|
+
@get_stats = val
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.stats
|
23
|
+
@get_stats.stats.to_json
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.stats_hash
|
27
|
+
@get_stats.stats
|
28
|
+
end
|
29
|
+
|
30
|
+
# Thread name is new in Ruby 2.3
|
31
|
+
def self.set_thread_name(name)
|
32
|
+
return unless Thread.current.respond_to?(:name=)
|
33
|
+
Thread.current.name = "puma #{name}"
|
34
|
+
end
|
35
|
+
end
|
data/lib/puma/accept_nonblock.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'openssl'
|
2
4
|
|
3
5
|
module OpenSSL
|
@@ -13,7 +15,11 @@ module OpenSSL
|
|
13
15
|
ssl.accept if @start_immediately
|
14
16
|
ssl
|
15
17
|
rescue SSLError => ex
|
16
|
-
|
18
|
+
if ssl
|
19
|
+
ssl.close
|
20
|
+
else
|
21
|
+
sock.close
|
22
|
+
end
|
17
23
|
raise ex
|
18
24
|
end
|
19
25
|
end
|
data/lib/puma/app/status.rb
CHANGED
@@ -1,26 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
1
5
|
module Puma
|
2
6
|
module App
|
7
|
+
# Check out {#call}'s source code to see what actions this web application
|
8
|
+
# can respond to.
|
3
9
|
class Status
|
4
|
-
def initialize(cli)
|
5
|
-
@cli = cli
|
6
|
-
@auth_token = nil
|
7
|
-
end
|
8
10
|
OK_STATUS = '{ "status": "ok" }'.freeze
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
return true unless @auth_token
|
14
|
-
env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
|
15
|
-
end
|
16
|
-
|
17
|
-
def rack_response(status, body, content_type='application/json')
|
18
|
-
headers = {
|
19
|
-
'Content-Type' => content_type,
|
20
|
-
'Content-Length' => body.bytesize.to_s
|
21
|
-
}
|
22
|
-
|
23
|
-
[status, headers, [body]]
|
12
|
+
def initialize(cli, token = nil)
|
13
|
+
@cli = cli
|
14
|
+
@auth_token = token
|
24
15
|
end
|
25
16
|
|
26
17
|
def call(env)
|
@@ -31,36 +22,72 @@ module Puma
|
|
31
22
|
case env['PATH_INFO']
|
32
23
|
when /\/stop$/
|
33
24
|
@cli.stop
|
34
|
-
|
25
|
+
rack_response(200, OK_STATUS)
|
35
26
|
|
36
27
|
when /\/halt$/
|
37
28
|
@cli.halt
|
38
|
-
|
29
|
+
rack_response(200, OK_STATUS)
|
39
30
|
|
40
31
|
when /\/restart$/
|
41
32
|
@cli.restart
|
42
|
-
|
33
|
+
rack_response(200, OK_STATUS)
|
43
34
|
|
44
35
|
when /\/phased-restart$/
|
45
36
|
if !@cli.phased_restart
|
46
|
-
|
37
|
+
rack_response(404, '{ "error": "phased restart not available" }')
|
47
38
|
else
|
48
|
-
|
39
|
+
rack_response(200, OK_STATUS)
|
49
40
|
end
|
50
41
|
|
51
42
|
when /\/reload-worker-directory$/
|
52
43
|
if !@cli.send(:reload_worker_directory)
|
53
|
-
|
44
|
+
rack_response(404, '{ "error": "reload_worker_directory not available" }')
|
54
45
|
else
|
55
|
-
|
46
|
+
rack_response(200, OK_STATUS)
|
56
47
|
end
|
57
48
|
|
49
|
+
when /\/gc$/
|
50
|
+
GC.start
|
51
|
+
rack_response(200, OK_STATUS)
|
52
|
+
|
53
|
+
when /\/gc-stats$/
|
54
|
+
rack_response(200, GC.stat.to_json)
|
55
|
+
|
58
56
|
when /\/stats$/
|
59
|
-
|
57
|
+
rack_response(200, @cli.stats.to_json)
|
58
|
+
|
59
|
+
when /\/thread-backtraces$/
|
60
|
+
backtraces = []
|
61
|
+
@cli.thread_status do |name, backtrace|
|
62
|
+
backtraces << { name: name, backtrace: backtrace }
|
63
|
+
end
|
64
|
+
|
65
|
+
rack_response(200, backtraces.to_json)
|
66
|
+
|
67
|
+
when /\/refork$/
|
68
|
+
Process.kill "SIGURG", $$
|
69
|
+
rack_response(200, OK_STATUS)
|
70
|
+
|
60
71
|
else
|
61
72
|
rack_response 404, "Unsupported action", 'text/plain'
|
62
73
|
end
|
63
74
|
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def authenticate(env)
|
79
|
+
return true unless @auth_token
|
80
|
+
env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
|
81
|
+
end
|
82
|
+
|
83
|
+
def rack_response(status, body, content_type='application/json')
|
84
|
+
headers = {
|
85
|
+
'Content-Type' => content_type,
|
86
|
+
'Content-Length' => body.bytesize.to_s
|
87
|
+
}
|
88
|
+
|
89
|
+
[status, headers, [body]]
|
90
|
+
end
|
64
91
|
end
|
65
92
|
end
|
66
93
|
end
|
data/lib/puma/binder.rb
CHANGED
@@ -1,16 +1,23 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'uri'
|
4
|
+
require 'socket'
|
5
|
+
|
6
|
+
require 'puma/const'
|
7
|
+
require 'puma/util'
|
8
|
+
require 'puma/minissl/context_builder'
|
3
9
|
|
4
10
|
module Puma
|
5
11
|
class Binder
|
6
12
|
include Puma::Const
|
7
13
|
|
8
|
-
RACK_VERSION = [1,
|
14
|
+
RACK_VERSION = [1,6].freeze
|
9
15
|
|
10
16
|
def initialize(events)
|
11
17
|
@events = events
|
12
18
|
@listeners = []
|
13
19
|
@inherited_fds = {}
|
20
|
+
@activated_sockets = {}
|
14
21
|
@unix_paths = []
|
15
22
|
|
16
23
|
@proto_env = {
|
@@ -22,13 +29,13 @@ module Puma
|
|
22
29
|
"SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
|
23
30
|
|
24
31
|
# I'd like to set a default CONTENT_TYPE here but some things
|
25
|
-
# depend on their not being a default set and
|
32
|
+
# depend on their not being a default set and inferring
|
26
33
|
# it from the content. And so if i set it here, it won't
|
27
34
|
# infer properly.
|
28
35
|
|
29
36
|
"QUERY_STRING".freeze => "",
|
30
37
|
SERVER_PROTOCOL => HTTP_11,
|
31
|
-
SERVER_SOFTWARE =>
|
38
|
+
SERVER_SOFTWARE => PUMA_SERVER_STRING,
|
32
39
|
GATEWAY_INTERFACE => CGI_VER
|
33
40
|
}
|
34
41
|
|
@@ -36,7 +43,8 @@ module Puma
|
|
36
43
|
@ios = []
|
37
44
|
end
|
38
45
|
|
39
|
-
attr_reader :listeners, :
|
46
|
+
attr_reader :ios, :listeners, :unix_paths, :proto_env, :envs, :activated_sockets, :inherited_fds
|
47
|
+
attr_writer :ios, :listeners
|
40
48
|
|
41
49
|
def env(sock)
|
42
50
|
@envs.fetch(sock, @proto_env)
|
@@ -44,73 +52,85 @@ module Puma
|
|
44
52
|
|
45
53
|
def close
|
46
54
|
@ios.each { |i| i.close }
|
47
|
-
@unix_paths.each { |i| File.unlink i }
|
48
55
|
end
|
49
56
|
|
50
|
-
def
|
51
|
-
|
57
|
+
def connected_ports
|
58
|
+
ios.map { |io| io.addr[1] }.uniq
|
59
|
+
end
|
52
60
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
if k =~ /LISTEN_FDS/ && ENV['LISTEN_PID'].to_i == $$
|
60
|
-
v.to_i.times do |num|
|
61
|
-
fd = num + 3
|
62
|
-
sock = TCPServer.for_fd(fd)
|
63
|
-
begin
|
64
|
-
url = "unix://" + Socket.unpack_sockaddr_un(sock.getsockname)
|
65
|
-
rescue ArgumentError
|
66
|
-
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
|
67
|
-
if addr =~ /\:/
|
68
|
-
addr = "[#{addr}]"
|
69
|
-
end
|
70
|
-
url = "tcp://#{addr}:#{port}"
|
71
|
-
end
|
72
|
-
@inherited_fds[url] = sock
|
73
|
-
end
|
74
|
-
ENV.delete k
|
75
|
-
ENV.delete 'LISTEN_PID'
|
76
|
-
end
|
77
|
-
end
|
61
|
+
def create_inherited_fds(env_hash)
|
62
|
+
env_hash.select {|k,v| k =~ /PUMA_INHERIT_\d+/}.each do |_k, v|
|
63
|
+
fd, url = v.split(":", 2)
|
64
|
+
@inherited_fds[url] = fd.to_i
|
65
|
+
end.keys # pass keys back for removal
|
66
|
+
end
|
78
67
|
|
79
|
-
|
80
|
-
|
68
|
+
# systemd socket activation.
|
69
|
+
# LISTEN_FDS = number of listening sockets. e.g. 2 means accept on 2 sockets w/descriptors 3 and 4.
|
70
|
+
# LISTEN_PID = PID of the service process, aka us
|
71
|
+
# see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
|
72
|
+
def create_activated_fds(env_hash)
|
73
|
+
return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
|
74
|
+
env_hash['LISTEN_FDS'].to_i.times do |index|
|
75
|
+
sock = TCPServer.for_fd(socket_activation_fd(index))
|
76
|
+
key = begin # Try to parse as a path
|
77
|
+
[:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
|
78
|
+
rescue ArgumentError # Try to parse as a port/ip
|
79
|
+
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
|
80
|
+
addr = "[#{addr}]" if addr =~ /\:/
|
81
|
+
[:tcp, addr, port]
|
82
|
+
end
|
83
|
+
@activated_sockets[key] = sock
|
84
|
+
@events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
|
81
85
|
end
|
86
|
+
["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
|
82
87
|
end
|
83
88
|
|
84
|
-
def parse(binds, logger)
|
89
|
+
def parse(binds, logger, log_msg = 'Listening')
|
85
90
|
binds.each do |str|
|
86
91
|
uri = URI.parse str
|
87
92
|
case uri.scheme
|
88
93
|
when "tcp"
|
89
94
|
if fd = @inherited_fds.delete(str)
|
90
|
-
logger.log "* Inherited #{str}"
|
91
95
|
io = inherit_tcp_listener uri.host, uri.port, fd
|
96
|
+
logger.log "* Inherited #{str}"
|
97
|
+
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
98
|
+
io = inherit_tcp_listener uri.host, uri.port, sock
|
99
|
+
logger.log "* Activated #{str}"
|
92
100
|
else
|
93
101
|
params = Util.parse_query uri.query
|
94
102
|
|
95
103
|
opt = params.key?('low_latency')
|
96
104
|
bak = params.fetch('backlog', 1024).to_i
|
97
105
|
|
98
|
-
logger.log "* Listening on #{str}"
|
99
106
|
io = add_tcp_listener uri.host, uri.port, opt, bak
|
107
|
+
|
108
|
+
@ios.each do |i|
|
109
|
+
next unless TCPServer === i
|
110
|
+
addr = if i.local_address.ipv6?
|
111
|
+
"[#{i.local_address.ip_unpack[0]}]:#{i.local_address.ip_unpack[1]}"
|
112
|
+
else
|
113
|
+
i.local_address.ip_unpack.join(':')
|
114
|
+
end
|
115
|
+
|
116
|
+
logger.log "* #{log_msg} on tcp://#{addr}"
|
117
|
+
end
|
100
118
|
end
|
101
119
|
|
102
|
-
@listeners << [str, io]
|
120
|
+
@listeners << [str, io] if io
|
103
121
|
when "unix"
|
104
122
|
path = "#{uri.host}#{uri.path}".gsub("%20", " ")
|
105
123
|
|
106
124
|
if fd = @inherited_fds.delete(str)
|
107
|
-
logger.log "* Inherited #{str}"
|
108
125
|
io = inherit_unix_listener path, fd
|
126
|
+
logger.log "* Inherited #{str}"
|
127
|
+
elsif sock = @activated_sockets.delete([ :unix, path ])
|
128
|
+
io = inherit_unix_listener path, sock
|
129
|
+
logger.log "* Activated #{str}"
|
109
130
|
else
|
110
|
-
logger.log "* Listening on #{str}"
|
111
|
-
|
112
131
|
umask = nil
|
113
132
|
mode = nil
|
133
|
+
backlog = 1024
|
114
134
|
|
115
135
|
if uri.query
|
116
136
|
params = Util.parse_query uri.query
|
@@ -122,77 +142,33 @@ module Puma
|
|
122
142
|
if u = params['mode']
|
123
143
|
mode = Integer('0'+u)
|
124
144
|
end
|
145
|
+
|
146
|
+
if u = params['backlog']
|
147
|
+
backlog = Integer(u)
|
148
|
+
end
|
125
149
|
end
|
126
150
|
|
127
|
-
io = add_unix_listener path, umask, mode
|
151
|
+
io = add_unix_listener path, umask, mode, backlog
|
152
|
+
logger.log "* #{log_msg} on #{str}"
|
128
153
|
end
|
129
154
|
|
130
155
|
@listeners << [str, io]
|
131
156
|
when "ssl"
|
132
|
-
MiniSSL.check
|
133
|
-
|
134
157
|
params = Util.parse_query uri.query
|
135
|
-
|
136
|
-
|
137
|
-
ctx = MiniSSL::Context.new
|
138
|
-
|
139
|
-
if defined?(JRUBY_VERSION)
|
140
|
-
unless params['keystore']
|
141
|
-
@events.error "Please specify the Java keystore via 'keystore='"
|
142
|
-
end
|
143
|
-
|
144
|
-
ctx.keystore = params['keystore']
|
145
|
-
|
146
|
-
unless params['keystore-pass']
|
147
|
-
@events.error "Please specify the Java keystore password via 'keystore-pass='"
|
148
|
-
end
|
149
|
-
|
150
|
-
ctx.keystore_pass = params['keystore-pass']
|
151
|
-
else
|
152
|
-
unless params['key']
|
153
|
-
@events.error "Please specify the SSL key via 'key='"
|
154
|
-
end
|
155
|
-
|
156
|
-
ctx.key = params['key']
|
157
|
-
|
158
|
-
unless params['cert']
|
159
|
-
@events.error "Please specify the SSL cert via 'cert='"
|
160
|
-
end
|
161
|
-
|
162
|
-
ctx.cert = params['cert']
|
163
|
-
|
164
|
-
if ['peer', 'force_peer'].include?(params['verify_mode'])
|
165
|
-
unless params['ca']
|
166
|
-
@events.error "Please specify the SSL ca via 'ca='"
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
ctx.ca = params['ca'] if params['ca']
|
171
|
-
|
172
|
-
if params['verify_mode']
|
173
|
-
ctx.verify_mode = case params['verify_mode']
|
174
|
-
when "peer"
|
175
|
-
MiniSSL::VERIFY_PEER
|
176
|
-
when "force_peer"
|
177
|
-
MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
178
|
-
when "none"
|
179
|
-
MiniSSL::VERIFY_NONE
|
180
|
-
else
|
181
|
-
@events.error "Please specify a valid verify_mode="
|
182
|
-
MiniSSL::VERIFY_NONE
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
158
|
+
ctx = MiniSSL::ContextBuilder.new(params, @events).context
|
186
159
|
|
187
160
|
if fd = @inherited_fds.delete(str)
|
188
161
|
logger.log "* Inherited #{str}"
|
189
|
-
io =
|
162
|
+
io = inherit_ssl_listener fd, ctx
|
163
|
+
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
164
|
+
io = inherit_ssl_listener sock, ctx
|
165
|
+
logger.log "* Activated #{str}"
|
190
166
|
else
|
191
|
-
logger.log "* Listening on #{str}"
|
192
167
|
io = add_ssl_listener uri.host, uri.port, ctx
|
168
|
+
logger.log "* Listening on #{str}"
|
193
169
|
end
|
194
170
|
|
195
|
-
@listeners << [str, io]
|
171
|
+
@listeners << [str, io] if io
|
196
172
|
else
|
197
173
|
logger.error "Invalid URI: #{str}"
|
198
174
|
end
|
@@ -204,12 +180,7 @@ module Puma
|
|
204
180
|
logger.log "* Closing unused inherited connection: #{str}"
|
205
181
|
|
206
182
|
begin
|
207
|
-
|
208
|
-
fd.close
|
209
|
-
else
|
210
|
-
IO.for_fd(fd).close
|
211
|
-
end
|
212
|
-
|
183
|
+
IO.for_fd(fd).close
|
213
184
|
rescue SystemCallError
|
214
185
|
end
|
215
186
|
|
@@ -221,6 +192,16 @@ module Puma
|
|
221
192
|
end
|
222
193
|
end
|
223
194
|
|
195
|
+
# Also close any unused activated sockets
|
196
|
+
@activated_sockets.each do |key, sock|
|
197
|
+
logger.log "* Closing unused activated socket: #{key.join ':'}"
|
198
|
+
begin
|
199
|
+
sock.close
|
200
|
+
rescue SystemCallError
|
201
|
+
end
|
202
|
+
# We have to unlink a unix socket path that's not being used
|
203
|
+
File.unlink key[1] if key[0] == :unix
|
204
|
+
end
|
224
205
|
end
|
225
206
|
|
226
207
|
# Tell the server to listen on host +host+, port +port+.
|
@@ -231,21 +212,25 @@ module Puma
|
|
231
212
|
# allow to accumulate before returning connection refused.
|
232
213
|
#
|
233
214
|
def add_tcp_listener(host, port, optimize_for_latency=true, backlog=1024)
|
215
|
+
if host == "localhost"
|
216
|
+
loopback_addresses.each do |addr|
|
217
|
+
add_tcp_listener addr, port, optimize_for_latency, backlog
|
218
|
+
end
|
219
|
+
return
|
220
|
+
end
|
221
|
+
|
234
222
|
host = host[1..-2] if host and host[0..0] == '['
|
235
|
-
|
223
|
+
tcp_server = TCPServer.new(host, port)
|
236
224
|
if optimize_for_latency
|
237
|
-
|
225
|
+
tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
238
226
|
end
|
239
|
-
|
240
|
-
|
241
|
-
@connected_port = s.addr[1]
|
227
|
+
tcp_server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
|
228
|
+
tcp_server.listen backlog
|
242
229
|
|
243
|
-
@ios <<
|
244
|
-
|
230
|
+
@ios << tcp_server
|
231
|
+
tcp_server
|
245
232
|
end
|
246
233
|
|
247
|
-
attr_reader :connected_port
|
248
|
-
|
249
234
|
def inherit_tcp_listener(host, port, fd)
|
250
235
|
if fd.kind_of? TCPServer
|
251
236
|
s = fd
|
@@ -263,6 +248,13 @@ module Puma
|
|
263
248
|
|
264
249
|
MiniSSL.check
|
265
250
|
|
251
|
+
if host == "localhost"
|
252
|
+
loopback_addresses.each do |addr|
|
253
|
+
add_ssl_listener addr, port, ctx, optimize_for_latency, backlog
|
254
|
+
end
|
255
|
+
return
|
256
|
+
end
|
257
|
+
|
266
258
|
host = host[1..-2] if host[0..0] == '['
|
267
259
|
s = TCPServer.new(host, port)
|
268
260
|
if optimize_for_latency
|
@@ -271,6 +263,7 @@ module Puma
|
|
271
263
|
s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
|
272
264
|
s.listen backlog
|
273
265
|
|
266
|
+
|
274
267
|
ssl = MiniSSL::Server.new s, ctx
|
275
268
|
env = @proto_env.dup
|
276
269
|
env[HTTPS_KEY] = HTTPS
|
@@ -280,11 +273,15 @@ module Puma
|
|
280
273
|
s
|
281
274
|
end
|
282
275
|
|
283
|
-
def
|
276
|
+
def inherit_ssl_listener(fd, ctx)
|
284
277
|
require 'puma/minissl'
|
285
278
|
MiniSSL.check
|
286
279
|
|
287
|
-
|
280
|
+
if fd.kind_of? TCPServer
|
281
|
+
s = fd
|
282
|
+
else
|
283
|
+
s = TCPServer.for_fd(fd)
|
284
|
+
end
|
288
285
|
ssl = MiniSSL::Server.new(s, ctx)
|
289
286
|
|
290
287
|
env = @proto_env.dup
|
@@ -298,8 +295,8 @@ module Puma
|
|
298
295
|
|
299
296
|
# Tell the server to listen on +path+ as a UNIX domain socket.
|
300
297
|
#
|
301
|
-
def add_unix_listener(path, umask=nil, mode=nil)
|
302
|
-
@unix_paths << path
|
298
|
+
def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
|
299
|
+
@unix_paths << path unless File.exist? path
|
303
300
|
|
304
301
|
# Let anyone connect by default
|
305
302
|
umask ||= 0
|
@@ -319,6 +316,7 @@ module Puma
|
|
319
316
|
end
|
320
317
|
|
321
318
|
s = UNIXServer.new(path)
|
319
|
+
s.listen backlog
|
322
320
|
@ios << s
|
323
321
|
ensure
|
324
322
|
File.umask old_mask
|
@@ -336,7 +334,7 @@ module Puma
|
|
336
334
|
end
|
337
335
|
|
338
336
|
def inherit_unix_listener(path, fd)
|
339
|
-
@unix_paths << path
|
337
|
+
@unix_paths << path unless File.exist? path
|
340
338
|
|
341
339
|
if fd.kind_of? TCPServer
|
342
340
|
s = fd
|
@@ -352,5 +350,38 @@ module Puma
|
|
352
350
|
s
|
353
351
|
end
|
354
352
|
|
353
|
+
def close_listeners
|
354
|
+
listeners.each do |l, io|
|
355
|
+
io.close unless io.closed? # Ruby 2.2 issue
|
356
|
+
uri = URI.parse(l)
|
357
|
+
next unless uri.scheme == 'unix'
|
358
|
+
unix_path = "#{uri.host}#{uri.path}"
|
359
|
+
File.unlink unix_path if unix_paths.include? unix_path
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
def redirects_for_restart
|
364
|
+
redirects = listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
|
365
|
+
redirects[:close_others] = true
|
366
|
+
redirects
|
367
|
+
end
|
368
|
+
|
369
|
+
def redirects_for_restart_env
|
370
|
+
listeners.each_with_object({}).with_index do |(listen, memo), i|
|
371
|
+
memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
private
|
376
|
+
|
377
|
+
def loopback_addresses
|
378
|
+
Socket.ip_address_list.select do |addrinfo|
|
379
|
+
addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
|
380
|
+
end.map { |addrinfo| addrinfo.ip_address }.uniq
|
381
|
+
end
|
382
|
+
|
383
|
+
def socket_activation_fd(int)
|
384
|
+
int + 3 # 3 is the magic number you add to follow the SA protocol
|
385
|
+
end
|
355
386
|
end
|
356
387
|
end
|