puma 6.4.0 → 6.6.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +235 -18
  3. data/README.md +87 -26
  4. data/docs/fork_worker.md +11 -1
  5. data/docs/java_options.md +54 -0
  6. data/docs/kubernetes.md +1 -1
  7. data/docs/plugins.md +4 -0
  8. data/docs/restart.md +1 -0
  9. data/docs/signals.md +2 -2
  10. data/docs/stats.md +8 -3
  11. data/docs/systemd.md +13 -6
  12. data/ext/puma_http11/extconf.rb +21 -14
  13. data/ext/puma_http11/mini_ssl.c +16 -2
  14. data/ext/puma_http11/org/jruby/puma/Http11.java +31 -8
  15. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +2 -1
  16. data/ext/puma_http11/puma_http11.c +4 -1
  17. data/lib/puma/app/status.rb +1 -1
  18. data/lib/puma/binder.rb +12 -4
  19. data/lib/puma/cli.rb +9 -5
  20. data/lib/puma/client.rb +104 -16
  21. data/lib/puma/cluster/worker.rb +9 -6
  22. data/lib/puma/cluster/worker_handle.rb +4 -5
  23. data/lib/puma/cluster.rb +112 -27
  24. data/lib/puma/configuration.rb +37 -21
  25. data/lib/puma/const.rb +22 -3
  26. data/lib/puma/control_cli.rb +4 -4
  27. data/lib/puma/detect.rb +3 -4
  28. data/lib/puma/dsl.rb +303 -58
  29. data/lib/puma/error_logger.rb +4 -4
  30. data/lib/puma/jruby_restart.rb +0 -16
  31. data/lib/puma/launcher.rb +21 -8
  32. data/lib/puma/log_writer.rb +9 -9
  33. data/lib/puma/minissl/context_builder.rb +3 -0
  34. data/lib/puma/minissl.rb +6 -0
  35. data/lib/puma/null_io.rb +42 -2
  36. data/lib/puma/request.rb +35 -14
  37. data/lib/puma/runner.rb +10 -3
  38. data/lib/puma/sd_notify.rb +1 -4
  39. data/lib/puma/server.rb +53 -21
  40. data/lib/puma/single.rb +1 -1
  41. data/lib/puma/state_file.rb +2 -2
  42. data/lib/puma/thread_pool.rb +14 -2
  43. data/lib/puma/util.rb +1 -1
  44. data/lib/rack/handler/puma.rb +8 -5
  45. data/tools/Dockerfile +5 -3
  46. metadata +10 -12
@@ -0,0 +1,54 @@
1
+ # Java Options
2
+
3
+ `System Properties` or `Environment Variables` can be used to change Puma's
4
+ default configuration for its Java extension. The provided values are evaluated
5
+ during initialization, and changes while running the app have no effect.
6
+ Moreover, default values may be used in case of invalid inputs.
7
+
8
+ ## Supported Options
9
+
10
+ | ENV Name | Default Value | Validation |
11
+ |------------------------------|:-------------:|:------------------------:|
12
+ | PUMA_QUERY_STRING_MAX_LENGTH | 1024 * 10 | Positive natural number |
13
+ | PUMA_REQUEST_PATH_MAX_LENGTH | 8192 | Positive natural number |
14
+ | PUMA_REQUEST_URI_MAX_LENGTH | 1024 * 12 | Positive natural number |
15
+ | PUMA_SKIP_SIGUSR2 | nil | n/a |
16
+
17
+ ## Examples
18
+
19
+ ### Invalid inputs
20
+
21
+ An empty string will be handled as missing, and the default value will be used instead.
22
+ Puma will print an error message for other invalid values.
23
+
24
+ ```
25
+ foo@bar:~/puma$ PUMA_QUERY_STRING_MAX_LENGTH=abc PUMA_REQUEST_PATH_MAX_LENGTH='' PUMA_REQUEST_URI_MAX_LENGTH=0 bundle exec bin/puma test/rackup/hello.ru
26
+
27
+ The value 0 for PUMA_REQUEST_URI_MAX_LENGTH is invalid. Using default value 12288 instead.
28
+ The value abc for PUMA_QUERY_STRING_MAX_LENGTH is invalid. Using default value 10240 instead.
29
+ Puma starting in single mode...
30
+ ```
31
+
32
+ ### Valid inputs
33
+
34
+ ```
35
+ foo@bar:~/puma$ PUMA_REQUEST_PATH_MAX_LENGTH=9 bundle exec bin/puma test/rackup/hello.ru
36
+
37
+ Puma starting in single mode...
38
+ ```
39
+ ```
40
+ foo@bar:~ export path=/123456789 # 10 chars
41
+ foo@bar:~ curl "http://localhost:9292${path}"
42
+
43
+ Puma caught this error: HTTP element REQUEST_PATH is longer than the 9 allowed length. (Puma::HttpParserError)
44
+
45
+ foo@bar:~ export path=/12345678 # 9 chars
46
+ foo@bar:~ curl "http://localhost:9292${path}"
47
+ Hello World
48
+ ```
49
+
50
+ ### Java Flight Recorder Compatibility
51
+
52
+ Unfortunately Java Flight Recorder uses `SIGUSR2` internally. If you wish to
53
+ use JFR, turn off Puma's trapping of `SIGUSR2` by setting the environment variable
54
+ `PUMA_SKIP_SIGUSR2` to any value.
data/docs/kubernetes.md CHANGED
@@ -69,7 +69,7 @@ More discussions and links to relevant articles can be found in https://github.c
69
69
 
