puma 4.3.5-java → 5.0.1-java

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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1149 -518
  3. data/LICENSE +23 -20
  4. data/README.md +26 -13
  5. data/docs/architecture.md +3 -3
  6. data/docs/deployment.md +9 -3
  7. data/docs/fork_worker.md +31 -0
  8. data/docs/jungle/README.md +13 -0
  9. data/{tools → docs}/jungle/rc.d/README.md +0 -0
  10. data/{tools → docs}/jungle/rc.d/puma +0 -0
  11. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  12. data/{tools → docs}/jungle/upstart/README.md +0 -0
  13. data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
  14. data/{tools → docs}/jungle/upstart/puma.conf +0 -0
  15. data/docs/signals.md +7 -6
  16. data/docs/systemd.md +1 -63
  17. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  18. data/ext/puma_http11/extconf.rb +4 -3
  19. data/ext/puma_http11/http11_parser.c +64 -64
  20. data/ext/puma_http11/mini_ssl.c +15 -2
  21. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  22. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  23. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
  24. data/ext/puma_http11/puma_http11.c +7 -38
  25. data/lib/puma.rb +20 -0
  26. data/lib/puma/app/status.rb +18 -3
  27. data/lib/puma/binder.rb +90 -68
  28. data/lib/puma/cli.rb +7 -15
  29. data/lib/puma/client.rb +62 -13
  30. data/lib/puma/cluster.rb +193 -74
  31. data/lib/puma/commonlogger.rb +2 -2
  32. data/lib/puma/configuration.rb +31 -42
  33. data/lib/puma/const.rb +3 -3
  34. data/lib/puma/control_cli.rb +29 -17
  35. data/lib/puma/detect.rb +17 -0
  36. data/lib/puma/dsl.rb +144 -70
  37. data/lib/puma/error_logger.rb +97 -0
  38. data/lib/puma/events.rb +37 -31
  39. data/lib/puma/io_buffer.rb +9 -2
  40. data/lib/puma/jruby_restart.rb +0 -58
  41. data/lib/puma/launcher.rb +57 -31
  42. data/lib/puma/minissl.rb +68 -18
  43. data/lib/puma/minissl/context_builder.rb +0 -3
  44. data/lib/puma/null_io.rb +1 -1
  45. data/lib/puma/plugin.rb +1 -10
  46. data/lib/puma/puma_http11.jar +0 -0
  47. data/lib/puma/rack/builder.rb +0 -4
  48. data/lib/puma/reactor.rb +10 -16
  49. data/lib/puma/runner.rb +13 -54
  50. data/lib/puma/server.rb +193 -241
  51. data/lib/puma/single.rb +8 -64
  52. data/lib/puma/state_file.rb +6 -3
  53. data/lib/puma/thread_pool.rb +116 -51
  54. data/lib/puma/util.rb +1 -0
  55. data/lib/rack/handler/puma.rb +1 -3
  56. data/tools/{docker/Dockerfile → Dockerfile} +0 -0
  57. metadata +17 -19
  58. data/docs/tcp_mode.md +0 -96
  59. data/ext/puma_http11/io_buffer.c +0 -155
  60. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  61. data/lib/puma/tcp_logger.rb +0 -41
  62. data/tools/jungle/README.md +0 -19
  63. data/tools/jungle/init.d/README.md +0 -61
  64. data/tools/jungle/init.d/puma +0 -421
  65. data/tools/jungle/init.d/run-puma +0 -18
