puma 4.3.12 → 5.6.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1526 -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/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/jungle/README.md +9 -0
  14. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  15. data/{tools → docs}/jungle/rc.d/puma +2 -2
  16. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  17. data/docs/kubernetes.md +66 -0
  18. data/docs/nginx.md +1 -1
  19. data/docs/plugins.md +15 -15
  20. data/docs/rails_dev_mode.md +28 -0
  21. data/docs/restart.md +46 -23
  22. data/docs/signals.md +13 -11
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +85 -128
  25. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  26. data/ext/puma_http11/ext_help.h +1 -1
  27. data/ext/puma_http11/extconf.rb +44 -10
  28. data/ext/puma_http11/http11_parser.c +45 -47
  29. data/ext/puma_http11/http11_parser.h +1 -1
  30. data/ext/puma_http11/http11_parser.java.rl +1 -1
  31. data/ext/puma_http11/http11_parser.rl +1 -1
  32. data/ext/puma_http11/http11_parser_common.rl +0 -0
  33. data/ext/puma_http11/mini_ssl.c +225 -89
  34. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11.java +5 -3
  36. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +3 -5
  37. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +109 -67
  38. data/ext/puma_http11/puma_http11.c +32 -51
  39. data/lib/puma/app/status.rb +50 -36
  40. data/lib/puma/binder.rb +225 -106
  41. data/lib/puma/cli.rb +24 -18
  42. data/lib/puma/client.rb +146 -84
  43. data/lib/puma/cluster/worker.rb +173 -0
  44. data/lib/puma/cluster/worker_handle.rb +94 -0
  45. data/lib/puma/cluster.rb +212 -220
  46. data/lib/puma/commonlogger.rb +2 -2
  47. data/lib/puma/configuration.rb +58 -49
  48. data/lib/puma/const.rb +22 -7
  49. data/lib/puma/control_cli.rb +99 -76
  50. data/lib/puma/detect.rb +29 -2
  51. data/lib/puma/dsl.rb +368 -96
  52. data/lib/puma/error_logger.rb +104 -0
  53. data/lib/puma/events.rb +55 -34
  54. data/lib/puma/io_buffer.rb +9 -2
  55. data/lib/puma/jruby_restart.rb +0 -58
  56. data/lib/puma/json_serialization.rb +96 -0
  57. data/lib/puma/launcher.rb +128 -46
  58. data/lib/puma/minissl/context_builder.rb +14 -9
  59. data/lib/puma/minissl.rb +137 -50
  60. data/lib/puma/null_io.rb +18 -1
  61. data/lib/puma/plugin/tmp_restart.rb +0 -0
  62. data/lib/puma/plugin.rb +3 -12
  63. data/lib/puma/queue_close.rb +26 -0
  64. data/lib/puma/rack/builder.rb +1 -5
  65. data/lib/puma/rack/urlmap.rb +0 -0
  66. data/lib/puma/rack_default.rb +0 -0
  67. data/lib/puma/reactor.rb +85 -369
  68. data/lib/puma/request.rb +489 -0
  69. data/lib/puma/runner.rb +46 -61
  70. data/lib/puma/server.rb +292 -763
  71. data/lib/puma/single.rb +9 -65
  72. data/lib/puma/state_file.rb +48 -8
  73. data/lib/puma/systemd.rb +46 -0
  74. data/lib/puma/thread_pool.rb +125 -57
  75. data/lib/puma/util.rb +32 -4
  76. data/lib/puma.rb +48 -0
  77. data/lib/rack/handler/puma.rb +2 -3
  78. data/lib/rack/version_restriction.rb +15 -0
  79. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  80. data/tools/trickletest.rb +0 -0
  81. metadata +29 -24
  82. data/docs/tcp_mode.md +0 -96
  83. data/ext/puma_http11/io_buffer.c +0 -155
  84. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  85. data/lib/puma/accept_nonblock.rb +0 -29
  86. data/lib/puma/tcp_logger.rb +0 -41
  87. data/tools/jungle/README.md +0 -19
  88. data/tools/jungle/init.d/README.md +0 -61
  89. data/tools/jungle/init.d/puma +0 -421
  90. data/tools/jungle/init.d/run-puma +0 -18
  91. data/tools/jungle/upstart/README.md +0 -61
  92. data/tools/jungle/upstart/puma-manager.conf +0 -31
  93. 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);