70
70
  With containerization, you will have to make a decision about how "big" to make each pod. Should you run 2 pods with 50 workers each? 25 pods, each with 4 workers? 100 pods, with each Puma running in single mode? Each scenario represents the same total amount of capacity (100 Puma processes that can respond to requests), but there are tradeoffs to make.
71
71
 
72
- * Worker counts should be somewhere between 4 and 32 in most cases. You want more than 4 in order to minimize time spent in request queueing for a free Puma worker, but probably less than ~32 because otherwise autoscaling is working in too large of an increment or they probably won't fit very well into your nodes.
72
+ * Worker counts should be somewhere between 4 and 32 in most cases. You want more than 4 in order to minimize time spent in request queueing for a free Puma worker, but probably less than ~32 because otherwise autoscaling is working in too large of an increment or they probably won't fit very well into your nodes. In any queueing system, queue time is proportional to 1/n, where n is the number of things pulling from the queue. Each pod will have its own request queue (i.e., the socket backlog). If you have 4 pods with 1 worker each (4 request queues), wait times are, proportionally, about 4 times higher than if you had 1 pod with 4 workers (1 request queue).
73
73
  * Unless you have a very I/O-heavy application (50%+ time spent waiting on IO), use the default thread count (5 for MRI). Using higher numbers of threads with low I/O wait (<50%) will lead to additional request queueing time (latency!) and additional memory usage.
74
74
  * More processes per pod reduces memory usage per process, because of copy-on-write memory and because the cost of the single master process is "amortized" over more child processes.
75
75
  * Don't run less than 4 processes per pod if you can. Low numbers of processes per pod will lead to high request queueing, which means you will have to run more pods.
data/docs/plugins.md CHANGED
@@ -36,3 +36,7 @@ object that is useful for additional configuration.
36
36
 
37
37
  Public methods in [`Puma::Plugin`](../lib/puma/plugin.rb) are treated as a
38
38
  public API for plugins.
39
+
40
+ ## Binder hooks
41
+
42
+ There's `Puma::Binder#before_parse` method that allows to add proc to run before the body of `Puma::Binder#parse`. Example of usage can be found in [that repository](https://github.com/anchordotdev/puma-acme/blob/v0.1.3/lib/puma/acme/plugin.rb#L97-L118) (`before_parse_hook` could be renamed `before_parse`, making monkey patching of [binder.rb](https://github.com/anchordotdev/puma-acme/blob/v0.1.3/lib/puma/acme/binder.rb) is unnecessary).
data/docs/restart.md CHANGED
@@ -27,6 +27,7 @@ Any of the following will cause a Puma server to perform a hot restart:
27
27
 
28
28
  ### Additional notes
29
29
 