@@ -301,6 +301,7 @@ void raise_error(SSL* ssl, int result) {
301
301
  char msg[512];
302
302
  const char* err_str;
303
303
  int err = errno;
304
+ int mask = 4095;
304
305
  int ssl_err = SSL_get_error(ssl, result);
305
306
  int verify_err = (int) SSL_get_verify_result(ssl);
306
307
 
@@ -317,8 +318,8 @@ void raise_error(SSL* ssl, int result) {
317
318
  } else {
318
319
  err = (int) ERR_get_error();
319
320
  ERR_error_string_n(err, buf, sizeof(buf));
320
- snprintf(msg, sizeof(msg), "OpenSSL error: %s - %d", buf, err);
321
-
321
+ int errexp = err & mask;
322
+ snprintf(msg, sizeof(msg), "OpenSSL error: %s - %d", buf, errexp);
322
323
  }
323
324
  } else {
324
325
  snprintf(msg, sizeof(msg), "Unknown OpenSSL error: %d", ssl_err);
@@ -462,6 +463,16 @@ VALUE engine_peercert(VALUE self) {
462
463
  return rb_cert_buf;
463
464
  }
464
465
 
466
+ /* @see Puma::MiniSSL::Socket#ssl_version_state
467
+ * @version 5.0.0
468
+ */
469
+ static VALUE
470
+ engine_ssl_vers_st(VALUE self) {
471
+ ms_conn* conn;
472
+ Data_Get_Struct(self, ms_conn, conn);
473
+ return rb_ary_new3(2, rb_str_new2(SSL_get_version(conn->ssl)), rb_str_new2(SSL_state_string(conn->ssl)));
474
+ }
475
+
465
476
  VALUE noop(VALUE self) {
466
477
  return Qnil;
467
478
  }
@@ -533,6 +544,8 @@ void Init_mini_ssl(VALUE puma) {
533
544
  rb_define_method(eng, "init?", engine_init, 0);
534
545
 
535
546
  rb_define_method(eng, "peercert", engine_peercert, 0);
547
+
548
+ rb_define_method(eng, "ssl_vers_st", engine_ssl_vers_st, 0);
536
549
  }
537
550
 
538
551
  #else
@@ -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 = 2048;
34
- public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the 2048 allowed length.";
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
- RubyClass klass = (RubyClass) recv;
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, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
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
- ks.load(new FileInputStream(keystoreFile), password);
149
- ts.load(new FileInputStream(keystoreFile), password);
150
-
151
- KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
152
- kmf.init(ks, password);
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 cases are OK and CLOSED. We're done here.
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(engine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
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
- SSLEngineResult res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
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
- handshakeStatus = engine.getHandshakeStatus();
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
  }
@@ -10,6 +10,7 @@
10
10
  #include "ext_help.h"
11
11
  #include <assert.h>
12
12
  #include <string.h>
13
+ #include <ctype.h>
13
14
  #include "http11_parser.h"
14
15
 
15
16
  #ifndef MANAGED_STRINGS
@@ -53,7 +54,7 @@ DEF_MAX_LENGTH(FIELD_NAME, 256);
53
54
  DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
54
55
  DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
55
56
  DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
56
- DEF_MAX_LENGTH(REQUEST_PATH, 2048);
57
+ DEF_MAX_LENGTH(REQUEST_PATH, 8192);
57
58
  DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
58
59
  DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
59
60
 
@@ -111,21 +112,6 @@ static struct common_field common_http_fields[] = {
111
112
  # undef f
112
113
  };
113
114
 
114
- /*
115
- * qsort(3) and bsearch(3) improve average performance slightly, but may
116
- * not be worth it for lack of portability to certain platforms...
117
- */
118
- #if defined(HAVE_QSORT_BSEARCH)
119
- /* sort by length, then by name if there's a tie */
120
- static int common_field_cmp(const void *a, const void *b)
121
- {
122
- struct common_field *cfa = (struct common_field *)a;
123
- struct common_field *cfb = (struct common_field *)b;
124
- signed long diff = cfa->len - cfb->len;
125
- return diff ? diff : memcmp(cfa->name, cfb->name, cfa->len);
126
- }
127
- #endif /* HAVE_QSORT_BSEARCH */
128
-
129
115
  static void init_common_fields(void)
130
116
  {
131
117
  unsigned i;
@@ -142,28 +128,10 @@ static void init_common_fields(void)
142
128
  }
143
129
  rb_global_variable(&cf->value);
144
130
  }
145
-
146
- #if defined(HAVE_QSORT_BSEARCH)
147
- qsort(common_http_fields,
148
- ARRAY_SIZE(common_http_fields),
149
- sizeof(struct common_field),
150
- common_field_cmp);
151
- #endif /* HAVE_QSORT_BSEARCH */
152
131
  }
153
132
 
154
133
  static VALUE find_common_field_value(const char *field, size_t flen)
