puma 4.3.12 → 5.6.4

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.

Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1461 -524
  3. data/LICENSE +23 -20
  4. data/README.md +120 -36
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +63 -26
  7. data/docs/compile_options.md +21 -0
  8. data/docs/deployment.md +60 -69
  9. data/docs/fork_worker.md +33 -0
  10. data/docs/jungle/README.md +9 -0
  11. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  12. data/{tools → docs}/jungle/rc.d/puma +2 -2
  13. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  14. data/docs/kubernetes.md +66 -0
  15. data/docs/nginx.md +1 -1
  16. data/docs/plugins.md +15 -15
  17. data/docs/rails_dev_mode.md +28 -0
  18. data/docs/restart.md +46 -23
  19. data/docs/signals.md +13 -11
  20. data/docs/stats.md +142 -0
  21. data/docs/systemd.md +85 -128
  22. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  23. data/ext/puma_http11/ext_help.h +1 -1
  24. data/ext/puma_http11/extconf.rb +38 -9
  25. data/ext/puma_http11/http11_parser.c +45 -47
  26. data/ext/puma_http11/http11_parser.h +1 -1
  27. data/ext/puma_http11/http11_parser.java.rl +1 -1
  28. data/ext/puma_http11/http11_parser.rl +1 -1
  29. data/ext/puma_http11/mini_ssl.c +204 -86
  30. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  31. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  32. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +3 -5
  33. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +105 -61
  34. data/ext/puma_http11/puma_http11.c +32 -51
  35. data/lib/puma/app/status.rb +47 -36
  36. data/lib/puma/binder.rb +225 -106
  37. data/lib/puma/cli.rb +24 -18
  38. data/lib/puma/client.rb +104 -76
  39. data/lib/puma/cluster/worker.rb +173 -0
  40. data/lib/puma/cluster/worker_handle.rb +94 -0
  41. data/lib/puma/cluster.rb +212 -220
  42. data/lib/puma/commonlogger.rb +2 -2
  43. data/lib/puma/configuration.rb +58 -49
  44. data/lib/puma/const.rb +13 -6
  45. data/lib/puma/control_cli.rb +93 -76
  46. data/lib/puma/detect.rb +29 -2
  47. data/lib/puma/dsl.rb +364 -96
  48. data/lib/puma/error_logger.rb +104 -0
  49. data/lib/puma/events.rb +55 -34
  50. data/lib/puma/io_buffer.rb +9 -2
  51. data/lib/puma/jruby_restart.rb +0 -58
  52. data/lib/puma/json_serialization.rb +96 -0
  53. data/lib/puma/launcher.rb +117 -46
  54. data/lib/puma/minissl/context_builder.rb +14 -9
  55. data/lib/puma/minissl.rb +128 -46
  56. data/lib/puma/null_io.rb +13 -1
  57. data/lib/puma/plugin.rb +3 -12
  58. data/lib/puma/queue_close.rb +26 -0
  59. data/lib/puma/rack/builder.rb +1 -5
  60. data/lib/puma/reactor.rb +85 -369
  61. data/lib/puma/request.rb +472 -0
  62. data/lib/puma/runner.rb +46 -61
  63. data/lib/puma/server.rb +290 -763
  64. data/lib/puma/single.rb +9 -65
  65. data/lib/puma/state_file.rb +47 -8
  66. data/lib/puma/systemd.rb +46 -0
  67. data/lib/puma/thread_pool.rb +125 -57
  68. data/lib/puma/util.rb +20 -1
  69. data/lib/puma.rb +46 -0
  70. data/lib/rack/handler/puma.rb +2 -3
  71. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  72. metadata +26 -22
  73. data/docs/tcp_mode.md +0 -96
  74. data/ext/puma_http11/io_buffer.c +0 -155
  75. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  76. data/lib/puma/accept_nonblock.rb +0 -29
  77. data/lib/puma/tcp_logger.rb +0 -41
  78. data/tools/jungle/README.md +0 -19
  79. data/tools/jungle/init.d/README.md +0 -61
  80. data/tools/jungle/init.d/puma +0 -421
  81. data/tools/jungle/init.d/run-puma +0 -18
  82. data/tools/jungle/upstart/README.md +0 -61
  83. data/tools/jungle/upstart/puma-manager.conf +0 -31
  84. data/tools/jungle/upstart/puma.conf +0 -69