30
+ * The newly started Puma process changes its current working directory to the directory specified by the `directory` option. If `directory` is set to symlink, this is automatically re-evaluated, so this mechanism can be used to upgrade the application.
30
31
  * Only one version of the application is running at a time.
31
32
  * `on_restart` is invoked just before the server shuts down. This can be used to clean up resources (like long-lived database connections) gracefully. Since Ruby 2.0, it is not typically necessary to explicitly close file descriptors on restart. This is because any file descriptor opened by Ruby will have the `FD_CLOEXEC` flag set, meaning that file descriptors are closed on `exec`. `on_restart` is useful, though, if your application needs to perform any more graceful protocol-specific shutdown procedures before closing connections.
32
33
 
data/docs/signals.md CHANGED
@@ -17,13 +17,13 @@ $ ps aux | grep tail
17
17
  schneems 87152 0.0 0.0 2432772 492 s032 S+ 12:46PM 0:00.00 tail -f my.log
18
18
  ```
19
19
 
20
- You can send a signal in Ruby using the [Process module](https://www.ruby-doc.org/core-2.1.1/Process.html#kill-method):
20
+ You can send a signal in Ruby using the [Process module](https://docs.ruby-lang.org/en/master/Process.html#method-c-kill):
21
21
 
22
22
  ```
23
23
  $ irb
24
24
  > puts pid
25
25
  => 87152
26
- Process.detach(pid) # https://ruby-doc.org/core-2.1.1/Process.html#method-c-detach
26
+ Process.detach(pid) # https://docs.ruby-lang.org/en/master/Process.html#method-c-detach
27
27
  Process.kill("TERM", pid)