134
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);
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,17 +266,16 @@ 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
  }
247
- }
248
-
249
- // after each op, run any delegated tasks if needed
250
- if(engine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
251
- Runnable runnable;
252
- while ((runnable = engine.getDelegatedTask()) != null) {
253
- runnable.run();
277
+ if (res.getHandshakeStatus() == HandshakeStatus.FINISHED) {
278
+ handshake = true;
254
279
  }
255
280
  }
256
281
 
@@ -258,7 +283,7 @@ public class MiniSSL extends RubyObject {
258
283
  }
259
284
 
260
285
  @JRubyMethod
261
- public IRubyObject read() throws Exception {
286
+ public IRubyObject read() {
262
287
  try {
263
288
  inboundNetData.flip();
264
289
 
@@ -272,21 +297,30 @@ public class MiniSSL extends RubyObject {
272
297
  HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
273
298
  boolean done = false;
274
299
  while (!done) {
300
+ SSLEngineResult res;
275
301
  switch (handshakeStatus) {
276
302
  case NEED_WRAP:
277
- doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
303
+ res = doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
304
+ handshakeStatus = res.getHandshakeStatus();
278
305
  break;
279
306
  case NEED_UNWRAP:
280
- SSLEngineResult res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
307
+ res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
281
308
  if (res.getStatus() == Status.BUFFER_UNDERFLOW) {
282
309
  // need more data before we can shake more hands
283
310
  done = true;
284
311
  }
312
+ handshakeStatus = res.getHandshakeStatus();
313
+ break;
314
+ case NEED_TASK:
315
+ Runnable runnable;
316
+ while ((runnable = engine.getDelegatedTask()) != null) {
317
+ runnable.run();
318
+ }
319
+ handshakeStatus = engine.getHandshakeStatus();
285
320
  break;
286
321
  default:
287
322
  done = true;
288
323
  }
289
- handshakeStatus = engine.getHandshakeStatus();
290
324
  }
291
325
 
292
326
  if (inboundNetData.hasRemaining()) {
@@ -300,55 +334,46 @@ public class MiniSSL extends RubyObject {
300
334
  return getRuntime().getNil();
301
335
  }
302
336
 
303
- RubyString str = getRuntime().newString("");
304
- str.setValue(appDataByteList);
305
- return str;
306
- } catch (Exception e) {
307
- throw getRuntime().newEOFError(e.getMessage());
337
+ return RubyString.newString(getRuntime(), appDataByteList);
338
+ } catch (SSLException e) {
339
+ RaiseException re = getRuntime().newEOFError(e.getMessage());
340
+ re.initCause(e);
341
+ throw re;
308
342
  }
309
343
  }
310
344
 
311
345
  @JRubyMethod
312
346
  public IRubyObject write(IRubyObject arg) {
313
- try {
314
- byte[] bls = arg.convertToString().getBytes();
315
- outboundAppData = new MiniSSLBuffer(bls);
347
+ byte[] bls = arg.convertToString().getBytes();
348
+ outboundAppData = new MiniSSLBuffer(bls);
316
349
 
317
- return getRuntime().newFixnum(bls.length);
318
- } catch (Exception e) {
319
- e.printStackTrace();
320
- throw new RuntimeException(e);
321
- }
350
+ return getRuntime().newFixnum(bls.length);
322
351
  }
323
352
 
324
353
  @JRubyMethod
325
- public IRubyObject extract() throws SSLException {
354
+ public IRubyObject extract(ThreadContext context) {
326
355
  try {
327
356
  ByteList dataByteList = outboundNetData.asByteList();
328
357
  if (dataByteList != null) {
329
- RubyString str = getRuntime().newString("");
330
- str.setValue(dataByteList);
331
- return str;
358
+ return RubyString.newString(context.runtime, dataByteList);
332
359
  }
333
360
 
334
361
  if (!outboundAppData.hasRemaining()) {
335
- return getRuntime().getNil();
362
+ return context.nil;
336
363
  }
337
364
 
338
365
  outboundNetData.clear();
339
366
  doOp(SSLOperation.WRAP, outboundAppData, outboundNetData);
340
367
  dataByteList = outboundNetData.asByteList();
341
368
  if (dataByteList == null) {
342
- return getRuntime().getNil();
369
+ return context.nil;
343
370
  }
344
371
 
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);
372
+ return RubyString.newString(context.runtime, dataByteList);
373
+ } catch (SSLException e) {
374
+ RaiseException ex = context.runtime.newRuntimeError(e.toString());
375
+ ex.initCause(e);
376
+ throw ex;
352
377
  }
353
378
  }
354
379
 
@@ -356,8 +381,25 @@ public class MiniSSL extends RubyObject {
356
381
  public IRubyObject peercert() throws CertificateEncodingException {
357
382
  try {
358
383
  return JavaEmbedUtils.javaToRuby(getRuntime(), engine.getSession().getPeerCertificates()[0].getEncoded());
359
- } catch (SSLPeerUnverifiedException ex) {
384
+ } catch (SSLPeerUnverifiedException e) {
360
385
  return getRuntime().getNil();
361
386
  }
362
387
  }
388
+
389
+ @JRubyMethod(name = "init?")
390
+ public IRubyObject isInit(ThreadContext context) {
391
+ return handshake ? getRuntime().getFalse() : getRuntime().getTrue();
392
+ }
393
+
394
+ @JRubyMethod
395
+ public IRubyObject shutdown() {
396
+ if (closed || engine.isInboundDone() && engine.isOutboundDone()) {
397
+ if (engine.isOutboundDone()) {
398
+ engine.closeOutbound();
399
+ }
400
+ return getRuntime().getTrue();
401
+ } else {
402
+ return getRuntime().getFalse();
403
+ }
404
+ }
363
405
  }
@@ -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,71 @@ 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 'refork'
43
+ @launcher.refork ? 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 'reload-worker-directory'
46
+ @launcher.send(:reload_worker_directory) ? 200 : 404
50
47
 
51
- when /\/gc$/
52
- GC.start
53
- rack_response(200, OK_STATUS)
48
+ when 'gc'
49
+ GC.start ; 200
54
50
 
55
- when /\/gc-stats$/
56
- rack_response(200, GC.stat.to_json)
51
+ when 'gc-stats'
52
+ Puma::JSONSerialization.generate GC.stat
53
+
54
+ when 'stats'
55
+ Puma::JSONSerialization.generate @launcher.stats
56
+
57
+ when 'thread-backtraces'
58
+ backtraces = []
59
+ @launcher.thread_status do |name, backtrace|
60
+ backtraces << { name: name, backtrace: backtrace }
61
+ end
62
+ Puma::JSONSerialization.generate backtraces
63
+
64
+ else
65
+ return rack_response(404, "Unsupported action", 'text/plain')
66
+ end
57
67
 
58
- when /\/stats$/
59
- rack_response(200, @cli.stats)
60
- else
61
- rack_response 404, "Unsupported action", 'text/plain'
68
+ case resp_type
69
+ when String
70
+ rack_response 200, resp_type
71
+ when 200
72
+ rack_response 200, OK_STATUS
73
+ when 404
74
+ str = env['PATH_INFO'][/\/(\S+)/, 1].tr '-', '_'
75
+ rack_response 404, "{ \"error\": \"#{str} not available\" }"
62
76
  end
63
77
  end
64
78