puma 6.4.1 → 7.2.1

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +407 -8
  3. data/README.md +109 -49
  4. data/docs/deployment.md +58 -23
  5. data/docs/fork_worker.md +11 -1
  6. data/docs/java_options.md +54 -0
  7. data/docs/jungle/README.md +1 -1
  8. data/docs/kubernetes.md +11 -16
  9. data/docs/plugins.md +6 -2
  10. data/docs/restart.md +2 -2
  11. data/docs/signals.md +21 -21
  12. data/docs/stats.md +11 -5
  13. data/docs/systemd.md +14 -5
  14. data/ext/puma_http11/extconf.rb +20 -32
  15. data/ext/puma_http11/mini_ssl.c +29 -9
  16. data/ext/puma_http11/org/jruby/puma/Http11.java +40 -9
  17. data/ext/puma_http11/puma_http11.c +125 -118
  18. data/lib/puma/app/status.rb +11 -3
  19. data/lib/puma/binder.rb +21 -11
  20. data/lib/puma/cli.rb +10 -8
  21. data/lib/puma/client.rb +183 -83
  22. data/lib/puma/cluster/worker.rb +24 -21
  23. data/lib/puma/cluster/worker_handle.rb +38 -8
  24. data/lib/puma/cluster.rb +73 -47
  25. data/lib/puma/cluster_accept_loop_delay.rb +91 -0
  26. data/lib/puma/commonlogger.rb +3 -3
  27. data/lib/puma/configuration.rb +131 -60
  28. data/lib/puma/const.rb +31 -12
  29. data/lib/puma/control_cli.rb +10 -6
  30. data/lib/puma/detect.rb +2 -0
  31. data/lib/puma/dsl.rb +411 -121
  32. data/lib/puma/error_logger.rb +7 -5
  33. data/lib/puma/events.rb +25 -10
  34. data/lib/puma/io_buffer.rb +8 -4
  35. data/lib/puma/jruby_restart.rb +0 -16
  36. data/lib/puma/launcher/bundle_pruner.rb +1 -1
  37. data/lib/puma/launcher.rb +73 -55
  38. data/lib/puma/log_writer.rb +9 -9
  39. data/lib/puma/minissl/context_builder.rb +1 -0
  40. data/lib/puma/minissl.rb +1 -1
  41. data/lib/puma/null_io.rb +26 -0
  42. data/lib/puma/plugin/systemd.rb +3 -3
  43. data/lib/puma/rack/urlmap.rb +1 -1
  44. data/lib/puma/reactor.rb +19 -13
  45. data/lib/puma/request.rb +71 -39
  46. data/lib/puma/runner.rb +15 -17
  47. data/lib/puma/sd_notify.rb +1 -4
  48. data/lib/puma/server.rb +134 -73
  49. data/lib/puma/single.rb +7 -4
  50. data/lib/puma/state_file.rb +3 -2
  51. data/lib/puma/thread_pool.rb +57 -80
  52. data/lib/puma/util.rb +0 -7
  53. data/lib/puma.rb +10 -0
  54. data/lib/rack/handler/puma.rb +10 -7
  55. data/tools/Dockerfile +15 -5
  56. metadata +14 -15
  57. data/ext/puma_http11/ext_help.h +0 -15
@@ -7,25 +7,13 @@
7
7
  #define RSTRING_NOT_MODIFIED 1
8
8
 
9
9
  #include "ruby.h"
10
- #include "ext_help.h"
10
+ #include "ruby/encoding.h"
11
11
  #include <assert.h>
12
12
  #include <string.h>
13
13
  #include <ctype.h>
14
14
  #include "http11_parser.h"
15
15
 
16
- #ifndef MANAGED_STRINGS
17
-
18
- #ifndef RSTRING_PTR
19
- #define RSTRING_PTR(s) (RSTRING(s)->ptr)
20
- #endif
21
- #ifndef RSTRING_LEN
22
- #define RSTRING_LEN(s) (RSTRING(s)->len)
23
- #endif
24
-
25
- #define rb_extract_chars(e, sz) (*sz = RSTRING_LEN(e), RSTRING_PTR(e))
26
- #define rb_free_chars(e) /* nothing */
27
-
28
- #endif
16
+ #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
29
17
 