28
28
  ```
29
29
 
data/docs/stats.md CHANGED
@@ -55,9 +55,14 @@ end
55
55
 
56
56
  When Puma runs in single mode, these stats are available at the top level. When Puma runs in cluster mode, these stats are available within the `worker_status` array in a hash labeled `last_status`, in an array of hashes where one hash represents each worker.
57
57
 
58
- * backlog: requests that are waiting for an available thread to be available. if this is above 0, you need more capacity [always true?]
59
- * running: how many threads are running
60
- * pool_capacity: the number of requests that the server is capable of taking right now. For example, if the number is 5, then it means there are 5 threads sitting idle ready to take a request. If one request comes in, then the value would be 4 until it finishes processing. If the minimum threads allowed is zero, this number will still have a maximum value of the maximum threads allowed.
58
+ * backlog: requests that are waiting for an available thread to be available. if this is frequently above 0, you need more capacity.
59
+ * running: how many threads are spawned. A spawned thread may be busy processing a request or waiting for a new request. If `min_threads` and `max_threads` are set to the same number,
60
+ this will be a never-changing number (other than rare cases when a thread dies, etc).
61
+ * busy_threads: `running` - `how many threads are waiting to receive work` + `how many requests are waiting for a thread to pick them up`.
62
+ this is a "wholistic" stat reflecting the overall current state of work to be done and the capacity to do it.
63
+ * pool_capacity: `how many threads are waiting to receive work` + `max_threads` - `running`. In a typical configuration where `min_threads`
64
+ and `max_threads` are configured to the same number, this is simply `how many threads are waiting to receive work`. This number exists only as a stat
65
+ and is not used for any internal decisions, unlike `busy_theads`, which is usually a more useful stat.
61
66
  * max_threads: the maximum number of threads Puma is configured to spool per worker
62
67
  * requests_count: the number of requests this worker has served since starting
63
68
 
data/docs/systemd.md CHANGED
@@ -51,7 +51,7 @@ ExecStart=/<FULLPATH>/bin/puma -C <YOUR_APP_PATH>/puma.rb
51
51
  # Variant: Rails start.
52
52
  # ExecStart=/<FULLPATH>/bin/puma -C <YOUR_APP_PATH>/config/puma.rb ../config.ru
53
53
 
54
- # Variant: Use `bundle exec --keep-file-descriptors puma` instead of binstub
54
+ # Variant: Use `bundle exec puma` instead of binstub
55
55
  # Variant: Specify directives inline.
56
56
  # ExecStart=/<FULLPATH>/puma -b tcp://0.0.0.0:9292 -b ssl://0.0.0.0:9293?key=key.pem&cert=cert.pem
57
57
 
@@ -72,13 +72,11 @@ systemd and Puma also support socket activation, where systemd opens the
72
72
  listening socket(s) in advance and provides them to the Puma master process on
73
73
  startup. Among other advantages, this keeps listening sockets open across puma
74
74
  restarts and achieves graceful restarts, including when upgraded Puma, and is
75
- compatible with both clustered mode and application preload.
75
+ compatible with both cluster mode and application preload.
76
76
 
77
77
  **Note:** Any wrapper scripts which `exec`, or other indirections in `ExecStart`
78
78
  may result in activated socket file descriptors being closed before reaching the
79
- puma master process. For example, if using `bundle exec`, pass the
80
- `--keep-file-descriptors` flag. `bundle exec` can be avoided by using a `puma`
81
- executable generated by `bundle binstubs puma`. This is tracked in [#1499].
79
+ puma master process.
82
80
 
83
81
  **Note:** Socket activation doesn't currently work on JRuby. This is tracked in
84
82
  [#1367].
@@ -101,9 +99,11 @@ ListenStream=0.0.0.0:9293
101
99
  # ListenStream=/run/puma.sock
102
100
 
103
101
  # Socket options matching Puma defaults
104
- NoDelay=true
105
102
  ReusePort=true
106
103
  Backlog=1024
104
+ # Enable this if you're using Puma with the "low_latency" option, read more in Puma DSL docs and systemd docs:
105
+ # https://www.freedesktop.org/software/systemd/man/latest/systemd.socket.html#NoDelay=
106
+ # NoDelay=true
107
107
 
108
108
  [Install]
109
109
  WantedBy=sockets.target
@@ -241,6 +241,13 @@ cap $stage puma:start --dry-run
241
241
  cap $stage puma:stop --dry-run
242
242
  ~~~~
243
243
 
244
+ ### Disabling Puma Systemd Integration
245
+
246
+ If you would like to disable Puma's systemd integration, for example if you handle it elsewhere
247
+ in your code yourself, simply set the the environment variable `PUMA_SKIP_SYSTEMD` to any value.
248
+
249
+
250
+
244
251
  [Restart]: https://www.freedesktop.org/software/systemd/man/systemd.service.html#Restart=
245
252
  [#1367]: https://github.com/puma/puma/issues/1367
246
253
  [#1499]: https://github.com/puma/puma/issues/1499
@@ -10,11 +10,13 @@ end
10
10
 
11
11
  unless ENV["PUMA_DISABLE_SSL"]
12
12
  # don't use pkg_config('openssl') if '--with-openssl-dir' is used
13
- has_openssl_dir = dir_config('openssl').any?
13
+ has_openssl_dir = dir_config('openssl').any? ||
14
+ RbConfig::CONFIG['configure_args']&.include?('openssl')
15
+
14
16
  found_pkg_config = !has_openssl_dir && pkg_config('openssl')
15
17
 
16
18
  found_ssl = if !$mingw && found_pkg_config
17
- puts 'using OpenSSL pkgconfig (openssl.pc)'
19
+ puts '──── Using OpenSSL pkgconfig (openssl.pc) ────'
18
20
  true
19
21
  elsif have_library('libcrypto', 'BIO_read') && have_library('libssl', 'SSL_CTX_new')
20
22
  true
@@ -29,22 +31,27 @@ unless ENV["PUMA_DISABLE_SSL"]
29
31
  if found_ssl
30
32
  have_header "openssl/bio.h"
31
33
 
32
- # below is yes for 1.0.2 & later
33
- have_func "DTLS_method" , "openssl/ssl.h"
34
- have_func "SSL_CTX_set_session_cache_mode(NULL, 0)", "openssl/ssl.h"
34
+ ssl_h = "openssl/ssl.h".freeze
35
+
36
+ puts "\n──── Below are yes for 1.0.2 & later ────"
37
+ have_func "DTLS_method" , ssl_h
38
+ have_func "SSL_CTX_set_session_cache_mode(NULL, 0)", ssl_h
39
+
40
+ puts "\n──── Below are yes for 1.1.0 & later ────"
41
+ have_func "TLS_server_method" , ssl_h
42
+ have_func "SSL_CTX_set_min_proto_version(NULL, 0)" , ssl_h
35
43
 
36
- # below are yes for 1.1.0 & later
37
- have_func "TLS_server_method" , "openssl/ssl.h"
38
- have_func "SSL_CTX_set_min_proto_version(NULL, 0)" , "openssl/ssl.h"
44
+ puts "\n──── Below is yes for 1.1.0 and later, but isn't documented until 3.0.0 ────"
45
+ # https://github.com/openssl/openssl/blob/OpenSSL_1_1_0/include/openssl/ssl.h#L1159
46
+ have_func "SSL_CTX_set_dh_auto(NULL, 0)" , ssl_h
39
47
 
40
- have_func "X509_STORE_up_ref"
41
- have_func "SSL_CTX_set_ecdh_auto(NULL, 0)" , "openssl/ssl.h"
48
+ puts "\n──── Below is yes for 1.1.1 & later ────"
49
+ have_func "SSL_CTX_set_ciphersuites(NULL, \"\")" , ssl_h
42
50
 
43
- # below exists in 1.1.0 and later, but isn't documented until 3.0.0
44
- have_func "SSL_CTX_set_dh_auto(NULL, 0)" , "openssl/ssl.h"
51
+ puts "\n──── Below is yes for 3.0.0 & later ────"
52
+ have_func "SSL_get1_peer_certificate" , ssl_h
45
53
 
46
- # below is yes for 3.0.0 & later
47
- have_func "SSL_get1_peer_certificate" , "openssl/ssl.h"
54
+ puts ''
48
55
 
49
56
  # Random.bytes available in Ruby 2.5 and later, Random::DEFAULT deprecated in 3.0
50
57
  if Random.respond_to?(:bytes)
@@ -229,7 +229,7 @@ VALUE
229
229
  sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
230
230
  SSL_CTX* ctx;
231
231
  int ssl_options;
232
- VALUE key, cert, ca, verify_mode, ssl_cipher_filter, no_tlsv1, no_tlsv1_1,
232
+ VALUE key, cert, ca, verify_mode, ssl_cipher_filter, ssl_ciphersuites, no_tlsv1, no_tlsv1_1,
233
233
  verification_flags, session_id_bytes, cert_pem, key_pem, key_password_command, key_password;
234
234
  BIO *bio;
235
235
  X509 *x509 = NULL;
@@ -269,6 +269,8 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
269
269
 
270
270
  ssl_cipher_filter = rb_funcall(mini_ssl_ctx, rb_intern_const("ssl_cipher_filter"), 0);
271
271
 
272
+ ssl_ciphersuites = rb_funcall(mini_ssl_ctx, rb_intern_const("ssl_ciphersuites"), 0);
273
+
272
274
  no_tlsv1 = rb_funcall(mini_ssl_ctx, rb_intern_const("no_tlsv1"), 0);
273
275
 
274
276
  no_tlsv1_1 = rb_funcall(mini_ssl_ctx, rb_intern_const("no_tlsv1_1"), 0);
@@ -444,6 +446,14 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
444
446
  SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL@STRENGTH");
445
447
  }
446
448
 
449
+ #if HAVE_SSL_CTX_SET_CIPHERSUITES
450
+ // Only override OpenSSL default ciphersuites if config option is supplied.
451
+ if (!NIL_P(ssl_ciphersuites)) {
452
+ StringValue(ssl_ciphersuites);
453
+ SSL_CTX_set_ciphersuites(ctx, RSTRING_PTR(ssl_ciphersuites));
454
+ }
455
+ #endif
456
+
447
457
  #if OPENSSL_VERSION_NUMBER < 0x10002000L
448
458
  // Remove this case if OpenSSL 1.0.1 (now EOL) support is no longer needed.
449
459
  ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
@@ -546,7 +556,7 @@ NORETURN(void raise_error(SSL* ssl, int result));
546
556
 
547
557
  void raise_error(SSL* ssl, int result) {
548
558
  char buf[512];
549
- char msg[512];
559
+ char msg[768];
550
560
  const char* err_str;
551
561
  int err = errno;
552
562
  int mask = 4095;
@@ -804,6 +814,10 @@ void Init_mini_ssl(VALUE puma) {
804
814
 
805
815
  rb_define_method(eng, "init?", engine_init, 0);
806
816
 
817
+ /* @!attribute [r] peercert
818
+ * Returns `nil` when `MiniSSL::Context#verify_mode` is set to `VERIFY_NONE`.
819
+ * @return [String, nil] DER encoded cert
820
+ */
807
821
  rb_define_method(eng, "peercert", engine_peercert, 0);
