puma 4.3.6-java → 5.0.2-java
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 +1153 -518
- data/LICENSE +23 -20
- data/README.md +26 -13
- 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/signals.md +7 -6
- data/docs/systemd.md +1 -63
- data/ext/puma_http11/PumaHttp11Service.java +2 -4
- data/ext/puma_http11/extconf.rb +4 -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 +3 -3
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
- data/ext/puma_http11/puma_http11.c +6 -38
- data/lib/puma.rb +20 -0
- data/lib/puma/app/status.rb +14 -1
- data/lib/puma/binder.rb +90 -68
- data/lib/puma/cli.rb +7 -15
- data/lib/puma/client.rb +62 -13
- data/lib/puma/cluster.rb +193 -74
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +31 -42
- data/lib/puma/const.rb +3 -3
- data/lib/puma/control_cli.rb +29 -17
- data/lib/puma/detect.rb +17 -0
- data/lib/puma/dsl.rb +144 -70
- data/lib/puma/error_logger.rb +97 -0
- data/lib/puma/events.rb +37 -31
- data/lib/puma/io_buffer.rb +9 -2
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/launcher.rb +57 -31
- data/lib/puma/minissl.rb +68 -18
- data/lib/puma/minissl/context_builder.rb +0 -3
- data/lib/puma/null_io.rb +1 -1
- data/lib/puma/plugin.rb +1 -10
- data/lib/puma/puma_http11.jar +0 -0
- data/lib/puma/rack/builder.rb +0 -4
- data/lib/puma/reactor.rb +10 -16
- data/lib/puma/runner.rb +8 -36
- data/lib/puma/server.rb +161 -218
- data/lib/puma/single.rb +8 -64
- data/lib/puma/state_file.rb +6 -3
- data/lib/puma/thread_pool.rb +116 -51
- data/lib/puma/util.rb +1 -0
- data/lib/rack/handler/puma.rb +1 -3
- data/tools/{docker/Dockerfile → Dockerfile} +0 -0
- metadata +17 -19
- 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/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
@@ -0,0 +1,15 @@
|
|
1
|
+
package puma;
|
2
|
+
|
3
|
+
import java.io.IOException;
|
4
|
+
|
5
|
+
import org.jruby.Ruby;
|
6
|
+
import org.jruby.runtime.load.BasicLibraryService;
|
7
|
+
|
8
|
+
import org.jruby.puma.Http11;
|
9
|
+
|
10
|
+
public class PumaHttp11Service implements BasicLibraryService {
|
11
|
+
public boolean basicLoad(final Ruby runtime) throws IOException {
|
12
|
+
Http11.createHttp11(runtime);
|
13
|
+
return true;
|
14
|
+
}
|
15
|
+
}
|
@@ -30,8 +30,8 @@ public class Http11 extends RubyObject {
|
|
30
30
|
public final static String MAX_REQUEST_URI_LENGTH_ERR = "HTTP element REQUEST_URI is longer than the 12288 allowed length.";
|
31
31
|
public final static int MAX_FRAGMENT_LENGTH = 1024;
|
32
32
|
public final static String MAX_FRAGMENT_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the 1024 allowed length.";
|
33
|
-
public final static int MAX_REQUEST_PATH_LENGTH =
|
34
|
-
public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the
|
33
|
+
public final static int MAX_REQUEST_PATH_LENGTH = 8192;
|
34
|
+
public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the 8192 allowed length.";
|
35
35
|
public final static int MAX_QUERY_STRING_LENGTH = 1024 * 10;
|
36
36
|
public final static String MAX_QUERY_STRING_LENGTH_ERR = "HTTP element QUERY_STRING is longer than the 10240 allowed length.";
|
37
37
|
public final static int MAX_HEADER_LENGTH = 1024 * (80 + 32);
|
@@ -197,7 +197,7 @@ public class Http11 extends RubyObject {
|
|
197
197
|
validateMaxLength(runtime, parser.nread,MAX_HEADER_LENGTH, MAX_HEADER_LENGTH_ERR);
|
198
198
|
|
199
199
|
if(hp.has_error()) {
|
200
|
-
throw newHTTPParserError(runtime, "Invalid HTTP format, parsing fails.");
|
200
|
+
throw newHTTPParserError(runtime, "Invalid HTTP format, parsing fails. Are you trying to open an SSL connection to a non-SSL Puma?");
|
201
201
|
} else {
|
202
202
|
return runtime.newFixnum(parser.nread);
|
203
203
|
}
|
@@ -22,6 +22,7 @@ import javax.net.ssl.SSLException;
|
|
22
22
|
import javax.net.ssl.SSLPeerUnverifiedException;
|
23
23
|
import javax.net.ssl.SSLSession;
|
24
24
|
import java.io.FileInputStream;
|
25
|
+
import java.io.InputStream;
|
25
26
|
import java.io.IOException;
|
26
27
|
import java.nio.Buffer;
|
27
28
|
import java.nio.ByteBuffer;
|
@@ -32,6 +33,8 @@ import java.security.NoSuchAlgorithmException;
|
|
32
33
|
import java.security.UnrecoverableKeyException;
|
33
34
|
import java.security.cert.CertificateEncodingException;
|
34
35
|
import java.security.cert.CertificateException;
|
36
|
+
import java.util.concurrent.ConcurrentHashMap;
|
37
|
+
import java.util.Map;
|
35
38
|
|
36
39
|
import static javax.net.ssl.SSLEngineResult.Status;
|
37
40
|
import static javax.net.ssl.SSLEngineResult.HandshakeStatus;
|
@@ -120,6 +123,8 @@ public class MiniSSL extends RubyObject {
|
|
120
123
|
}
|
121
124
|
|
122
125
|
private SSLEngine engine;
|
126
|
+
private boolean closed;
|
127
|
+
private boolean handshake;
|
123
128
|
private MiniSSLBuffer inboundNetData;
|
124
129
|
private MiniSSLBuffer outboundAppData;
|
125
130
|
private MiniSSLBuffer outboundNetData;
|
@@ -128,10 +133,39 @@ public class MiniSSL extends RubyObject {
|
|
128
133
|
super(runtime, klass);
|
129
134
|
}
|
130
135
|
|
136
|
+
private static Map<String, KeyManagerFactory> keyManagerFactoryMap = new ConcurrentHashMap<String, KeyManagerFactory>();
|
137
|
+
private static Map<String, TrustManagerFactory> trustManagerFactoryMap = new ConcurrentHashMap<String, TrustManagerFactory>();
|
138
|
+
|
131
139
|
@JRubyMethod(meta = true)
|
132
|
-
public static IRubyObject server(ThreadContext context, IRubyObject recv, IRubyObject miniSSLContext)
|
133
|
-
|
140
|
+
public static synchronized IRubyObject server(ThreadContext context, IRubyObject recv, IRubyObject miniSSLContext)
|
141
|
+
throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
|
142
|
+
// Create the KeyManagerFactory and TrustManagerFactory for this server
|
143
|
+
String keystoreFile = miniSSLContext.callMethod(context, "keystore").convertToString().asJavaString();
|
144
|
+
char[] password = miniSSLContext.callMethod(context, "keystore_pass").convertToString().asJavaString().toCharArray();
|
134
145
|
|
146
|
+
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
|
147
|
+
InputStream is = new FileInputStream(keystoreFile);
|
148
|
+
try {
|
149
|
+
ks.load(is, password);
|
150
|
+
} finally {
|
151
|
+
is.close();
|
152
|
+
}
|
153
|
+
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
|
154
|
+
kmf.init(ks, password);
|
155
|
+
keyManagerFactoryMap.put(keystoreFile, kmf);
|
156
|
+
|
157
|
+
KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
|
158
|
+
is = new FileInputStream(keystoreFile);
|
159
|
+
try {
|
160
|
+
ts.load(is, password);
|
161
|
+
} finally {
|
162
|
+
is.close();
|
163
|
+
}
|
164
|
+
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
|
165
|
+
tmf.init(ts);
|
166
|
+
trustManagerFactoryMap.put(keystoreFile, tmf);
|
167
|
+
|
168
|
+
RubyClass klass = (RubyClass) recv;
|
135
169
|
return klass.newInstance(context,
|
136
170
|
new IRubyObject[] { miniSSLContext },
|
137
171
|
Block.NULL_BLOCK);
|
@@ -139,24 +173,22 @@ public class MiniSSL extends RubyObject {
|
|
139
173
|
|
140
174
|
@JRubyMethod
|
141
175
|
public IRubyObject initialize(ThreadContext threadContext, IRubyObject miniSSLContext)
|
142
|
-
throws KeyStoreException,
|
176
|
+
throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
|
143
177
|
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
|
144
178
|
KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
|
145
179
|
|
146
|
-
char[] password = miniSSLContext.callMethod(threadContext, "keystore_pass").convertToString().asJavaString().toCharArray();
|
147
180
|
String keystoreFile = miniSSLContext.callMethod(threadContext, "keystore").convertToString().asJavaString();
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
|
155
|
-
tmf.init(ts);
|
181
|
+
KeyManagerFactory kmf = keyManagerFactoryMap.get(keystoreFile);
|
182
|
+
TrustManagerFactory tmf = trustManagerFactoryMap.get(keystoreFile);
|
183
|
+
if(kmf == null || tmf == null) {
|
184
|
+
throw new KeyStoreException("Could not find KeyManagerFactory/TrustManagerFactory for keystore: " + keystoreFile);
|
185
|
+
}
|
156
186
|
|
157
187
|
SSLContext sslCtx = SSLContext.getInstance("TLS");
|
158
188
|
|
159
189
|
sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
|
190
|
+
closed = false;
|
191
|
+
handshake = false;
|
160
192
|
engine = sslCtx.createSSLEngine();
|
161
193
|
|
162
194
|
String[] protocols;
|
@@ -173,7 +205,7 @@ public class MiniSSL extends RubyObject {
|
|
173
205
|
engine.setEnabledProtocols(protocols);
|
174
206
|
engine.setUseClientMode(false);
|
175
207
|
|
176
|
-
long verify_mode = miniSSLContext.callMethod(threadContext, "verify_mode").convertToInteger().getLongValue();
|
208
|
+
long verify_mode = miniSSLContext.callMethod(threadContext, "verify_mode").convertToInteger("to_i").getLongValue();
|
177
209
|
if ((verify_mode & 0x1) != 0) { // 'peer'
|
178
210
|
engine.setWantClientAuth(true);
|
179
211
|
}
|
@@ -240,14 +272,21 @@ public class MiniSSL extends RubyObject {
|
|
240
272
|
// need to wait for more data to come in before we retry
|
241
273
|
retryOp = false;
|
242
274
|
break;
|
275
|
+
case CLOSED:
|
276
|
+
closed = true;
|
277
|
+
retryOp = false;
|
278
|
+
break;
|
243
279
|
default:
|
244
|
-
// other
|
280
|
+
// other case is OK. We're done here.
|
245
281
|
retryOp = false;
|
246
282
|
}
|
283
|
+
if (res.getHandshakeStatus() == HandshakeStatus.FINISHED) {
|
284
|
+
handshake = true;
|
285
|
+
}
|
247
286
|
}
|
248
287
|
|
249
288
|
// after each op, run any delegated tasks if needed
|
250
|
-
if(
|
289
|
+
if(res.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
|
251
290
|
Runnable runnable;
|
252
291
|
while ((runnable = engine.getDelegatedTask()) != null) {
|
253
292
|
runnable.run();
|
@@ -271,13 +310,14 @@ public class MiniSSL extends RubyObject {
|
|
271
310
|
|
272
311
|
HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
|
273
312
|
boolean done = false;
|
313
|
+
SSLEngineResult res = null;
|
274
314
|
while (!done) {
|
275
315
|
switch (handshakeStatus) {
|
276
316
|
case NEED_WRAP:
|
277
|
-
doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
|
317
|
+
res = doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
|
278
318
|
break;
|
279
319
|
case NEED_UNWRAP:
|
280
|
-
|
320
|
+
res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
|
281
321
|
if (res.getStatus() == Status.BUFFER_UNDERFLOW) {
|
282
322
|
// need more data before we can shake more hands
|
283
323
|
done = true;
|
@@ -286,7 +326,9 @@ public class MiniSSL extends RubyObject {
|
|
286
326
|
default:
|
287
327
|
done = true;
|
288
328
|
}
|
289
|
-
|
329
|
+
if (!done) {
|
330
|
+
handshakeStatus = res.getHandshakeStatus();
|
331
|
+
}
|
290
332
|
}
|
291
333
|
|
292
334
|
if (inboundNetData.hasRemaining()) {
|
@@ -360,4 +402,21 @@ public class MiniSSL extends RubyObject {
|
|
360
402
|
return getRuntime().getNil();
|
361
403
|
}
|
362
404
|
}
|
405
|
+
|
406
|
+
@JRubyMethod(name = "init?")
|
407
|
+
public IRubyObject isInit(ThreadContext context) {
|
408
|
+
return handshake ? getRuntime().getFalse() : getRuntime().getTrue();
|
409
|
+
}
|
410
|
+
|
411
|
+
@JRubyMethod
|
412
|
+
public IRubyObject shutdown() {
|
413
|
+
if (closed || engine.isInboundDone() && engine.isOutboundDone()) {
|
414
|
+
if (engine.isOutboundDone()) {
|
415
|
+
engine.closeOutbound();
|
416
|
+
}
|
417
|
+
return getRuntime().getTrue();
|
418
|
+
} else {
|
419
|
+
return getRuntime().getFalse();
|
420
|
+
}
|
421
|
+
}
|
363
422
|
}
|
@@ -54,7 +54,7 @@ DEF_MAX_LENGTH(FIELD_NAME, 256);
|
|
54
54
|
DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
|
55
55
|
DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
|
56
56
|
DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
|
57
|
-
DEF_MAX_LENGTH(REQUEST_PATH,
|
57
|
+
DEF_MAX_LENGTH(REQUEST_PATH, 8192);
|
58
58
|
DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
|
59
59
|
DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
|
60
60
|
|
@@ -112,21 +112,6 @@ static struct common_field common_http_fields[] = {
|
|
112
112
|
# undef f
|
113
113
|
};
|
114
114
|
|
115
|
-
/*
|
116
|
-
* qsort(3) and bsearch(3) improve average performance slightly, but may
|
117
|
-
* not be worth it for lack of portability to certain platforms...
|
118
|
-
*/
|
119
|
-
#if defined(HAVE_QSORT_BSEARCH)
|
120
|
-
/* sort by length, then by name if there's a tie */
|
121
|
-
static int common_field_cmp(const void *a, const void *b)
|
122
|
-
{
|
123
|
-
struct common_field *cfa = (struct common_field *)a;
|
124
|
-
struct common_field *cfb = (struct common_field *)b;
|
125
|
-
signed long diff = cfa->len - cfb->len;
|
126
|
-
return diff ? diff : memcmp(cfa->name, cfb->name, cfa->len);
|
127
|
-
}
|
128
|
-
#endif /* HAVE_QSORT_BSEARCH */
|
129
|
-
|
130
115
|
static void init_common_fields(void)
|
131
116
|
{
|
132
117
|
unsigned i;
|
@@ -143,28 +128,10 @@ static void init_common_fields(void)
|
|
143
128
|
}
|
144
129
|
rb_global_variable(&cf->value);
|
145
130
|
}
|
146
|
-
|
147
|
-
#if defined(HAVE_QSORT_BSEARCH)
|
148
|
-
qsort(common_http_fields,
|
149
|
-
ARRAY_SIZE(common_http_fields),
|
150
|
-
sizeof(struct common_field),
|
151
|
-
common_field_cmp);
|
152
|
-
#endif /* HAVE_QSORT_BSEARCH */
|
153
131
|
}
|
154
132
|
|
155
133
|
static VALUE find_common_field_value(const char *field, size_t flen)
|
156
134
|
{
|
157
|
-
#if defined(HAVE_QSORT_BSEARCH)
|
158
|
-
struct common_field key;
|
159
|
-
struct common_field *found;
|
160
|
-
key.name = field;
|
161
|
-
key.len = (signed long)flen;
|
162
|
-
found = (struct common_field *)bsearch(&key, common_http_fields,
|
163
|
-
ARRAY_SIZE(common_http_fields),
|
164
|
-
sizeof(struct common_field),
|
165
|
-
common_field_cmp);
|
166
|
-
return found ? found->value : Qnil;
|
167
|
-
#else /* !HAVE_QSORT_BSEARCH */
|
168
135
|
unsigned i;
|
169
136
|
struct common_field *cf = common_http_fields;
|
170
137
|
for(i = 0; i < ARRAY_SIZE(common_http_fields); i++, cf++) {
|
@@ -172,7 +139,6 @@ static VALUE find_common_field_value(const char *field, size_t flen)
|
|
172
139
|
return cf->value;
|
173
140
|
}
|
174
141
|
return Qnil;
|
175
|
-
#endif /* !HAVE_QSORT_BSEARCH */
|
176
142
|
}
|
177
143
|
|
178
144
|
void http_field(puma_parser* hp, const char *field, size_t flen,
|
@@ -401,7 +367,7 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
|
|
401
367
|
VALIDATE_MAX_LENGTH(puma_parser_nread(http), HEADER);
|
402
368
|
|
403
369
|
if(puma_parser_has_error(http)) {
|
404
|
-
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?");
|
405
371
|
} else {
|
406
372
|
return INT2FIX(puma_parser_nread(http));
|
407
373
|
}
|
@@ -468,8 +434,9 @@ VALUE HttpParser_body(VALUE self) {
|
|
468
434
|
return http->body;
|
469
435
|
}
|
470
436
|
|
471
|
-
|
437
|
+
#ifdef HAVE_OPENSSL_BIO_H
|
472
438
|
void Init_mini_ssl(VALUE mod);
|
439
|
+
#endif
|
473
440
|
|
474
441
|
void Init_puma_http11()
|
475
442
|
{
|
@@ -498,6 +465,7 @@ void Init_puma_http11()
|
|
498
465
|
rb_define_method(cHttpParser, "body", HttpParser_body, 0);
|
499
466
|
init_common_fields();
|
500
467
|
|
501
|
-
|
468
|
+
#ifdef HAVE_OPENSSL_BIO_H
|
502
469
|
Init_mini_ssl(mPuma);
|
470
|
+
#endif
|
503
471
|
}
|
data/lib/puma.rb
CHANGED
@@ -10,16 +10,28 @@ require 'stringio'
|
|
10
10
|
|
11
11
|
require 'thread'
|
12
12
|
|
13
|
+
require 'puma/puma_http11'
|
14
|
+
require 'puma/detect'
|
15
|
+
|
13
16
|
module Puma
|
14
17
|
autoload :Const, 'puma/const'
|
15
18
|
autoload :Server, 'puma/server'
|
16
19
|
autoload :Launcher, 'puma/launcher'
|
17
20
|
|
21
|
+
# @!attribute [rw] stats_object=
|
18
22
|
def self.stats_object=(val)
|
19
23
|
@get_stats = val
|
20
24
|
end
|
21
25
|
|
26
|
+
# @!attribute [rw] stats_object
|
22
27
|
def self.stats
|
28
|
+
require 'json'
|
29
|
+
@get_stats.stats.to_json
|
30
|
+
end
|
31
|
+
|
32
|
+
# @!attribute [r] stats_hash
|
33
|
+
# @version 5.0.0
|
34
|
+
def self.stats_hash
|
23
35
|
@get_stats.stats
|
24
36
|
end
|
25
37
|
|
@@ -28,4 +40,12 @@ module Puma
|
|
28
40
|
return unless Thread.current.respond_to?(:name=)
|
29
41
|
Thread.current.name = "puma #{name}"
|
30
42
|
end
|
43
|
+
|
44
|
+
unless HAS_SSL
|
45
|
+
module MiniSSL
|
46
|
+
# this class is defined so that it exists when Puma is compiled
|
47
|
+
# without ssl support, as Server and Reactor use it in rescue statements.
|
48
|
+
class SSLError < StandardError ; end
|
49
|
+
end
|
50
|
+
end
|
31
51
|
end
|
data/lib/puma/app/status.rb
CHANGED
@@ -56,7 +56,20 @@ module Puma
|
|
56
56
|
rack_response(200, GC.stat.to_json)
|
57
57
|
|
58
58
|
when /\/stats$/
|
59
|
-
rack_response(200, @cli.stats)
|
59
|
+
rack_response(200, @cli.stats.to_json)
|
60
|
+
|
61
|
+
when /\/thread-backtraces$/
|
62
|
+
backtraces = []
|
63
|
+
@cli.thread_status do |name, backtrace|
|
64
|
+
backtraces << { name: name, backtrace: backtrace }
|
65
|
+
end
|
66
|
+
|
67
|
+
rack_response(200, backtraces.to_json)
|
68
|
+
|
69
|
+
when /\/refork$/
|
70
|
+
Process.kill "SIGURG", $$
|
71
|
+
rack_response(200, OK_STATUS)
|
72
|
+
|
60
73
|
else
|
61
74
|
rack_response 404, "Unsupported action", 'text/plain'
|
62
75
|
end
|
data/lib/puma/binder.rb
CHANGED
@@ -5,15 +5,22 @@ require 'socket'
|
|
5
5
|
|
6
6
|
require 'puma/const'
|
7
7
|
require 'puma/util'
|
8
|
-
require 'puma/
|
8
|
+
require 'puma/configuration'
|
9
9
|
|
10
10
|
module Puma
|
11
|
+
|
12
|
+
if HAS_SSL
|
13
|
+
require 'puma/minissl'
|
14
|
+
require 'puma/minissl/context_builder'
|
15
|
+
require 'puma/accept_nonblock'
|
16
|
+
end
|
17
|
+
|
11
18
|
class Binder
|
12
19
|
include Puma::Const
|
13
20
|
|
14
|
-
RACK_VERSION = [1,
|
21
|
+
RACK_VERSION = [1,6].freeze
|
15
22
|
|
16
|
-
def initialize(events)
|
23
|
+
def initialize(events, conf = Configuration.new)
|
17
24
|
@events = events
|
18
25
|
@listeners = []
|
19
26
|
@inherited_fds = {}
|
@@ -23,8 +30,8 @@ module Puma
|
|
23
30
|
@proto_env = {
|
24
31
|
"rack.version".freeze => RACK_VERSION,
|
25
32
|
"rack.errors".freeze => events.stderr,
|
26
|
-
"rack.multithread".freeze =>
|
27
|
-
"rack.multiprocess".freeze =>
|
33
|
+
"rack.multithread".freeze => conf.options[:max_threads] > 1,
|
34
|
+
"rack.multiprocess".freeze => conf.options[:workers] >= 1,
|
28
35
|
"rack.run_once".freeze => false,
|
29
36
|
"SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
|
30
37
|
|
@@ -45,6 +52,12 @@ module Puma
|
|
45
52
|
|
46
53
|
attr_reader :ios
|
47
54
|
|
55
|
+
# @version 5.0.0
|
56
|
+
attr_reader :activated_sockets, :envs, :inherited_fds, :listeners, :proto_env, :unix_paths
|
57
|
+
|
58
|
+
# @version 5.0.0
|
59
|
+
attr_writer :ios, :listeners
|
60
|
+
|
48
61
|
def env(sock)
|
49
62
|
@envs.fetch(sock, @proto_env)
|
50
63
|
end
|
@@ -53,40 +66,44 @@ module Puma
|
|
53
66
|
@ios.each { |i| i.close }
|
54
67
|
end
|
55
68
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
fd, url = v.split(":", 2)
|
62
|
-
@inherited_fds[url] = fd.to_i
|
63
|
-
remove << k
|
64
|
-
elsif k == 'LISTEN_FDS' && ENV['LISTEN_PID'].to_i == $$
|
65
|
-
v.to_i.times do |num|
|
66
|
-
fd = num + 3
|
67
|
-
sock = TCPServer.for_fd(fd)
|
68
|
-
begin
|
69
|
-
key = [ :unix, Socket.unpack_sockaddr_un(sock.getsockname) ]
|
70
|
-
rescue ArgumentError
|
71
|
-
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
|
72
|
-
if addr =~ /\:/
|
73
|
-
addr = "[#{addr}]"
|
74
|
-
end
|
75
|
-
key = [ :tcp, addr, port ]
|
76
|
-
end
|
77
|
-
@activated_sockets[key] = sock
|
78
|
-
@events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
|
79
|
-
end
|
80
|
-
remove << k << 'LISTEN_PID'
|
81
|
-
end
|
82
|
-
end
|
69
|
+
# @!attribute [r] connected_ports
|
70
|
+
# @version 5.0.0
|
71
|
+
def connected_ports
|
72
|
+
ios.map { |io| io.addr[1] }.uniq
|
73
|
+
end
|
83
74
|
|
84
|
-
|
85
|
-
|
75
|
+
# @version 5.0.0
|
76
|
+
def create_inherited_fds(env_hash)
|
77
|
+
env_hash.select {|k,v| k =~ /PUMA_INHERIT_\d+/}.each do |_k, v|
|
78
|
+
fd, url = v.split(":", 2)
|
79
|
+
@inherited_fds[url] = fd.to_i
|
80
|
+
end.keys # pass keys back for removal
|
81
|
+
end
|
82
|
+
|
83
|
+
# systemd socket activation.
|
84
|
+
# LISTEN_FDS = number of listening sockets. e.g. 2 means accept on 2 sockets w/descriptors 3 and 4.
|
85
|
+
# LISTEN_PID = PID of the service process, aka us
|
86
|
+
# @see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
|
87
|
+
# @version 5.0.0
|
88
|
+
#
|
89
|
+
def create_activated_fds(env_hash)
|
90
|
+
return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
|
91
|
+
env_hash['LISTEN_FDS'].to_i.times do |index|
|
92
|
+
sock = TCPServer.for_fd(socket_activation_fd(index))
|
93
|
+
key = begin # Try to parse as a path
|
94
|
+
[:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
|
95
|
+
rescue ArgumentError # Try to parse as a port/ip
|
96
|
+
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
|
97
|
+
addr = "[#{addr}]" if addr =~ /\:/
|
98
|
+
[:tcp, addr, port]
|
99
|
+
end
|
100
|
+
@activated_sockets[key] = sock
|
101
|
+
@events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
|
86
102
|
end
|
103
|
+
["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
|
87
104
|
end
|
88
105
|
|
89
|
-
def parse(binds, logger)
|
106
|
+
def parse(binds, logger, log_msg = 'Listening')
|
90
107
|
binds.each do |str|
|
91
108
|
uri = URI.parse str
|
92
109
|
case uri.scheme
|
@@ -113,7 +130,7 @@ module Puma
|
|
113
130
|
i.local_address.ip_unpack.join(':')
|
114
131
|
end
|
115
132
|
|
116
|
-
logger.log "*
|
133
|
+
logger.log "* #{log_msg} on http://#{addr}"
|
117
134
|
end
|
118
135
|
end
|
119
136
|
|
@@ -149,11 +166,14 @@ module Puma
|
|
149
166
|
end
|
150
167
|
|
151
168
|
io = add_unix_listener path, umask, mode, backlog
|
152
|
-
logger.log "*
|
169
|
+
logger.log "* #{log_msg} on #{str}"
|
153
170
|
end
|
154
171
|
|
155
172
|
@listeners << [str, io]
|
156
173
|
when "ssl"
|
174
|
+
|
175
|
+
raise "Puma compiled without SSL support" unless HAS_SSL
|
176
|
+
|
157
177
|
params = Util.parse_query uri.query
|
158
178
|
ctx = MiniSSL::ContextBuilder.new(params, @events).context
|
159
179
|
|
@@ -204,12 +224,6 @@ module Puma
|
|
204
224
|
end
|
205
225
|
end
|
206
226
|
|
207
|
-
def loopback_addresses
|
208
|
-
Socket.ip_address_list.select do |addrinfo|
|
209
|
-
addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
|
210
|
-
end.map { |addrinfo| addrinfo.ip_address }.uniq
|
211
|
-
end
|
212
|
-
|
213
227
|
# Tell the server to listen on host +host+, port +port+.
|
214
228
|
# If +optimize_for_latency+ is true (the default) then clients connecting
|
215
229
|
# will be optimized for latency over throughput.
|
@@ -226,20 +240,17 @@ module Puma
|
|
226
240
|
end
|
227
241
|
|
228
242
|
host = host[1..-2] if host and host[0..0] == '['
|
229
|
-
|
243
|
+
tcp_server = TCPServer.new(host, port)
|
230
244
|
if optimize_for_latency
|
231
|
-
|
245
|
+
tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
232
246
|
end
|
233
|
-
|
234
|
-
|
235
|
-
@connected_port = s.addr[1]
|
247
|
+
tcp_server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
|
248
|
+
tcp_server.listen backlog
|
236
249
|
|
237
|
-
@ios <<
|
238
|
-
|
250
|
+
@ios << tcp_server
|
251
|
+
tcp_server
|
239
252
|
end
|
240
253
|
|
241
|
-
attr_reader :connected_port
|
242
|
-
|
243
254
|
def inherit_tcp_listener(host, port, fd)
|
244
255
|
if fd.kind_of? TCPServer
|
245
256
|
s = fd
|
@@ -253,9 +264,8 @@ module Puma
|
|
253
264
|
|
254
265
|
def add_ssl_listener(host, port, ctx,
|
255
266
|
optimize_for_latency=true, backlog=1024)
|
256
|
-
require 'puma/minissl'
|
257
267
|
|
258
|
-
|
268
|
+
raise "Puma compiled without SSL support" unless HAS_SSL
|
259
269
|
|
260
270
|
if host == "localhost"
|
261
271
|
loopback_addresses.each do |addr|
|
@@ -272,7 +282,6 @@ module Puma
|
|
272
282
|
s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
|
273
283
|
s.listen backlog
|
274
284
|
|
275
|
-
|
276
285
|
ssl = MiniSSL::Server.new s, ctx
|
277
286
|
env = @proto_env.dup
|
278
287
|
env[HTTPS_KEY] = HTTPS
|
@@ -283,8 +292,7 @@ module Puma
|
|
283
292
|
end
|
284
293
|
|
285
294
|
def inherit_ssl_listener(fd, ctx)
|
286
|
-
|
287
|
-
MiniSSL.check
|
295
|
+
raise "Puma compiled without SSL support" unless HAS_SSL
|
288
296
|
|
289
297
|
if fd.kind_of? TCPServer
|
290
298
|
s = fd
|
@@ -360,26 +368,40 @@ module Puma
|
|
360
368
|
end
|
361
369
|
|
362
370
|
def close_listeners
|
363
|
-
|
364
|
-
io.close
|
371
|
+
listeners.each do |l, io|
|
372
|
+
io.close unless io.closed? # Ruby 2.2 issue
|
365
373
|
uri = URI.parse(l)
|
366
374
|
next unless uri.scheme == 'unix'
|
367
375
|
unix_path = "#{uri.host}#{uri.path}"
|
368
|
-
File.unlink unix_path if
|
376
|
+
File.unlink unix_path if unix_paths.include? unix_path
|
369
377
|
end
|
370
378
|
end
|
371
379
|
|
372
|
-
def
|
373
|
-
|
380
|
+
def redirects_for_restart
|
381
|
+
redirects = listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
|
382
|
+
redirects[:close_others] = true
|
383
|
+
redirects
|
374
384
|
end
|
375
385
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
redirects[io.to_i] = io.to_i
|
386
|
+
# @version 5.0.0
|
387
|
+
def redirects_for_restart_env
|
388
|
+
listeners.each_with_object({}).with_index do |(listen, memo), i|
|
389
|
+
memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
|
381
390
|
end
|
382
|
-
|
391
|
+
end
|
392
|
+
|
393
|
+
private
|
394
|
+
|
395
|
+
# @!attribute [r] loopback_addresses
|
396
|
+
def loopback_addresses
|
397
|
+
Socket.ip_address_list.select do |addrinfo|
|
398
|
+
addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
|
399
|
+
end.map { |addrinfo| addrinfo.ip_address }.uniq
|
400
|
+
end
|
401
|
+
|
402
|
+
# @version 5.0.0
|
403
|
+
def socket_activation_fd(int)
|
404
|
+
int + 3 # 3 is the magic number you add to follow the SA protocol
|
383
405
|
end
|
384
406
|
end
|
385
407
|
end
|