30
18
  static VALUE eHttpParserError;
31
19
 
@@ -48,8 +36,11 @@ static VALUE global_request_path;
48
36
  #define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, MAX_##N##_LENGTH_ERR, len); }
49
37
 
50
38
  /** Defines global strings in the init method. */
51
- #define DEF_GLOBAL(N, val) global_##N = rb_str_new2(val); rb_global_variable(&global_##N)
52
-
39
+ static inline void DEF_GLOBAL(VALUE *var, const char *cstr)
40
+ {
41
+ rb_global_variable(var);
42
+ *var = rb_enc_interned_str_cstr(cstr, rb_utf8_encoding());
43
+ }
53
44
 
54
45
  /* Defines the maximum allowed lengths for various input elements.*/
55
46
  #ifndef PUMA_REQUEST_URI_MAX_LENGTH
@@ -73,10 +64,10 @@ DEF_MAX_LENGTH(QUERY_STRING, PUMA_QUERY_STRING_MAX_LENGTH);
73
64
  DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
74
65
 
75
66
  struct common_field {
76
- const size_t len;
77
- const char *name;
67
+ const size_t len;
68
+ const char *name;
78
69
  int raw;
79
- VALUE value;
70
+ VALUE value;
80
71
  };
81
72
 
82
73
  /*
@@ -87,42 +78,42 @@ struct common_field {
87
78
  static struct common_field common_http_fields[] = {
88
79
  # define f(N) { (sizeof(N) - 1), N, 0, Qnil }
89
80
  # define fr(N) { (sizeof(N) - 1), N, 1, Qnil }
90
- f("ACCEPT"),
91
- f("ACCEPT_CHARSET"),
92
- f("ACCEPT_ENCODING"),
93
- f("ACCEPT_LANGUAGE"),
94
- f("ALLOW"),
95
- f("AUTHORIZATION"),
96
- f("CACHE_CONTROL"),
97
- f("CONNECTION"),
98
- f("CONTENT_ENCODING"),
99
- fr("CONTENT_LENGTH"),
100
- fr("CONTENT_TYPE"),
101
- f("COOKIE"),
102
- f("DATE"),
103
- f("EXPECT"),
104
- f("FROM"),
105
- f("HOST"),
106
- f("IF_MATCH"),
107
- f("IF_MODIFIED_SINCE"),
108
- f("IF_NONE_MATCH"),
109
- f("IF_RANGE"),
110
- f("IF_UNMODIFIED_SINCE"),
111
- f("KEEP_ALIVE"), /* Firefox sends this */
112
- f("MAX_FORWARDS"),
113
- f("PRAGMA"),
114
- f("PROXY_AUTHORIZATION"),
115
- f("RANGE"),
116
- f("REFERER"),
117
- f("TE"),
118
- f("TRAILER"),
119
- f("TRANSFER_ENCODING"),
120
- f("UPGRADE"),
121
- f("USER_AGENT"),
122
- f("VIA"),
123
- f("X_FORWARDED_FOR"), /* common for proxies */
124
- f("X_REAL_IP"), /* common for proxies */
125
- f("WARNING")
81
+ f("ACCEPT"),
82
+ f("ACCEPT_CHARSET"),
83
+ f("ACCEPT_ENCODING"),
84
+ f("ACCEPT_LANGUAGE"),
85
+ f("ALLOW"),
86
+ f("AUTHORIZATION"),
87
+ f("CACHE_CONTROL"),
88
+ f("CONNECTION"),
89
+ f("CONTENT_ENCODING"),
90
+ fr("CONTENT_LENGTH"),
91
+ fr("CONTENT_TYPE"),
92
+ f("COOKIE"),
93
+ f("DATE"),
94
+ f("EXPECT"),
95
+ f("FROM"),
96
+ f("HOST"),
97
+ f("IF_MATCH"),
98
+ f("IF_MODIFIED_SINCE"),
99
+ f("IF_NONE_MATCH"),
100
+ f("IF_RANGE"),
101
+ f("IF_UNMODIFIED_SINCE"),
102
+ f("KEEP_ALIVE"), /* Firefox sends this */
103
+ f("MAX_FORWARDS"),
104
+ f("PRAGMA"),
105
+ f("PROXY_AUTHORIZATION"),
106
+ f("RANGE"),
107
+ f("REFERER"),
108
+ f("TE"),
109
+ f("TRAILER"),
110
+ f("TRANSFER_ENCODING"),
111
+ f("UPGRADE"),
112
+ f("USER_AGENT"),
113
+ f("VIA"),
114
+ f("X_FORWARDED_FOR"), /* common for proxies */
115
+ f("X_REAL_IP"), /* common for proxies */
116
+ f("WARNING")
126
117
  # undef f