808
822
 
809
823
  rb_define_method(eng, "ssl_vers_st", engine_ssl_vers_st, 0);
@@ -26,14 +26,14 @@ public class Http11 extends RubyObject {
26
26
  public final static String MAX_FIELD_NAME_LENGTH_ERR = "HTTP element FIELD_NAME is longer than the 256 allowed length.";
27
27
  public final static int MAX_FIELD_VALUE_LENGTH = 80 * 1024;
28
28
  public final static String MAX_FIELD_VALUE_LENGTH_ERR = "HTTP element FIELD_VALUE is longer than the 81920 allowed length.";
29
- public final static int MAX_REQUEST_URI_LENGTH = 1024 * 12;
30
- public final static String MAX_REQUEST_URI_LENGTH_ERR = "HTTP element REQUEST_URI is longer than the 12288 allowed length.";
29
+ public final static int MAX_REQUEST_URI_LENGTH = getConstLength("PUMA_REQUEST_URI_MAX_LENGTH", 1024 * 12);
30
+ public final static String MAX_REQUEST_URI_LENGTH_ERR = "HTTP element REQUEST_URI is longer than the " + MAX_REQUEST_URI_LENGTH + " allowed length.";
31
31
  public final static int MAX_FRAGMENT_LENGTH = 1024;
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 = 8192;
34
- public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the 8192 allowed length.";
35
- public final static int MAX_QUERY_STRING_LENGTH = 1024 * 10;
36
- public final static String MAX_QUERY_STRING_LENGTH_ERR = "HTTP element QUERY_STRING is longer than the 10240 allowed length.";
32
+ public final static String MAX_FRAGMENT_LENGTH_ERR = "HTTP element FRAGMENT is longer than the 1024 allowed length.";
33
+ public final static int MAX_REQUEST_PATH_LENGTH = getConstLength("PUMA_REQUEST_PATH_MAX_LENGTH", 8192);
34
+ public final static String MAX_REQUEST_PATH_LENGTH_ERR = "HTTP element REQUEST_PATH is longer than the " + MAX_REQUEST_PATH_LENGTH + " allowed length.";
35
+ public final static int MAX_QUERY_STRING_LENGTH = getConstLength("PUMA_QUERY_STRING_MAX_LENGTH", 10 * 1024);
36
+ public final static String MAX_QUERY_STRING_LENGTH_ERR = "HTTP element QUERY_STRING is longer than the " + MAX_QUERY_STRING_LENGTH +" allowed length.";
37
37
  public final static int MAX_HEADER_LENGTH = 1024 * (80 + 32);
38
38
  public final static String MAX_HEADER_LENGTH_ERR = "HTTP element HEADER is longer than the 114688 allowed length.";
39
39
 
@@ -48,6 +48,27 @@ public class Http11 extends RubyObject {
48
48
  public static final ByteList QUERY_STRING_BYTELIST = new ByteList(ByteList.plain("QUERY_STRING"));
49
49
  public static final ByteList SERVER_PROTOCOL_BYTELIST = new ByteList(ByteList.plain("SERVER_PROTOCOL"));
50
50
 
51
+ public static String getEnvOrProperty(String name) {
52
+ String envValue = System.getenv(name);
53
+ return (envValue != null) ? envValue : System.getProperty(name);
54
+ }
55
+
56
+ public static int getConstLength(String name, Integer defaultValue) {
57
+ String stringValue = getEnvOrProperty(name);
58
+ if (stringValue == null || stringValue.isEmpty()) return defaultValue;
59
+
60
+ try {
61
+ int value = Integer.parseUnsignedInt(stringValue);
62
+ if (value <= 0) {
63
+ throw new NumberFormatException("The number is not positive.");
64
+ }
65
+ return value;
66
+ } catch (NumberFormatException e) {
67
+ System.err.println(String.format("The value %s for %s is invalid. Using default value %d instead.", stringValue, name, defaultValue));
68
+ return defaultValue;
69
+ }
70
+ }
71
+
51
72
  private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
52
73
  public IRubyObject allocate(Ruby runtime, RubyClass klass) {
53
74
  return new Http11(runtime, klass);
@@ -56,7 +77,7 @@ public class Http11 extends RubyObject {
56
77
 
57
78
  public static void createHttp11(Ruby runtime) {
58
79
  RubyModule mPuma = runtime.defineModule("Puma");
59
- mPuma.defineClassUnder("HttpParserError",runtime.getClass("IOError"),runtime.getClass("IOError").getAllocator());
80
+ mPuma.defineClassUnder("HttpParserError",runtime.getClass("StandardError"),runtime.getClass("StandardError").getAllocator());
60
81
 
61
82
  RubyClass cHttpParser = mPuma.defineClassUnder("HttpParser",runtime.getObject(),ALLOCATOR);
62
83
  cHttpParser.defineAnnotatedMethods(Http11.class);
@@ -99,6 +120,8 @@ public class Http11 extends RubyObject {
99
120
  int bite = b.get(i) & 0xFF;
100
121
  if(bite == '-') {
101
122
  b.set(i, (byte)'_');
123
+ } else if(bite == '_') {
124
+ b.set(i, (byte)',');
102
125
  } else {
103
126
  b.set(i, (byte)Character.toUpperCase(bite));
104
127
  }
@@ -47,6 +47,7 @@ import static javax.net.ssl.SSLEngineResult.Status;
47
47
  import static javax.net.ssl.SSLEngineResult.HandshakeStatus;
48
48
 
49
49
  public class MiniSSL extends RubyObject { // MiniSSL::Engine
50
+ private static final long serialVersionUID = -6903439483039141234L;
50
51
  private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
51
52
  public IRubyObject allocate(Ruby runtime, RubyClass klass) {
52
53
  return new MiniSSL(runtime, klass);
@@ -500,7 +501,7 @@ public class MiniSSL extends RubyObject { // MiniSSL::Engine
500
501
  }
501
502
 
502
503
  private static RaiseException newError(Ruby runtime, RubyClass errorClass, String message, Throwable cause) {
503
- RaiseException ex = new RaiseException(runtime, errorClass, message, true);
504
+ RaiseException ex = RaiseException.from(runtime, errorClass, message);
504
505
  ex.initCause(cause);
505
506
  return ex;
506
507
  }
@@ -461,6 +461,9 @@ void Init_mini_ssl(VALUE mod);
461
461
 
462
462
  void Init_puma_http11(void)
463
463
  {
464
+ #ifdef HAVE_RB_EXT_RACTOR_SAFE
465
+ rb_ext_ractor_safe(true);
466
+ #endif
464
467
 
465
468
  VALUE mPuma = rb_define_module("Puma");
466
469
  VALUE cHttpParser = rb_define_class_under(mPuma, "HttpParser", rb_cObject);
@@ -472,7 +475,7 @@ void Init_puma_http11(void)
472
475
  DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
473
476
  DEF_GLOBAL(request_path, "REQUEST_PATH");
474
477
 
475
- eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eIOError);
478
+ eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eStandardError);
476
479
  rb_global_variable(&eHttpParserError);
477
480
 
478
481
  rb_define_alloc_func(cHttpParser, HttpParser_alloc);
@@ -80,7 +80,7 @@ module Puma
80
80
 
81
81
  def authenticate(env)
82
82
  return true unless @auth_token
83
- env['QUERY_STRING'].to_s.split('&;').include? "token=#{@auth_token}"
83
+ env['QUERY_STRING'].to_s.split(/[&;]/).include? "token=#{@auth_token}"
84
84
  end
85
85
 
86
86
  def rack_response(status, body, content_type='application/json')
data/lib/puma/binder.rb CHANGED
@@ -19,13 +19,14 @@ module Puma
19
19
 
20
20
  RACK_VERSION = [1,6].freeze
21
21
 
22
- def initialize(log_writer, conf = Configuration.new)
22
+ def initialize(log_writer, conf = Configuration.new, env: ENV)
23
23
  @log_writer = log_writer
24
24
  @conf = conf
25
25
  @listeners = []
26
26
  @inherited_fds = {}
27
27
  @activated_sockets = {}
28
28
  @unix_paths = []
29
+ @env = env
29
30
 
30
31
  @proto_env = {
31
32
  "rack.version".freeze => RACK_VERSION,
@@ -34,7 +35,7 @@ module Puma
34
35
  "rack.multiprocess".freeze => conf.options[:workers] >= 1,
35
36
  "rack.run_once".freeze => false,
36
37
  RACK_URL_SCHEME => conf.options[:rack_url_scheme],
37
- "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
38
+ "SCRIPT_NAME".freeze => env['SCRIPT_NAME'] || "",
38
39
 
39
40
  # I'd like to set a default CONTENT_TYPE here but some things
40
41
  # depend on their not being a default set and inferring
@@ -87,7 +88,7 @@ module Puma
87
88
  # @version 5.0.0
88
89
  #
89
90
  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}"
91
+ @log_writer.debug "ENV['LISTEN_FDS'] #{@env['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
91
92
  return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
92
93
  env_hash['LISTEN_FDS'].to_i.times do |index|
93
94
  sock = TCPServer.for_fd(socket_activation_fd(index))
@@ -141,7 +142,14 @@ module Puma
141
142
  end
142
143
  end
143
144
 
145
+ def before_parse(&block)
146
+ @before_parse ||= []
147
+ @before_parse << block if block
148
+ @before_parse
149
+ end
150
+
144
151
  def parse(binds, log_writer = nil, log_msg = 'Listening')
152
+ before_parse.each(&:call)
145
153
  log_writer ||= @log_writer
146
154
  binds.each do |str|
147
155
  uri = URI.parse str
@@ -183,7 +191,7 @@ module Puma
183
191
  io = inherit_unix_listener path, fd
184
192
  log_writer.log "* Inherited #{str}"
185
193
  elsif sock = @activated_sockets.delete([ :unix, path ]) ||
186
- @activated_sockets.delete([ :unix, File.realdirpath(path) ])
194
+ !abstract && @activated_sockets.delete([ :unix, File.realdirpath(path) ])
187
195
  @unix_paths << path unless abstract || File.exist?(path)
188
196
  io = inherit_unix_listener path, sock
189
197
  log_writer.log "* Activated #{str}"
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,7 +39,7 @@ module Puma
39
39
  @control_url = nil
40
40
  @control_options = {}
41
41
 
42
- setup_options
42
+ setup_options env
43
43
 
44
44
  begin
45
45
  @parser.parse! @argv
@@ -63,7 +63,7 @@ module Puma
63
63
  end
64
64
  end
65
65
 
66
- @launcher = Puma::Launcher.new(@conf, :log_writer => @log_writer, :events => @events, :argv => argv)
66
+ @launcher = Puma::Launcher.new(@conf, env: ENV, log_writer: @log_writer, events: @events, argv: argv)
67
67
  end
68
68
 
69
69
  attr_reader :launcher
@@ -92,8 +92,8 @@ module Puma
92
92
  # Build the OptionParser object to handle the available options.
93
93
  #
94
94
 
95
- def setup_options
96
- @conf = Configuration.new({}, {events: @events}) do |user_config, file_config|
95
+ def setup_options(env = ENV)
96
+ @conf = Configuration.new({}, {events: @events}, env) do |user_config, file_config|
97
97
  @parser = OptionParser.new do |o|
98
98
  o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
99
99
  user_config.bind arg
@@ -157,6 +157,10 @@ module Puma
157
157
  user_config.pidfile arg
158
158
  end
159
159
 
160
+ o.on "--plugin PLUGIN", "Load the given PLUGIN. Can be used multiple times to load multiple plugins." do |arg|
161
+ user_config.plugin arg
162
+ end
163
+
160
164
  o.on "--preload", "Preload the app. Cluster mode only" do
161
165
  user_config.preload_app!
162
166
  end