155
134
  {
156
- #if defined(HAVE_QSORT_BSEARCH)
157
- struct common_field key;
158
- struct common_field *found;
159
- key.name = field;
160
- key.len = (signed long)flen;
161
- found = (struct common_field *)bsearch(&key, common_http_fields,
162
- ARRAY_SIZE(common_http_fields),
163
- sizeof(struct common_field),
164
- common_field_cmp);
165
- return found ? found->value : Qnil;
166
- #else /* !HAVE_QSORT_BSEARCH */
167
135
  unsigned i;
168
136
  struct common_field *cf = common_http_fields;
169
137
  for(i = 0; i < ARRAY_SIZE(common_http_fields); i++, cf++) {
@@ -171,7 +139,6 @@ static VALUE find_common_field_value(const char *field, size_t flen)
171
139
  return cf->value;
172
140
  }
173
141
  return Qnil;
174
- #endif /* !HAVE_QSORT_BSEARCH */
175
142
  }
176
143
 
177
144
  void http_field(puma_parser* hp, const char *field, size_t flen,
@@ -400,7 +367,7 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
400
367
  VALIDATE_MAX_LENGTH(puma_parser_nread(http), HEADER);
401
368
 
402
369
  if(puma_parser_has_error(http)) {
403
- 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?");
404
371
  } else {
405
372
  return INT2FIX(puma_parser_nread(http));
406
373
  }
@@ -467,8 +434,9 @@ VALUE HttpParser_body(VALUE self) {
467
434
  return http->body;
468
435
  }
469
436
 
470
- void Init_io_buffer(VALUE puma);
437
+ #ifdef HAVE_OPENSSL_BIO_H
471
438
  void Init_mini_ssl(VALUE mod);
439
+ #endif
472
440
 
473
441
  void Init_puma_http11()
474
442
  {
@@ -497,6 +465,7 @@ void Init_puma_http11()
497
465
  rb_define_method(cHttpParser, "body", HttpParser_body, 0);
498
466
  init_common_fields();
499
467
 
500
- Init_io_buffer(mPuma);
468
+ #ifdef HAVE_OPENSSL_BIO_H
501
469
  Init_mini_ssl(mPuma);
470
+ #endif
502
471
  }
@@ -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
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
-
5
3
  module Puma
6
4
  module App
7
5
  # Check out {#call}'s source code to see what actions this web application
@@ -19,6 +17,10 @@ module Puma
19
17
  return rack_response(403, 'Invalid auth token', 'text/plain')
20
18
  end
21
19
 
20
+ if env['PATH_INFO'] =~ /\/(gc-stats|stats|thread-backtraces)$/
21
+ require 'json'
22
+ end
23
+
22
24
  case env['PATH_INFO']
23
25
  when /\/stop$/
24
26
  @cli.stop
@@ -54,7 +56,20 @@ module Puma
54
56
  rack_response(200, GC.stat.to_json)
55
57
 
56
58
  when /\/stats$/
57
- 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
+
58
73
  else
59
74
  rack_response 404, "Unsupported action", 'text/plain'
60
75
  end
@@ -5,15 +5,22 @@ require 'socket'
5
5
 
6
6
  require 'puma/const'
7
7
  require 'puma/util'
8
- require 'puma/minissl/context_builder'
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,3].freeze
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 => true,
27
- "rack.multiprocess".freeze => false,
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
- def import_from_env
57
- remove = []
58
-
59
- ENV.each do |k,v|
60
- if k =~ /PUMA_INHERIT_\d+/
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
- remove.each do |k|
85
- ENV.delete k
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 "* Listening on tcp://#{addr}"
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 "* Listening on #{str}"
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
- s = TCPServer.new(host, port)
243
+ tcp_server = TCPServer.new(host, port)
230
244
  if optimize_for_latency
231
- s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
245
+ tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
232
246
  end
233
- s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
234
- s.listen backlog
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 << s
238
- s
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
- MiniSSL.check
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
- require 'puma/minissl'
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
- @listeners.each do |l, io|
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 @unix_paths.include? unix_path
376
+ File.unlink unix_path if unix_paths.include? unix_path
369
377
  end
370
378
  end
371
379
 
372
- def close_unix_paths
373
- @unix_paths.each { |up| File.unlink(up) if File.exist? up }
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
- def redirects_for_restart
377
- redirects = {:close_others => true}
378
- @listeners.each_with_index do |(l, io), i|
379
- ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
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
- redirects
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