127
118
  };
128
119
 
@@ -134,13 +125,13 @@ static void init_common_fields(void)
134
125
  memcpy(tmp, HTTP_PREFIX, HTTP_PREFIX_LEN);
135
126
 
136
127
  for(i = 0; i < ARRAY_SIZE(common_http_fields); cf++, i++) {
128
+ rb_global_variable(&cf->value);
137
129
  if(cf->raw) {
138
- cf->value = rb_str_new(cf->name, cf->len);
130
+ cf->value = rb_enc_interned_str(cf->name, cf->len, rb_utf8_encoding());
139
131
  } else {
140
132
  memcpy(tmp + HTTP_PREFIX_LEN, cf->name, cf->len + 1);
141
- cf->value = rb_str_new(tmp, HTTP_PREFIX_LEN + cf->len);
133
+ cf->value = rb_enc_interned_str(tmp, HTTP_PREFIX_LEN + cf->len, rb_utf8_encoding());
142
134
  }
143
- rb_global_variable(&cf->value);
144
135
  }
145
136
  }
146
137
 
@@ -155,7 +146,11 @@ static VALUE find_common_field_value(const char *field, size_t flen)
155
146
  return Qnil;
156
147
  }
157
148
 
158
- void http_field(puma_parser* hp, const char *field, size_t flen,
149
+ static int is_ows(const char c) {
150
+ return c == ' ' || c == '\t';
151
+ }
152
+
153
+ static void http_field(puma_parser* hp, const char *field, size_t flen,
159
154
  const char *value, size_t vlen)
160
155
  {
161
156
  VALUE f = Qnil;
@@ -178,10 +173,14 @@ void http_field(puma_parser* hp, const char *field, size_t flen,
178
173
  memcpy(hp->buf, HTTP_PREFIX, HTTP_PREFIX_LEN);
179
174
  memcpy(hp->buf + HTTP_PREFIX_LEN, field, flen);
180
175
 
181
- f = rb_str_new(hp->buf, new_size);
176
+ f = rb_enc_interned_str(hp->buf, new_size, rb_utf8_encoding());
182
177
  }
183
178
 
184
- while (vlen > 0 && isspace(value[vlen - 1])) vlen--;
179
+ while (vlen > 0 && is_ows(value[vlen - 1])) vlen--;
180
+ while (vlen > 0 && is_ows(value[0])) {
181
+ vlen--;
182
+ value++;
183
+ }
185
184
 
186
185
  /* check for duplicate header */
187
186
  v = rb_hash_aref(hp->request, f);
@@ -196,7 +195,7 @@ void http_field(puma_parser* hp, const char *field, size_t flen,
196
195
  }
197
196
  }
198
197
 
199
- void request_method(puma_parser* hp, const char *at, size_t length)
198
+ static void request_method(puma_parser* hp, const char *at, size_t length)
200
199
  {
201
200
  VALUE val = Qnil;
202
201
 
@@ -204,7 +203,7 @@ void request_method(puma_parser* hp, const char *at, size_t length)
204
203
  rb_hash_aset(hp->request, global_request_method, val);
205
204
  }
206
205
 
207
- void request_uri(puma_parser* hp, const char *at, size_t length)
206
+ static void request_uri(puma_parser* hp, const char *at, size_t length)
208
207
  {
209
208
  VALUE val = Qnil;
210
209
 
@@ -214,7 +213,7 @@ void request_uri(puma_parser* hp, const char *at, size_t length)
214
213
  rb_hash_aset(hp->request, global_request_uri, val);
215
214
  }
216
215
 
217
- void fragment(puma_parser* hp, const char *at, size_t length)
216
+ static void fragment(puma_parser* hp, const char *at, size_t length)
218
217
  {
219
218
  VALUE val = Qnil;
220
219
 
@@ -224,7 +223,7 @@ void fragment(puma_parser* hp, const char *at, size_t length)
224
223
  rb_hash_aset(hp->request, global_fragment, val);
225
224
  }
226
225
 
227
- void request_path(puma_parser* hp, const char *at, size_t length)
226
+ static void request_path(puma_parser* hp, const char *at, size_t length)
228
227
  {
229
228
  VALUE val = Qnil;
230
229
 
@@ -234,7 +233,7 @@ void request_path(puma_parser* hp, const char *at, size_t length)
234
233
  rb_hash_aset(hp->request, global_request_path, val);
235
234
  }
236
235
 
237
- void query_string(puma_parser* hp, const char *at, size_t length)
236
+ static void query_string(puma_parser* hp, const char *at, size_t length)
238
237
  {
239
238
  VALUE val = Qnil;
240
239
 
@@ -244,7 +243,7 @@ void query_string(puma_parser* hp, const char *at, size_t length)
244
243
  rb_hash_aset(hp->request, global_query_string, val);
245
244
  }
246
245
 
247
- void server_protocol(puma_parser* hp, const char *at, size_t length)
246
+ static void server_protocol(puma_parser* hp, const char *at, size_t length)
248
247
  {
249
248
  VALUE val = rb_str_new(at, length);
250
249
  rb_hash_aset(hp->request, global_server_protocol, val);
@@ -253,36 +252,42 @@ void server_protocol(puma_parser* hp, const char *at, size_t length)
253
252
  /** Finalizes the request header to have a bunch of stuff that's
254
253
  needed. */
255
254
 
256
- void header_done(puma_parser* hp, const char *at, size_t length)
255
+ static void header_done(puma_parser* hp, const char *at, size_t length)
257
256
  {
258
257
  hp->body = rb_str_new(at, length);
259
258
  }
260
259
 
261
260
 
262
- void HttpParser_free(void *data) {
263
- TRACE();
261
+ static void HttpParser_mark(void *ptr) {
262
+ puma_parser *hp = ptr;
263
+ rb_gc_mark_movable(hp->request);
264
+ rb_gc_mark_movable(hp->body);
265
+ }
264
266
 
265
- if(data) {
266
- xfree(data);
267
- }
267
+ static size_t HttpParser_size(const void *ptr) {
268
+ return sizeof(puma_parser);
268
269
  }
269
270
 
270
- void HttpParser_mark(void *ptr) {
271
+ static void HttpParser_compact(void *ptr) {
271
272
  puma_parser *hp = ptr;
272
- if(hp->request) rb_gc_mark(hp->request);
273
- if(hp->body) rb_gc_mark(hp->body);
273
+ hp->request = rb_gc_location(hp->request);
274
+ hp->body = rb_gc_location(hp->body);
274
275
  }
275
276
 
276
- const rb_data_type_t HttpParser_data_type = {
277
- "HttpParser",
278
- { HttpParser_mark, HttpParser_free, 0 },
279
- 0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
277
+ static const rb_data_type_t HttpParser_data_type = {
278
+ .wrap_struct_name = "Puma::HttpParser",
279
+ .function = {
280
+ .dmark = HttpParser_mark,
281
+ .dfree = RUBY_TYPED_DEFAULT_FREE,
282
+ .dsize = HttpParser_size,
283
+ .dcompact = HttpParser_compact,
284
+ },
285
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY,
280
286
  };
281
287
 
282
- VALUE HttpParser_alloc(VALUE klass)
288
+ static VALUE HttpParser_alloc(VALUE klass)
283
289
  {
284
290
  puma_parser *hp = ALLOC_N(puma_parser, 1);
285
- TRACE();
286
291
  hp->http_field = http_field;
287
292
  hp->request_method = request_method;
288
293
  hp->request_uri = request_uri;
@@ -298,16 +303,25 @@ VALUE HttpParser_alloc(VALUE klass)
298
303
  return TypedData_Wrap_Struct(klass, &HttpParser_data_type, hp);
299
304
  }
300
305
 
306
+ static inline puma_parser *HttpParser_unwrap(VALUE self)
307
+ {
308
+ puma_parser *http;
309
+ TypedData_Get_Struct(self, puma_parser, &HttpParser_data_type, http);
310
+ if (http == NULL) {
311
+ rb_raise(rb_eArgError, "%s", "NULL http_parser found");
312
+ }
313
+ return http;
314
+ }
315
+
301
316
  /**
302
317
  * call-seq:
303
318
  * parser.new -> parser
304
319
  *
305
320
  * Creates a new parser.
306
321
  */
307
- VALUE HttpParser_init(VALUE self)
322
+ static VALUE HttpParser_init(VALUE self)
308
323
  {
309
- puma_parser *http = NULL;
310
- DATA_GET(self, puma_parser, &HttpParser_data_type, http);
324
+ puma_parser *http = HttpParser_unwrap(self);
311
325
  puma_parser_init(http);
312
326
 
313
327
  return self;
@@ -321,10 +335,9 @@ VALUE HttpParser_init(VALUE self)
321
335
  * Resets the parser to it's initial state so that you can reuse it
322
336
  * rather than making new ones.
323
337
  */
324
- VALUE HttpParser_reset(VALUE self)
338
+ static VALUE HttpParser_reset(VALUE self)
325
339
  {
326
- puma_parser *http = NULL;
327
- DATA_GET(self, puma_parser, &HttpParser_data_type, http);
340
+ puma_parser *http = HttpParser_unwrap(self);
328
341
  puma_parser_init(http);
329
342
 
330
343
  return Qnil;
@@ -338,10 +351,9 @@ VALUE HttpParser_reset(VALUE self)
338
351
  * Finishes a parser early which could put in a "good" or bad state.
339
352
  * You should call reset after finish it or bad things will happen.
340
353
  */
341
- VALUE HttpParser_finish(VALUE self)
354
+ static VALUE HttpParser_finish(VALUE self)
342
355
  {
343
- puma_parser *http = NULL;
344
- DATA_GET(self, puma_parser, &HttpParser_data_type, http);
356
+ puma_parser *http = HttpParser_unwrap(self);
345
357
  puma_parser_finish(http);
346
358
 
347
359
  return puma_parser_is_finished(http) ? Qtrue : Qfalse;
@@ -365,26 +377,22 @@ VALUE HttpParser_finish(VALUE self)
365
377
  * the parsing from that position. It needs all of the original data as well
366
378
  * so you have to append to the data buffer as you read.
367
379
  */
368
- VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
380
+ static VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
369
381
  {
370
- puma_parser *http = NULL;
382
+ puma_parser *http = HttpParser_unwrap(self);
371
383
  int from = 0;
372
384
  char *dptr = NULL;
373
385
  long dlen = 0;
374
386
 
375
- DATA_GET(self, puma_parser, &HttpParser_data_type, http);
376
-
377
387
  from = FIX2INT(start);
378
- dptr = rb_extract_chars(data, &dlen);
388
+ RSTRING_GETMEM(data, dptr, dlen);
379
389
 
380
390
  if(from >= dlen) {
381
- rb_free_chars(dptr);
382
391
  rb_raise(eHttpParserError, "%s", "Requested start is after data buffer end.");
383
392
  } else {
384
393
  http->request = req_hash;
385
394
  puma_parser_execute(http, dptr, dlen, from);
386
395
 
387
- rb_free_chars(dptr);
388
396
  VALIDATE_MAX_LENGTH(puma_parser_nread(http), HEADER);
389
397
 
390
398
  if(puma_parser_has_error(http)) {
@@ -403,10 +411,9 @@ VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
403
411
  *
404
412
  * Tells you whether the parser is in an error state.
405
413
  */
406
- VALUE HttpParser_has_error(VALUE self)
414
+ static VALUE HttpParser_has_error(VALUE self)
407
415
  {
408
- puma_parser *http = NULL;
409
- DATA_GET(self, puma_parser, &HttpParser_data_type, http);
416
+ puma_parser *http = HttpParser_unwrap(self);
410
417
 
411
418
  return puma_parser_has_error(http) ? Qtrue : Qfalse;
412
419
  }
@@ -418,10 +425,9 @@ VALUE HttpParser_has_error(VALUE self)
418
425
  *
419
426
  * Tells you whether the parser is finished or not and in a good state.
420
427
  */
421
- VALUE HttpParser_is_finished(VALUE self)
428
+ static VALUE HttpParser_is_finished(VALUE self)
422
429
  {
423
- puma_parser *http = NULL;
424
- DATA_GET(self, puma_parser, &HttpParser_data_type, http);
430
+ puma_parser *http = HttpParser_unwrap(self);
425
431
 
426
432
  return puma_parser_is_finished(http) ? Qtrue : Qfalse;
427
433
  }
@@ -434,10 +440,9 @@ VALUE HttpParser_is_finished(VALUE self)
434
440
  * Returns the amount of data processed so far during this processing cycle. It is
435
441
  * set to 0 on initialize or reset calls and is incremented each time execute is called.
436
442
  */
437
- VALUE HttpParser_nread(VALUE self)
443
+ static VALUE HttpParser_nread(VALUE self)
438
444
  {
439
- puma_parser *http = NULL;
440
- DATA_GET(self, puma_parser, &HttpParser_data_type, http);
445
+ puma_parser *http = HttpParser_unwrap(self);
441
446
 
442
447
  return INT2FIX(http->nread);
443
448
  }
@@ -448,9 +453,8 @@ VALUE HttpParser_nread(VALUE self)
448
453
  *
449
454
  * If the request included a body, returns it.
450
455
  */
451
- VALUE HttpParser_body(VALUE self) {
452
- puma_parser *http = NULL;
453
- DATA_GET(self, puma_parser, &HttpParser_data_type, http);
456
+ static VALUE HttpParser_body(VALUE self) {
457
+ puma_parser *http = HttpParser_unwrap(self);
454
458
 
455
459
  return http->body;
456
460
  }
@@ -459,21 +463,24 @@ VALUE HttpParser_body(VALUE self) {
459
463
  void Init_mini_ssl(VALUE mod);
460
464
  #endif
461
465
 
462
- void Init_puma_http11(void)
466
+ RUBY_FUNC_EXPORTED void Init_puma_http11(void)
463
467
  {
468
+ #ifdef HAVE_RB_EXT_RACTOR_SAFE
469
+ rb_ext_ractor_safe(true);
470
+ #endif
464
471
 
465
472
  VALUE mPuma = rb_define_module("Puma");
466
473
  VALUE cHttpParser = rb_define_class_under(mPuma, "HttpParser", rb_cObject);
467
474
 
468
- DEF_GLOBAL(request_method, "REQUEST_METHOD");
469
- DEF_GLOBAL(request_uri, "REQUEST_URI");
470
- DEF_GLOBAL(fragment, "FRAGMENT");
471
- DEF_GLOBAL(query_string, "QUERY_STRING");
472
- DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
473
- DEF_GLOBAL(request_path, "REQUEST_PATH");
475
+ DEF_GLOBAL(&global_request_method, "REQUEST_METHOD");
476
+ DEF_GLOBAL(&global_request_uri, "REQUEST_URI");
477
+ DEF_GLOBAL(&global_fragment, "FRAGMENT");
478
+ DEF_GLOBAL(&global_query_string, "QUERY_STRING");
479
+ DEF_GLOBAL(&global_server_protocol, "SERVER_PROTOCOL");
480
+ DEF_GLOBAL(&global_request_path, "REQUEST_PATH");
474
481
 
475
- eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eIOError);
476
482
  rb_global_variable(&eHttpParserError);
483
+ eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eStandardError);
477
484
 
478
485
  rb_define_alloc_func(cHttpParser, HttpParser_alloc);
479
486
  rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
@@ -7,13 +7,16 @@ module Puma
7
7
  # can respond to.
8
8
  class Status
9
9
  OK_STATUS = '{ "status": "ok" }'.freeze
10
+ READ_ONLY_COMMANDS = %w[gc-stats stats].freeze
10
11
 
11
12
  # @param launcher [::Puma::Launcher]
12
13
  # @param token [String, nil] the token used for authentication
14
+ # @param data_only [Boolean] if true, restrict to read-only data commands
13
15
  #
14
- def initialize(launcher, token = nil)
16
+ def initialize(launcher, token: nil, data_only: false)
15
17
  @launcher = launcher
16
18
  @auth_token = token
19
+ @enabled_commands = READ_ONLY_COMMANDS if data_only
17
20
  end
18
21
 
19
22
  # most commands call methods in `::Puma::Launcher` based on command in
@@ -25,8 +28,13 @@ module Puma
25
28
 
26
29
  # resp_type is processed by following case statement, return
27
30
  # is a number (status) or a string used as the body of a 200 response
31
+ command = env['PATH_INFO'][/\/([^\/]+)$/, 1]
32
+ if @enabled_commands && !@enabled_commands.include?(command)
33
+ return rack_response(404, "Command #{command.inspect} unavailable", 'text/plain')
34
+ end
35
+
28
36
  resp_type =
29
- case env['PATH_INFO'][/\/([^\/]+)$/, 1]
37
+ case command
30
38
  when 'stop'
31
39
  @launcher.stop ; 200
32
40
 
@@ -80,7 +88,7 @@ module Puma
80
88
 
81
89
  def authenticate(env)
82
90
  return true unless @auth_token
83
- env['QUERY_STRING'].to_s.split('&;').include? "token=#{@auth_token}"
91
+ env['QUERY_STRING'].to_s.split(/[&;]/).include? "token=#{@auth_token}"
84
92
  end
85
93
 
86
94
  def rack_response(status, body, content_type='application/json')
data/lib/puma/binder.rb CHANGED
@@ -5,7 +5,6 @@ require 'socket'
5
5
 
6
6
  require_relative 'const'
7
7
  require_relative 'util'
8
- require_relative 'configuration'
9
8
 
10
9
  module Puma
11
10
 
@@ -19,22 +18,23 @@ module Puma
19
18
 
20
19
  RACK_VERSION = [1,6].freeze
21
20
 
22
- def initialize(log_writer, conf = Configuration.new)
21
+ def initialize(log_writer, options, env: ENV)
23
22
  @log_writer = log_writer
24
- @conf = conf
23
+ @options = options
25
24
  @listeners = []
26
25
  @inherited_fds = {}
27
26
  @activated_sockets = {}
28
27
  @unix_paths = []
28
+ @env = env
29
29
 
30
30
  @proto_env = {
31
31
  "rack.version".freeze => RACK_VERSION,
32
32
  "rack.errors".freeze => log_writer.stderr,
33
- "rack.multithread".freeze => conf.options[:max_threads] > 1,
34
- "rack.multiprocess".freeze => conf.options[:workers] >= 1,
33
+ "rack.multithread".freeze => options[:max_threads] > 1,
34
+ "rack.multiprocess".freeze => options[:workers] >= 1,
35
35
  "rack.run_once".freeze => false,
36
- RACK_URL_SCHEME => conf.options[:rack_url_scheme],
37
- "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
36
+ RACK_URL_SCHEME => options[:rack_url_scheme],
37
+ "SCRIPT_NAME".freeze => env['SCRIPT_NAME'] || "",
38
38
 
39
39
  # I'd like to set a default CONTENT_TYPE here but some things
40
40
  # depend on their not being a default set and inferring
@@ -43,7 +43,10 @@ module Puma
43
43
 
44
44
  "QUERY_STRING".freeze => "",
45
45
  SERVER_SOFTWARE => PUMA_SERVER_STRING,
46
- GATEWAY_INTERFACE => CGI_VER
46
+ GATEWAY_INTERFACE => CGI_VER,
47
+
48
+ RACK_AFTER_REPLY => nil,
49
+ RACK_RESPONSE_FINISHED => nil,
47
50
  }
48
51
 
49
52
  @envs = {}
@@ -87,7 +90,7 @@ module Puma
87
90
  # @version 5.0.0
88
91
  #
89
92
  def create_activated_fds(env_hash)
90
- @log_writer.debug "ENV['LISTEN_FDS'] #{ENV['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
93
+ @log_writer.debug "ENV['LISTEN_FDS'] #{@env['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
91
94
  return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
92
95
  env_hash['LISTEN_FDS'].to_i.times do |index|
93
96
  sock = TCPServer.for_fd(socket_activation_fd(index))
@@ -141,7 +144,14 @@ module Puma
141
144
  end
142
145
  end
143
146
 
147
+ def before_parse(&block)
148
+ @before_parse ||= []
149
+ @before_parse << block if block
150
+ @before_parse
151
+ end
152
+
144
153
  def parse(binds, log_writer = nil, log_msg = 'Listening')
154
+ before_parse.each(&:call)
145
155
  log_writer ||= @log_writer
146
156
  binds.each do |str|
147
157
  uri = URI.parse str
@@ -183,7 +193,7 @@ module Puma
183
193
  io = inherit_unix_listener path, fd
184
194
  log_writer.log "* Inherited #{str}"
185
195
  elsif sock = @activated_sockets.delete([ :unix, path ]) ||
186
- @activated_sockets.delete([ :unix, File.realdirpath(path) ])
196
+ !abstract && @activated_sockets.delete([ :unix, File.realdirpath(path) ])
187
197
  @unix_paths << path unless abstract || File.exist?(path)
188
198
  io = inherit_unix_listener path, sock
189
199
  log_writer.log "* Activated #{str}"
@@ -235,7 +245,7 @@ module Puma
235
245
  cert_key.each do |v|
236
246
  if params[v]&.start_with?('store:')
237
247
  index = Integer(params.delete(v).split('store:').last)
238
- params["#{v}_pem"] = @conf.options[:store][index]
248
+ params["#{v}_pem"] = @options[:store][index]
239
249
  end
240
250
  end
241
251
  MiniSSL::ContextBuilder.new(params, @log_writer).context
data/lib/puma/cli.rb CHANGED
@@ -24,7 +24,7 @@ module Puma
24
24
  # Create a new CLI object using +argv+ as the command line
25
25
  # arguments.
26
26
  #
27
- def initialize(argv, log_writer = LogWriter.stdio, events = Events.new)
27
+ def initialize(argv, log_writer = LogWriter.stdio, events = Events.new, env: ENV)
28
28
  @debug = false
29
29
  @argv = argv.dup
30
30
  @log_writer = log_writer
@@ -39,10 +39,8 @@ module Puma
39
39
  @control_url = nil
40
40
  @control_options = {}
41
41
 
42
- setup_options
43
-
44
42
  begin
45
- @parser.parse! @argv
43
+ setup_options env
46
44
 
47
45
  if file = @argv.shift
48
46
  @conf.configure do |user_config, file_config|
@@ -63,7 +61,7 @@ module Puma
63
61
  end
64
62
  end
65
63
 
66
- @launcher = Puma::Launcher.new(@conf, :log_writer => @log_writer, :events => @events, :argv => argv)
64
+ @launcher = Puma::Launcher.new(@conf, env: ENV, log_writer: @log_writer, events: @events, argv: argv)
67
65
  end
68
66
 
69
67
  attr_reader :launcher
@@ -92,8 +90,8 @@ module Puma
92
90
  # Build the OptionParser object to handle the available options.
93
91
  #
94
92
 
95
- def setup_options
96
- @conf = Configuration.new({}, {events: @events}) do |user_config, file_config|
93
+ def setup_options(env = ENV)
94
+ @conf = Configuration.new({}, { events: @events }, env) do |user_config, file_config|
97
95
  @parser = OptionParser.new do |o|
98
96
  o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
99
97
  user_config.bind arg
@@ -157,6 +155,10 @@ module Puma
157
155
  user_config.pidfile arg
158
156
  end
159
157
 
158
+ o.on "--plugin PLUGIN", "Load the given PLUGIN. Can be used multiple times to load multiple plugins." do |arg|
159
+ user_config.plugin arg
160
+ end
161
+
160
162
  o.on "--preload", "Preload the app. Cluster mode only" do
161
163
  user_config.preload_app!
162
164
  end
@@ -236,7 +238,7 @@ module Puma
236
238
  $stdout.puts o
237
239
  exit 0
238
240
  end
239
- end
241
+ end.parse! @argv
240
242
  end
241
243
  end
242
244
  end