@@ -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;
@@ -22,6 +23,7 @@ import javax.net.ssl.SSLException;
22
23
  import javax.net.ssl.SSLPeerUnverifiedException;
23
24
  import javax.net.ssl.SSLSession;
24
25
  import java.io.FileInputStream;
26
+ import java.io.InputStream;
25
27
  import java.io.IOException;
26
28
  import java.nio.Buffer;
27
29
  import java.nio.ByteBuffer;
@@ -32,6 +34,8 @@ import java.security.NoSuchAlgorithmException;
32
34
  import java.security.UnrecoverableKeyException;
33
35
  import java.security.cert.CertificateEncodingException;
34
36
  import java.security.cert.CertificateException;
37
+ import java.util.concurrent.ConcurrentHashMap;
38
+ import java.util.Map;
35
39
 
36
40
  import static javax.net.ssl.SSLEngineResult.Status;
37
41
  import static javax.net.ssl.SSLEngineResult.HandshakeStatus;
@@ -77,11 +81,11 @@ public class MiniSSL extends RubyObject {
77
81
  /**
78
82
  * Writes bytes to the buffer after ensuring there's room
79
83
  */
80
- public void put(byte[] bytes) {
81
- if (buffer.remaining() < bytes.length) {
82
- resize(buffer.limit() + bytes.length);
84
+ private void put(byte[] bytes, final int offset, final int length) {
85
+ if (buffer.remaining() < length) {
86
+ resize(buffer.limit() + length);
83
87
  }
84
- buffer.put(bytes);
88
+ buffer.put(bytes, offset, length);
85
89
  }
86
90
 
87
91
  /**
@@ -112,7 +116,7 @@ public class MiniSSL extends RubyObject {
112
116
 
113
117
  buffer.get(bss);
114
118
  buffer.clear();
115
- return new ByteList(bss);
119
+ return new ByteList(bss, false);
116
120
  }
117
121
 
118
122
  @Override
@@ -120,6 +124,8 @@ public class MiniSSL extends RubyObject {
120
124
  }
121
125
 
122
126
  private SSLEngine engine;
127
+ private boolean closed;
128
+ private boolean handshake;
123
129
  private MiniSSLBuffer inboundNetData;
124
130
  private MiniSSLBuffer outboundAppData;
125
131
  private MiniSSLBuffer outboundNetData;
@@ -128,10 +134,39 @@ public class MiniSSL extends RubyObject {
128
134
  super(runtime, klass);
129
135
  }
130
136
 
137
+ private static Map<String, KeyManagerFactory> keyManagerFactoryMap = new ConcurrentHashMap<String, KeyManagerFactory>();
138
+ private static Map<String, TrustManagerFactory> trustManagerFactoryMap = new ConcurrentHashMap<String, TrustManagerFactory>();
139
+
131
140
  @JRubyMethod(meta = true)
132
- public static IRubyObject server(ThreadContext context, IRubyObject recv, IRubyObject miniSSLContext) {
133
- RubyClass klass = (RubyClass) recv;
141
+ public static synchronized IRubyObject server(ThreadContext context, IRubyObject recv, IRubyObject miniSSLContext)
142
+ throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
143
+ // Create the KeyManagerFactory and TrustManagerFactory for this server
144
+ String keystoreFile = miniSSLContext.callMethod(context, "keystore").convertToString().asJavaString();
145
+ char[] password = miniSSLContext.callMethod(context, "keystore_pass").convertToString().asJavaString().toCharArray();
146
+
147
+ KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
148
+ InputStream is = new FileInputStream(keystoreFile);
149
+ try {
150
+ ks.load(is, password);
151
+ } finally {
152
+ is.close();
153
+ }
154
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
155
+ kmf.init(ks, password);
156
+ keyManagerFactoryMap.put(keystoreFile, kmf);
157
+
158
+ KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
159
+ is = new FileInputStream(keystoreFile);
160
+ try {
161
+ ts.load(is, password);
162
+ } finally {
163
+ is.close();
164
+ }
165
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
166
+ tmf.init(ts);
167
+ trustManagerFactoryMap.put(keystoreFile, tmf);
134
168
 
169
+ RubyClass klass = (RubyClass) recv;
135
170
  return klass.newInstance(context,
136
171
  new IRubyObject[] { miniSSLContext },
137
172
  Block.NULL_BLOCK);
@@ -139,24 +174,20 @@ public class MiniSSL extends RubyObject {
139
174
 
140
175
  @JRubyMethod
141
176
  public IRubyObject initialize(ThreadContext threadContext, IRubyObject miniSSLContext)
142
- throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
143
- KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
144
- KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
177
+ throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
145
178
 
146
- char[] password = miniSSLContext.callMethod(threadContext, "keystore_pass").convertToString().asJavaString().toCharArray();
147
179
  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);
180
+ KeyManagerFactory kmf = keyManagerFactoryMap.get(keystoreFile);
181
+ TrustManagerFactory tmf = trustManagerFactoryMap.get(keystoreFile);
182
+ if(kmf == null || tmf == null) {
183
+ throw new KeyStoreException("Could not find KeyManagerFactory/TrustManagerFactory for keystore: " + keystoreFile);
184
+ }
156
185
 
157
186
  SSLContext sslCtx = SSLContext.getInstance("TLS");
158
187
 
159
188
  sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
189
+ closed = false;
190
+ handshake = false;
160
191
  engine = sslCtx.createSSLEngine();
161
192
 
162
193
  String[] protocols;
@@ -173,7 +204,7 @@ public class MiniSSL extends RubyObject {
173
204
  engine.setEnabledProtocols(protocols);
174
205
  engine.setUseClientMode(false);
175
206
 
176
- long verify_mode = miniSSLContext.callMethod(threadContext, "verify_mode").convertToInteger().getLongValue();
207
+ long verify_mode = miniSSLContext.callMethod(threadContext, "verify_mode").convertToInteger("to_i").getLongValue();
177
208
  if ((verify_mode & 0x1) != 0) { // 'peer'
178
209
  engine.setWantClientAuth(true);
179
210
  }
@@ -198,14 +229,9 @@ public class MiniSSL extends RubyObject {
198
229
 
199
230
  @JRubyMethod
200
231
  public IRubyObject inject(IRubyObject arg) {
201
- try {
202
- byte[] bytes = arg.convertToString().getBytes();
203
- inboundNetData.put(bytes);
204
- return this;
205
- } catch (Exception e) {
206
- e.printStackTrace();
207
- throw new RuntimeException(e);
208
- }
232
+ ByteList bytes = arg.convertToString().getByteList();
233
+ inboundNetData.put(bytes.unsafeBytes(), bytes.getBegin(), bytes.getRealSize());
234
+ return this;
209
235
  }
210
236
 
211
237
  private enum SSLOperation {
@@ -240,14 +266,21 @@ public class MiniSSL extends RubyObject {
240
266
  // need to wait for more data to come in before we retry
241
267
  retryOp = false;
242
268
  break;
269
+ case CLOSED:
270
+ closed = true;
271
+ retryOp = false;
272
+ break;
243
273
  default:
244
- // other cases are OK and CLOSED. We're done here.
274
+ // other case is OK. We're done here.
245
275
  retryOp = false;
246
276
  }
277
+ if (res.getHandshakeStatus() == HandshakeStatus.FINISHED) {
278
+ handshake = true;
279
+ }
247
280
  }
248
281
 
249
282
  // after each op, run any delegated tasks if needed
250
- if(engine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
283
+ if(res.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
251
284
  Runnable runnable;
252
285
  while ((runnable = engine.getDelegatedTask()) != null) {
253
286
  runnable.run();
@@ -258,7 +291,7 @@ public class MiniSSL extends RubyObject {
258
291
  }
259
292
 
260
293
  @JRubyMethod
261
- public IRubyObject read() throws Exception {
294
+ public IRubyObject read() {
262
295
  try {
263
296
  inboundNetData.flip();
264
297
 
@@ -271,13 +304,14 @@ public class MiniSSL extends RubyObject {
271
304
 
272
305
  HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
273
306
  boolean done = false;
307
+ SSLEngineResult res = null;
274
308
  while (!done) {
275
309
  switch (handshakeStatus) {
276
310
  case NEED_WRAP:
277
- doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
311
+ res = doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
278
312
  break;
279
313
  case NEED_UNWRAP:
280
- SSLEngineResult res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
314
+ res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
281
315
  if (res.getStatus() == Status.BUFFER_UNDERFLOW) {
282
316
  // need more data before we can shake more hands
283
317
  done = true;
@@ -286,7 +320,9 @@ public class MiniSSL extends RubyObject {
286
320
  default:
287
321
  done = true;
288
322
  }
289
- handshakeStatus = engine.getHandshakeStatus();
323
+ if (!done) {
324
+ handshakeStatus = res.getHandshakeStatus();
325
+ }
290
326
  }
291
327
 
292
328
  if (inboundNetData.hasRemaining()) {
@@ -300,55 +336,46 @@ public class MiniSSL extends RubyObject {
300
336
  return getRuntime().getNil();
301
337
  }
302
338
 
303
- RubyString str = getRuntime().newString("");
304
- str.setValue(appDataByteList);
305
- return str;
306
- } catch (Exception e) {
307
- throw getRuntime().newEOFError(e.getMessage());
339
+ return RubyString.newString(getRuntime(), appDataByteList);
340
+ } catch (SSLException e) {
341
+ RaiseException re = getRuntime().newEOFError(e.getMessage());
342
+ re.initCause(e);
343
+ throw re;
308
344
  }
309
345
  }
310
346
 
311
347
  @JRubyMethod
312
348
  public IRubyObject write(IRubyObject arg) {
313
- try {
314
- byte[] bls = arg.convertToString().getBytes();
315
- outboundAppData = new MiniSSLBuffer(bls);
349
+ byte[] bls = arg.convertToString().getBytes();
350
+ outboundAppData = new MiniSSLBuffer(bls);
316
351
 
317
- return getRuntime().newFixnum(bls.length);
318
- } catch (Exception e) {
319
- e.printStackTrace();
320
- throw new RuntimeException(e);
321
- }
352
+ return getRuntime().newFixnum(bls.length);
322
353
  }
323
354
 
324
355
  @JRubyMethod
325
- public IRubyObject extract() throws SSLException {
356
+ public IRubyObject extract(ThreadContext context) {
326
357
  try {
327
358
  ByteList dataByteList = outboundNetData.asByteList();
328
359
  if (dataByteList != null) {
329
- RubyString str = getRuntime().newString("");
330
- str.setValue(dataByteList);
331
- return str;
360
+ return RubyString.newString(context.runtime, dataByteList);
332
361
  }
333
362
 
334
363
  if (!outboundAppData.hasRemaining()) {
335
- return getRuntime().getNil();
364
+ return context.nil;
336
365
  }
337
366
 
338
367
  outboundNetData.clear();
339
368
  doOp(SSLOperation.WRAP, outboundAppData, outboundNetData);
340
369
  dataByteList = outboundNetData.asByteList();
341
370
  if (dataByteList == null) {
342
- return getRuntime().getNil();
371
+ return context.nil;
343
372
  }
344
373
 
345
- RubyString str = getRuntime().newString("");
346
- str.setValue(dataByteList);
347
-
348
- return str;
349
- } catch (Exception e) {
350
- e.printStackTrace();
351
- throw new RuntimeException(e);
374
+ return RubyString.newString(context.runtime, dataByteList);
375
+ } catch (SSLException e) {
376
+ RaiseException ex = context.runtime.newRuntimeError(e.toString());
377
+ ex.initCause(e);
378
+ throw ex;
352
379
  }
353
380
  }
354
381
 
@@ -356,8 +383,25 @@ public class MiniSSL extends RubyObject {
356
383
  public IRubyObject peercert() throws CertificateEncodingException {
357
384
  try {
358
385
  return JavaEmbedUtils.javaToRuby(getRuntime(), engine.getSession().getPeerCertificates()[0].getEncoded());
359
- } catch (SSLPeerUnverifiedException ex) {
386
+ } catch (SSLPeerUnverifiedException e) {
360
387
  return getRuntime().getNil();
361
388
  }
362
389
  }
390
+
391
+ @JRubyMethod(name = "init?")
392
+ public IRubyObject isInit(ThreadContext context) {
393
+ return handshake ? getRuntime().getFalse() : getRuntime().getTrue();
394
+ }
395
+
396
+ @JRubyMethod
397
+ public IRubyObject shutdown() {
398
+ if (closed || engine.isInboundDone() && engine.isOutboundDone()) {
399
+ if (engine.isOutboundDone()) {
400
+ engine.closeOutbound();
401
+ }
402
+ return getRuntime().getTrue();
403
+ } else {
404
+ return getRuntime().getFalse();
405
+ }
406
+ }
363
407
  }
@@ -40,7 +40,9 @@ static VALUE global_http_version;
40
40
  static VALUE global_request_path;
41
41
 
42
42
  /** Defines common length and error messages for input length validation. */
43
- #define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N " is longer than the " # length " allowed length (was %d)"
43
+ #define QUOTE(s) #s
44
+ #define EXPLAIN_MAX_LENGTH_VALUE(s) QUOTE(s)
45
+ #define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N " is longer than the " EXPLAIN_MAX_LENGTH_VALUE(length) " allowed length (was %d)"
44
46
 
45
47
  /** Validates the max length of given input and throws an HttpParserError exception if over. */
46
48
  #define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, MAX_##N##_LENGTH_ERR, len); }
@@ -50,12 +52,16 @@ static VALUE global_request_path;
50
52
 
51
53
 
52
54
  /* Defines the maximum allowed lengths for various input elements.*/
55
+ #ifndef PUMA_QUERY_STRING_MAX_LENGTH
56
+ #define PUMA_QUERY_STRING_MAX_LENGTH (1024 * 10)
57
+ #endif
58
+
53
59
  DEF_MAX_LENGTH(FIELD_NAME, 256);
54
60
  DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
55
61
  DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
56
62
  DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
57
- DEF_MAX_LENGTH(REQUEST_PATH, 2048);
58
- DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
63
+ DEF_MAX_LENGTH(REQUEST_PATH, 8192);
64
+ DEF_MAX_LENGTH(QUERY_STRING, PUMA_QUERY_STRING_MAX_LENGTH);
59
65
  DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
60
66
 
61
67
  struct common_field {
@@ -112,21 +118,6 @@ static struct common_field common_http_fields[] = {
112
118
  # undef f
113
119
  };
114
120
 
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
121
  static void init_common_fields(void)
131
122
  {
132
123
  unsigned i;
@@ -143,28 +134,10 @@ static void init_common_fields(void)
143
134
  }
144
135
  rb_global_variable(&cf->value);
145
136
  }
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
137
  }
154
138
 
155
139
  static VALUE find_common_field_value(const char *field, size_t flen)
156
140
  {
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
141
  unsigned i;
169
142
  struct common_field *cf = common_http_fields;
170
143
  for(i = 0; i < ARRAY_SIZE(common_http_fields); i++, cf++) {
@@ -172,7 +145,6 @@ static VALUE find_common_field_value(const char *field, size_t flen)
172
145
  return cf->value;
173
146
  }
174
147
  return Qnil;
175
- #endif /* !HAVE_QSORT_BSEARCH */
176
148
  }
177
149
 
178
150
  void http_field(puma_parser* hp, const char *field, size_t flen,
@@ -287,11 +259,18 @@ void HttpParser_free(void *data) {
287
259
  }
288
260
  }
289
261
 
290
- void HttpParser_mark(puma_parser* hp) {
262
+ void HttpParser_mark(void *ptr) {
263
+ puma_parser *hp = ptr;
291
264
  if(hp->request) rb_gc_mark(hp->request);
292
265
  if(hp->body) rb_gc_mark(hp->body);
293
266
  }
294
267
 
268
+ const rb_data_type_t HttpParser_data_type = {
269
+ "HttpParser",
270
+ { HttpParser_mark, HttpParser_free, 0 },
271
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
272
+ };
273
+
295
274
  VALUE HttpParser_alloc(VALUE klass)
296
275
  {
297
276
  puma_parser *hp = ALLOC_N(puma_parser, 1);
@@ -308,7 +287,7 @@ VALUE HttpParser_alloc(VALUE klass)
308
287
 
309
288
  puma_parser_init(hp);
310
289
 
311
- return Data_Wrap_Struct(klass, HttpParser_mark, HttpParser_free, hp);
290
+ return TypedData_Wrap_Struct(klass, &HttpParser_data_type, hp);
312
291
  }
313
292
 
314
293
  /**
@@ -320,7 +299,7 @@ VALUE HttpParser_alloc(VALUE klass)
320
299
  VALUE HttpParser_init(VALUE self)
321
300
  {
322
301
  puma_parser *http = NULL;
323
- DATA_GET(self, puma_parser, http);
302
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
324
303
  puma_parser_init(http);
325
304
 
326
305
  return self;
@@ -337,7 +316,7 @@ VALUE HttpParser_init(VALUE self)
337
316
  VALUE HttpParser_reset(VALUE self)
338
317
  {
339
318
  puma_parser *http = NULL;
340
- DATA_GET(self, puma_parser, http);
319
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
341
320
  puma_parser_init(http);
342
321
 
343
322
  return Qnil;
@@ -354,7 +333,7 @@ VALUE HttpParser_reset(VALUE self)
354
333
  VALUE HttpParser_finish(VALUE self)
355
334
  {
356
335
  puma_parser *http = NULL;
357
- DATA_GET(self, puma_parser, http);
336
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
358
337
  puma_parser_finish(http);
359
338
 
360
339
  return puma_parser_is_finished(http) ? Qtrue : Qfalse;
@@ -385,7 +364,7 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
385
364
  char *dptr = NULL;
386
365
  long dlen = 0;
387
366
 
388
- DATA_GET(self, puma_parser, http);
367
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
389
368
 
390
369
  from = FIX2INT(start);
391
370
  dptr = rb_extract_chars(data, &dlen);
@@ -401,7 +380,7 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
401
380
  VALIDATE_MAX_LENGTH(puma_parser_nread(http), HEADER);
402
381
 
403
382
  if(puma_parser_has_error(http)) {
404
- rb_raise(eHttpParserError, "%s", "Invalid HTTP format, parsing fails.");
383
+ rb_raise(eHttpParserError, "%s", "Invalid HTTP format, parsing fails. Are you trying to open an SSL connection to a non-SSL Puma?");
405
384
  } else {
406
385
  return INT2FIX(puma_parser_nread(http));
407
386
  }
@@ -419,7 +398,7 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
419
398
  VALUE HttpParser_has_error(VALUE self)
420
399
  {
421
400
  puma_parser *http = NULL;
422
- DATA_GET(self, puma_parser, http);
401
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
423
402
 
424
403
  return puma_parser_has_error(http) ? Qtrue : Qfalse;
425
404
  }
@@ -434,7 +413,7 @@ VALUE HttpParser_has_error(VALUE self)
434
413
  VALUE HttpParser_is_finished(VALUE self)
435
414
  {
436
415
  puma_parser *http = NULL;
437
- DATA_GET(self, puma_parser, http);
416
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
438
417
 
439
418
  return puma_parser_is_finished(http) ? Qtrue : Qfalse;
440
419
  }
@@ -450,7 +429,7 @@ VALUE HttpParser_is_finished(VALUE self)
450
429
  VALUE HttpParser_nread(VALUE self)
451
430
  {
452
431
  puma_parser *http = NULL;
453
- DATA_GET(self, puma_parser, http);
432
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
454
433
 
455
434
  return INT2FIX(http->nread);
456
435
  }
@@ -463,15 +442,16 @@ VALUE HttpParser_nread(VALUE self)
463
442
  */
464
443
  VALUE HttpParser_body(VALUE self) {
465
444
  puma_parser *http = NULL;
466
- DATA_GET(self, puma_parser, http);
445
+ DATA_GET(self, puma_parser, &HttpParser_data_type, http);
467
446
 
468
447
  return http->body;
469
448
  }
470
449
 
471
- void Init_io_buffer(VALUE puma);
450
+ #ifdef HAVE_OPENSSL_BIO_H
472
451
  void Init_mini_ssl(VALUE mod);
452
+ #endif
473
453
 
474
- void Init_puma_http11()
454
+ void Init_puma_http11(void)
475
455
  {
476
456
 
477
457
  VALUE mPuma = rb_define_module("Puma");
@@ -498,6 +478,7 @@ void Init_puma_http11()
498
478
  rb_define_method(cHttpParser, "body", HttpParser_body, 0);
499
479
  init_common_fields();
500
480
 
501
- Init_io_buffer(mPuma);
481
+ #ifdef HAVE_OPENSSL_BIO_H
502
482
  Init_mini_ssl(mPuma);
483
+ #endif
503
484
  }
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require 'puma/json_serialization'
2
3
 
3
4
  module Puma
4
5
  module App
@@ -7,58 +8,68 @@ module Puma
7
8
  class Status
8
9
  OK_STATUS = '{ "status": "ok" }'.freeze
9
10
 
10
- def initialize(cli, token = nil)
11
- @cli = cli
11
+ # @param launcher [::Puma::Launcher]
12
+ # @param token [String, nil] the token used for authentication
13
+ #
14
+ def initialize(launcher, token = nil)
15
+ @launcher = launcher
12
16
  @auth_token = token
13
17
  end
14
18
 
19
+ # most commands call methods in `::Puma::Launcher` based on command in
20
+ # `env['PATH_INFO']`
15
21
  def call(env)
16
22
  unless authenticate(env)
17
23
  return rack_response(403, 'Invalid auth token', 'text/plain')
18
24
  end
19
25
 
20
- if env['PATH_INFO'] =~ /\/(gc-stats|stats|thread-backtraces)$/
21
- require 'json'
22
- end
26
+ # resp_type is processed by following case statement, return
27
+ # is a number (status) or a string used as the body of a 200 response
28
+ resp_type =
29
+ case env['PATH_INFO'][/\/([^\/]+)$/, 1]
30
+ when 'stop'
31
+ @launcher.stop ; 200
23
32
 
24
- case env['PATH_INFO']
25
- when /\/stop$/
26
- @cli.stop
27
- rack_response(200, OK_STATUS)
33
+ when 'halt'
34
+ @launcher.halt ; 200
28
35
 
29
- when /\/halt$/
30
- @cli.halt
31
- rack_response(200, OK_STATUS)
36
+ when 'restart'
37
+ @launcher.restart ; 200
32
38
 
33
- when /\/restart$/
34
- @cli.restart
35
- rack_response(200, OK_STATUS)
39
+ when 'phased-restart'
40
+ @launcher.phased_restart ? 200 : 404
36
41
 
37
- when /\/phased-restart$/
38
- if !@cli.phased_restart
39
- rack_response(404, '{ "error": "phased restart not available" }')
40
- else
41
- rack_response(200, OK_STATUS)
42
- end
42
+ when 'reload-worker-directory'
43
+ @launcher.send(:reload_worker_directory) ? 200 : 404
43
44
 
44
- when /\/reload-worker-directory$/
45
- if !@cli.send(:reload_worker_directory)
46
- rack_response(404, '{ "error": "reload_worker_directory not available" }')
47
- else
48
- rack_response(200, OK_STATUS)
49
- end
45
+ when 'gc'
46
+ GC.start ; 200
50
47
 
51
- when /\/gc$/
52
- GC.start
53
- rack_response(200, OK_STATUS)
48
+ when 'gc-stats'
49
+ Puma::JSONSerialization.generate GC.stat
54
50
 
55
- when /\/gc-stats$/
56
- rack_response(200, GC.stat.to_json)
51
+ when 'stats'
52
+ Puma::JSONSerialization.generate @launcher.stats
53
+
54
+ when 'thread-backtraces'
55
+ backtraces = []
56
+ @launcher.thread_status do |name, backtrace|
57
+ backtraces << { name: name, backtrace: backtrace }
58
+ end
59
+ Puma::JSONSerialization.generate backtraces
60
+
61
+ else
62
+ return rack_response(404, "Unsupported action", 'text/plain')
63
+ end
57
64
 
58
- when /\/stats$/
59
- rack_response(200, @cli.stats)
60
- else
61
- rack_response 404, "Unsupported action", 'text/plain'
65
+ case resp_type
66
+ when String
67
+ rack_response 200, resp_type
68
+ when 200
69
+ rack_response 200, OK_STATUS
70
+ when 404
71
+ str = env['PATH_INFO'][/\/(\S+)/, 1].tr '-', '_'
72
+ rack_response 404, "{ \"error\": \"#{str} not available\" }"
62
73
  end
63
74
  end
64
75