puma 6.3.0-java → 6.4.0-java

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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ad102abd347c33f98c3a3a1000304ece5cbdbfe4bef67b4adbade6c91111cf0
4
- data.tar.gz: 8f9d1ce702bcffeac26ee1882c94603c29130ec61a21e6158d0c0b4cf69cc228
3
+ metadata.gz: 725988ce035212443c9cd9c6e250dc80edb5de0d1b843a49d11158e5055699b8
4
+ data.tar.gz: e5f9f2eb0cf769ff5497ce68bb565f8ead8057ca1293d83a2ff10b89044e58ef
5
5
  SHA512:
6
- metadata.gz: f03c8f279ba9aa8139e056b1047a16c2e7f280cfbd9f3106c578d5782b8078dfd6d4c7ed3731066b20643f66bed6bcdf7b8a1e58fec429e0757940191e173dd0
7
- data.tar.gz: 28e109af79749191f8ea47247f66dcdd55ed74a53fcd3443e3e918a173e880ba8938fdea59aaa6c5abbfe8626358a2e52da50cf4df67e9fafa546bc9b3dc5db9
6
+ metadata.gz: f8a546a676d69611917f30f3839ca01f84f4374e40aa1167882784013b8bddfd95ce03d84aecff86017bf23c9cd7a5efa097fc4c746299da224866737388cdfe
7
+ data.tar.gz: 7bbf658a44f85eb817101d86e565e03def93a580b31bf4cfd739ecca6c639b070bea75cb3660fe25ab9519396fed9fd31b664b625328430784fdc05cab922f78
data/History.md CHANGED
@@ -1,3 +1,26 @@
1
+ ## 6.4.0 / 2023-09-21
2
+
3
+ * Features
4
+ * on_thread_exit hook ([#2920])
5
+ * on_thread_start_hook ([#3195])
6
+ * Shutdown on idle ([#3209], [#2580])
7
+ * New error message when control server port taken ([#3204])
8
+
9
+ * Refactor
10
+ * Remove `Forwardable` dependency ([#3191], #3190)
11
+ * Update URLMap Regexp usage for Ruby v3.3 ([#3165])
12
+
13
+ * Bugfixes
14
+ * Bring the cert_pem: parameter into parity with the cert: parameter to ssl_bind. ([#3174])
15
+ * Fix using control server with IPv6 host ([#3181])
16
+ * control_cli.rb - add require_relative 'log_writer' ([#3187])
17
+ * Fix cases where fallback Rack response wasn't sent to the client ([#3094])
18
+
19
+ ## 6.3.1 / 2023-08-18
20
+
21
+ * Security
22
+ * Address HTTP request smuggling vulnerabilities with zero-length Content Length header and trailer fields ([GHSA-68xg-gqqm-vgj8](https://github.com/puma/puma/security/advisories/GHSA-68xg-gqqm-vgj8))
23
+
1
24
  ## 6.3.0 / 2023-05-31
2
25
 
3
26
  * Features
@@ -10,7 +33,7 @@
10
33
  * Handle malformed request path ([#3155], [#3148])
11
34
  * Misc lib file fixes - trapping additional errors, CI helper ([#3129])
12
35
  * Fixup req form data file upload with "r\n" line endings ([#3137])
13
- * Restore rack 1.6 compatibility Restore rack 1.6 compatibility ([#3156])
36
+ * Restore rack 1.6 compatibility ([#3156])
14
37
 
15
38
  * Refactor
16
39
  * const.rb - Update Puma::HTTP_STATUS_CODES ([#3162])
@@ -121,6 +144,16 @@
121
144
  * Ruby 3.2 will have native IO#wait_* methods, don't require io/wait ([#2903])
122
145
  * Various internal API refactorings ([#2942], [#2921], [#2922], [#2955])
123
146
 
147
+ ## 5.6.7 / 2023-08-18
148
+
149
+ * Security
150
+ * Address HTTP request smuggling vulnerabilities with zero-length Content Length header and trailer fields ([GHSA-68xg-gqqm-vgj8](https://github.com/puma/puma/security/advisories/GHSA-68xg-gqqm-vgj8))
151
+
152
+ ## 5.6.6 / 2023-06-21
153
+
154
+ * Bugfix
155
+ * Prevent loading with rack 3 ([#3166])
156
+
124
157
  ## 5.6.5 / 2022-08-23
125
158
 
126
159
  * Feature
@@ -1996,6 +2029,17 @@ be added back in a future date when a java Puma::MiniSSL is added.
1996
2029
  * Bugfixes
1997
2030
  * Your bugfix goes here <Most recent on the top, like GitHub> (#Github Number)
1998
2031
 
2032
+ [#2920]:https://github.com/puma/puma/pull/2920 "PR by @biinari, merged 2023-07-11"
2033
+ [#3195]:https://github.com/puma/puma/pull/3195 "PR by @binarygit, merged 2023-08-15"
2034
+ [#3209]:https://github.com/puma/puma/pull/3209 "PR by @joshuay03, merged 2023-09-04"
2035
+ [#2580]:https://github.com/puma/puma/issues/2580 "Issue by @schuetzm, closed 2023-09-04"
2036
+ [#3204]:https://github.com/puma/puma/pull/3204 "PR by @dhavalsingh, merged 2023-08-25"
2037
+ [#3191]:https://github.com/puma/puma/pull/3191 "PR by @MSP-Greg, merged 2023-08-31"
2038
+ [#3165]:https://github.com/puma/puma/pull/3165 "PR by @fallwith, merged 2023-06-06"
2039
+ [#3174]:https://github.com/puma/puma/pull/3174 "PR by @copiousfreetime, merged 2023-06-11"
2040
+ [#3181]:https://github.com/puma/puma/pull/3181 "PR by @MSP-Greg, merged 2023-06-23"
2041
+ [#3187]:https://github.com/puma/puma/pull/3187 "PR by @MSP-Greg, merged 2023-06-30"
2042
+ [#3094]:https://github.com/puma/puma/pull/3094 "PR by @Vuta, merged 2023-07-23"
1999
2043
  [#3106]:https://github.com/puma/puma/pull/3106 "PR by @MSP-Greg, merged 2023-05-29"
2000
2044
  [#3014]:https://github.com/puma/puma/issues/3014 "Issue by @kyledrake, closed 2023-05-29"
2001
2045
  [#3161]:https://github.com/puma/puma/pull/3161 "PR by @MSP-Greg, merged 2023-05-27"
@@ -2094,6 +2138,7 @@ be added back in a future date when a java Puma::MiniSSL is added.
2094
2138
  [#2921]:https://github.com/puma/puma/issues/2921 "Issue by @MSP-Greg, closed 2022-09-15"
2095
2139
  [#2922]:https://github.com/puma/puma/issues/2922 "Issue by @MSP-Greg, closed 2022-09-10"
2096
2140
  [#2955]:https://github.com/puma/puma/pull/2955 "PR by @cafedomancer, merged 2022-09-15"
2141
+ [#3166]:https://github.com/puma/puma/pull/3166 "PR by @JoeDupuis, merged 2023-06-08"
2097
2142
  [#2868]:https://github.com/puma/puma/pull/2868 "PR by @MSP-Greg, merged 2022-06-02"
2098
2143
  [#2866]:https://github.com/puma/puma/issues/2866 "Issue by @slondr, closed 2022-06-02"
2099
2144
  [#2883]:https://github.com/puma/puma/pull/2883 "PR by @MSP-Greg, merged 2022-06-02"
@@ -2120,7 +2165,7 @@ be added back in a future date when a java Puma::MiniSSL is added.
2120
2165
  [#2794]:https://github.com/puma/puma/pull/2794 "PR by @johnnyshields, merged 2022-01-10"
2121
2166
  [#2759]:https://github.com/puma/puma/pull/2759 "PR by @ob-stripe, merged 2021-12-11"
2122
2167
  [#2731]:https://github.com/puma/puma/pull/2731 "PR by @baelter, merged 2021-11-02"
2123
- [#2341]:https://github.com/puma/puma/issues/2341 "Issue by @cjlarose, opened 2020-08-18"
2168
+ [#2341]:https://github.com/puma/puma/issues/2341 "Issue by @cjlarose, closed 2023-07-23"
2124
2169
  [#2728]:https://github.com/puma/puma/pull/2728 "PR by @dalibor, merged 2021-10-31"
2125
2170
  [#2733]:https://github.com/puma/puma/pull/2733 "PR by @ob-stripe, merged 2021-12-12"
2126
2171
  [#2807]:https://github.com/puma/puma/pull/2807 "PR by @MSP-Greg, merged 2022-01-25"
data/README.md CHANGED
@@ -12,11 +12,15 @@ Puma is a **simple, fast, multi-threaded, and highly parallel HTTP 1.1 server fo
12
12
 
13
13
  ## Built For Speed &amp; Parallelism
14
14
 
15
- Puma processes requests using a C-optimized Ragel extension (inherited from Mongrel) that provides fast, accurate HTTP 1.1 protocol parsing in a portable way. Puma then serves the request using a thread pool. Each request is served in a separate thread, so truly parallel Ruby implementations (JRuby, Rubinius) will use all available CPU cores.
15
+ Puma is a server for [Rack](https://github.com/rack/rack)-powered HTTP applications written in Ruby. It is:
16
+ * **Multi-threaded**. Each request is served in a separate thread. This helps you serve more requests per second with less memory use.
17
+ * **Multi-process**. "Pre-forks" in cluster mode, using less memory per-process thanks to copy-on-write memory.
18
+ * **Standalone**. With SSL support, zero-downtime rolling restarts and a built-in request bufferer, you can deploy Puma without any reverse proxy.
19
+ * **Battle-tested**. Our HTTP parser is inherited from Mongrel and has over 15 years of production use. Puma is currently the most popular Ruby webserver, and is the default server for Ruby on Rails.
16
20
 
17
21
  Originally designed as a server for [Rubinius](https://github.com/rubinius/rubinius), Puma also works well with Ruby (MRI) and JRuby.
18
22
 
19
- On MRI, there is a Global VM Lock (GVL) that ensures only one thread can run Ruby code at a time. But if you're doing a lot of blocking IO (such as HTTP calls to external APIs like Twitter), Puma still improves MRI's throughput by allowing IO waiting to be done in parallel.
23
+ On MRI, there is a Global VM Lock (GVL) that ensures only one thread can run Ruby code at a time. But if you're doing a lot of blocking IO (such as HTTP calls to external APIs like Twitter), Puma still improves MRI's throughput by allowing IO waiting to be done in parallel. Truly parallel Ruby implementations (TruffleRuby, JRuby) don't have this limitation.
20
24
 
21
25
  ## Quick Start
22
26
 
@@ -114,6 +118,8 @@ $ WEB_CONCURRENCY=3 puma -t 8:32
114
118
 
115
119
  Note that threads are still used in clustered mode, and the `-t` thread flag setting is per worker, so `-w 2 -t 16:16` will spawn 32 threads in total, with 16 in each worker process.
116
120
 
121
+ For an in-depth discussion of the tradeoffs of thread and process count settings, [see our docs](https://github.com/puma/puma/blob/9282a8efa5a0c48e39c60d22ca70051a25df9f55/docs/kubernetes.md#workers-per-pod-and-other-config-issues).
122
+
117
123
  In clustered mode, Puma can "preload" your application. This loads all the application code *prior* to forking. Preloading reduces total memory usage of your application via an operating system feature called [copy-on-write](https://en.wikipedia.org/wiki/Copy-on-write).
118
124
 
119
125
  If the `WEB_CONCURRENCY` environment variable is set to a value > 1 (and `--prune-bundler` has not been specified), preloading will be enabled by default. Otherwise, you can use the `--preload` flag from the command line:
@@ -211,29 +217,32 @@ Puma supports the [`localhost`] gem for self-signed certificates. This is partic
211
217
 
212
218
  Puma automatically configures SSL when the [`localhost`] gem is loaded in a `development` environment:
213
219
 
220
+ Add the gem to your Gemfile:
214
221
  ```ruby
215
- # Add the gem to your Gemfile
216
222
  group(:development) do
217
223
  gem 'localhost'
218
224
  end
225
+ ```
219
226
 
220
- # And require it implicitly using bundler
227
+ And require it implicitly using bundler:
228
+ ```ruby
221
229
  require "bundler"
222
230
  Bundler.require(:default, ENV["RACK_ENV"].to_sym)
231
+ ```
223
232
 
224
- # Alternatively, you can require the gem in config.ru:
225
- require './app'
233
+ Alternatively, you can require the gem in your configuration file, either `config/puma/development.rb`, `config/puma.rb`, or set via the `-C` cli option:
234
+ ```ruby
226
235
  require 'localhost'
227
- run Sinatra::Application
236
+ # configuration methods (from Puma::DSL) as needed
228
237
  ```
229
238
 
230
239
  Additionally, Puma must be listening to an SSL socket:
231
240
 
232
241
  ```shell
233
- $ puma -b 'ssl://localhost:9292' config.ru
242
+ $ puma -b 'ssl://localhost:9292' -C config/use_local_host.rb
234
243
 
235
244
  # The following options allow you to reach Puma over HTTP as well:
236
- $ puma -b ssl://localhost:9292 -b tcp://localhost:9393 config.ru
245
+ $ puma -b ssl://localhost:9292 -b tcp://localhost:9393 -C config/use_local_host.rb
237
246
  ```
238
247
 
239
248
  [`localhost`]: https://github.com/socketry/localhost
data/docs/kubernetes.md CHANGED
@@ -64,3 +64,15 @@ There is a subtle race condition between step 2 and 3: The replication controlle
64
64
  The way Kubernetes works this way, rather than handling step 2 synchronously, is due to the CAP theorem: in a distributed system there is no way to guarantee that any message will arrive promptly. In particular, waiting for all Service controllers to report back might get stuck for an indefinite time if one of them has already been terminated or if there has been a net split. A way to work around this is to add a sleep to the pre-stop hook of the same time as the `terminationGracePeriodSeconds` time. This will allow the Puma process to keep serving new requests during the entire grace period, although it will no longer receive new requests after all Service controllers have propagated the removal of the pod from their endpoint lists. Then, after `terminationGracePeriodSeconds`, the pod receives `SIGKILL` and closes down. If your process can't handle SIGKILL properly, for example because it needs to release locks in different services, you can also sleep for a shorter period (and/or increase `terminationGracePeriodSeconds`) as long as the time slept is longer than the time that your Service controllers take to propagate the pod removal. The downside of this workaround is that all pods will take at minimum the amount of time slept to shut down and this will increase the time required for your rolling deploy.
65
65
 
66
66
  More discussions and links to relevant articles can be found in https://github.com/puma/puma/issues/2343.
67
+
68
+ ## Workers Per Pod, and Other Config Issues
69
+
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
+
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.
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
+ * 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
+ * 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.
76
+ * If multithreaded, allocate 1 CPU per worker. If single threaded, allocate 0.75 cpus per worker. Most web applications spend about 25% of their time in I/O - but when you're running multi-threaded, your Puma process will have higher CPU usage and should be able to fully saturate a CPU core.
77
+ * Most Puma processes will use about ~512MB-1GB per worker, and about 1GB for the master process. However, you probably shouldn't bother with setting memory limits lower than around 2GB per process, because most places you are deploying will have 2GB of RAM per CPU. A sensible memory limit for a Puma configuration of 4 child workers might be something like 8 GB (1 GB for the master, 7GB for the 4 children).
78
+
@@ -36,6 +36,12 @@ void raise_file_error(const char* caller, const char *filename) {
36
36
  rb_raise(eError, "%s: error in file '%s': %s", caller, filename, ERR_error_string(ERR_get_error(), NULL));
37
37
  }
38
38
 
39
+ NORETURN(void raise_param_error(const char* caller, const char *param));
40
+
41
+ void raise_param_error(const char* caller, const char *param) {
42
+ rb_raise(eError, "%s: error with parameter '%s': %s", caller, param, ERR_error_string(ERR_get_error(), NULL));
43
+ }
44
+
39
45
  void engine_free(void *ptr) {
40
46
  ms_conn *conn = ptr;
41
47
  ms_cert_buf* cert_buf = (ms_cert_buf*)SSL_get_app_data(conn->ssl);
@@ -226,7 +232,7 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
226
232
  VALUE key, cert, ca, verify_mode, ssl_cipher_filter, no_tlsv1, no_tlsv1_1,
227
233
  verification_flags, session_id_bytes, cert_pem, key_pem, key_password_command, key_password;
228
234
  BIO *bio;
229
- X509 *x509;
235
+ X509 *x509 = NULL;
230
236
  EVP_PKEY *pkey;
231
237
  pem_password_cb *password_cb = NULL;
232
238
  const char *password = NULL;
@@ -298,16 +304,65 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
298
304
  }
299
305
 
300
306
  if (!NIL_P(cert_pem)) {
307
+ X509 *ca = NULL;
308
+ unsigned long err;
309
+
301
310
  bio = BIO_new(BIO_s_mem());
302
311
  BIO_puts(bio, RSTRING_PTR(cert_pem));
312
+
313
+ /**
314
+ * Much of this pulled as a simplified version of the `use_certificate_chain_file` method
315
+ * from openssl's `ssl_rsa.c` file.
316
+ */
317
+
318
+ /* first read the cert as the first item in the pem file */
303
319
  x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
320
+ if (NULL == x509) {
321
+ BIO_free_all(bio);
322
+ raise_param_error("PEM_read_bio_X509", "cert_pem");
323
+ }
304
324
 
305
- if (SSL_CTX_use_certificate(ctx, x509) != 1) {
306
- BIO_free(bio);
307
- raise_file_error("SSL_CTX_use_certificate", RSTRING_PTR(cert_pem));
325
+ /* Add the cert to the context */
326
+ /* 1 is success - otherwise check the error codes */
327
+ if (1 != SSL_CTX_use_certificate(ctx, x509)) {
328
+ BIO_free_all(bio);
329
+ raise_param_error("SSL_CTX_use_certificate", "cert_pem");
330
+ }
331
+
332
+ X509_free(x509); /* no longer need our reference */
333
+
334
+ /* Now lets load up the rest of the certificate chain */
335
+ /* 1 is success 0 is error */
336
+ if (0 == SSL_CTX_clear_chain_certs(ctx)) {
337
+ BIO_free_all(bio);
338
+ raise_param_error("SSL_CTX_clear_chain_certs","cert_pem");
339
+ }
340
+
341
+ while (1) {
342
+ ca = PEM_read_bio_X509(bio, NULL, NULL, NULL);
343
+
344
+ if (NULL == ca) {
345
+ break;
346
+ }
347
+
348
+ if (0 == SSL_CTX_add0_chain_cert(ctx, ca)) {
349
+ BIO_free_all(bio);
350
+ raise_param_error("SSL_CTX_add0_chain_cert","cert_pem");
351
+ }
352
+ /* don't free ca - its now owned by the context */
353
+ }
354
+
355
+ /* ca is NULL - so its either the end of the file or an error */
356
+ err = ERR_peek_last_error();
357
+
358
+ /* If its the end of the file - then we are done, in any case free the bio */
359
+ BIO_free_all(bio);
360
+
361
+ if ((ERR_GET_LIB(err) == ERR_LIB_PEM) && (ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) {
362
+ ERR_clear_error();
363
+ } else {
364
+ raise_param_error("PEM_read_bio_X509","cert_pem");
308
365
  }
309
- X509_free(x509);
310
- BIO_free(bio);
311
366
  }
312
367
 
313
368
  if (!NIL_P(key_pem)) {
data/lib/puma/binder.rb CHANGED
@@ -330,7 +330,7 @@ module Puma
330
330
  return
331
331
  end
332
332
 
333
- host = host[1..-2] if host and host[0..0] == '['
333
+ host = host[1..-2] if host&.start_with? '['
334
334
  tcp_server = TCPServer.new(host, port)
335
335
 
336
336
  if optimize_for_latency
@@ -364,7 +364,7 @@ module Puma
364
364
  return
365
365
  end
366
366
 
367
- host = host[1..-2] if host[0..0] == '['
367
+ host = host[1..-2] if host&.start_with? '['
368
368
  s = TCPServer.new(host, port)
369
369
  if optimize_for_latency
370
370
  s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
data/lib/puma/cli.rb CHANGED
@@ -144,6 +144,10 @@ module Puma
144
144
  $LOAD_PATH.unshift(*arg.split(':'))
145
145
  end
146
146
 
147
+ o.on "--idle-timeout SECONDS", "Number of seconds until the next request before automatic shutdown" do |arg|
148
+ user_config.idle_timeout arg
149
+ end
150
+
147
151
  o.on "-p", "--port PORT", "Define the TCP port to bind to",
148
152
  "Use -b for more advanced options" do |arg|
149
153
  user_config.bind "tcp://#{Configuration::DEFAULTS[:tcp_host]}:#{arg}"
data/lib/puma/client.rb CHANGED
@@ -11,7 +11,6 @@ end
11
11
  require_relative 'detect'
12
12
  require_relative 'io_buffer'
13
13
  require 'tempfile'
14
- require 'forwardable'
15
14
 
16
15
  if Puma::IS_JRUBY
17
16
  # We have to work around some OpenSSL buffer/io-readiness bugs
@@ -49,7 +48,8 @@ module Puma
49
48
 
50
49
  # chunked body validation
51
50
  CHUNK_SIZE_INVALID = /[^\h]/.freeze
52
- CHUNK_VALID_ENDING = "\r\n".freeze
51
+ CHUNK_VALID_ENDING = Const::LINE_END
52
+ CHUNK_VALID_ENDING_SIZE = CHUNK_VALID_ENDING.bytesize
53
53
 
54
54
  # Content-Length header value validation
55
55
  CONTENT_LENGTH_VALUE_INVALID = /[^\d]/.freeze
@@ -61,7 +61,6 @@ module Puma
61
61
  EmptyBody = NullIO.new
62
62
 
63
63
  include Puma::Const
64
- extend Forwardable
65
64
 
66
65
  def initialize(io, env=nil)
67
66
  @io = io
@@ -110,7 +109,10 @@ module Puma
110
109
 
111
110
  attr_accessor :remote_addr_header, :listener
112
111
 
113
- def_delegators :@io, :closed?
112
+ # Remove in Puma 7?
113
+ def closed?
114
+ @to_io.closed?
115
+ end
114
116
 
115
117
  # Test to see if io meets a bare minimum of functioning, @to_io needs to be
116
118
  # used for MiniSSL::Socket
@@ -382,8 +384,8 @@ module Puma
382
384
  cl = @env[CONTENT_LENGTH]
383
385
 
384
386
  if cl
385
- # cannot contain characters that are not \d
386
- if CONTENT_LENGTH_VALUE_INVALID.match? cl
387
+ # cannot contain characters that are not \d, or be empty
388
+ if CONTENT_LENGTH_VALUE_INVALID.match?(cl) || cl.empty?
387
389
  raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
388
390
  end
389
391
  else
@@ -544,7 +546,7 @@ module Puma
544
546
 
545
547
  while !io.eof?
546
548
  line = io.gets
547
- if line.end_with?("\r\n")
549
+ if line.end_with?(CHUNK_VALID_ENDING)
548
550
  # Puma doesn't process chunk extensions, but should parse if they're
549
551
  # present, which is the reason for the semicolon regex
550
552
  chunk_hex = line.strip[/\A[^;]+/]
@@ -556,13 +558,19 @@ module Puma
556
558
  @in_last_chunk = true
557
559
  @body.rewind
558
560
  rest = io.read
559
- last_crlf_size = "\r\n".bytesize
560
- if rest.bytesize < last_crlf_size
561
+ if rest.bytesize < CHUNK_VALID_ENDING_SIZE
561
562
  @buffer = nil
562
- @partial_part_left = last_crlf_size - rest.bytesize
563
+ @partial_part_left = CHUNK_VALID_ENDING_SIZE - rest.bytesize
563
564
  return false
564
565
  else
565
- @buffer = rest[last_crlf_size..-1]
566
+ # if the next character is a CRLF, set buffer to everything after that CRLF
567
+ start_of_rest = if rest.start_with?(CHUNK_VALID_ENDING)
568
+ CHUNK_VALID_ENDING_SIZE
569
+ else # we have started a trailer section, which we do not support. skip it!
570
+ rest.index(CHUNK_VALID_ENDING*2) + CHUNK_VALID_ENDING_SIZE*2
571
+ end
572
+
573
+ @buffer = rest[start_of_rest..-1]
566
574
  @buffer = nil if @buffer.empty?
567
575
  set_ready
568
576
  return true
@@ -133,8 +133,10 @@ module Puma
133
133
  debug: false,
134
134
  early_hints: nil,
135
135
  environment: 'development'.freeze,
136
- # Number of seconds to wait until we get the first data for the request
136
+ # Number of seconds to wait until we get the first data for the request.
137
137
  first_data_timeout: 30,
138
+ # Number of seconds to wait until the next request before shutting down.
139
+ idle_timeout: nil,
138
140
  io_selector_backend: :auto,
139
141
  log_requests: false,
140
142
  logger: STDOUT,
data/lib/puma/const.rb CHANGED
@@ -100,8 +100,8 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "6.3.0"
104
- CODE_NAME = "Mugi No Toki Itaru"
103
+ PUMA_VERSION = VERSION = "6.4.0"
104
+ CODE_NAME = "The Eagle of Durango"
105
105
 
106
106
  PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
107
107
 
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'optparse'
4
- require_relative 'state_file'
5
4
  require_relative 'const'
6
5
  require_relative 'detect'
7
- require_relative 'configuration'
8
6
  require 'uri'
9
7
  require 'socket'
10
8
 
@@ -126,6 +124,9 @@ module Puma
126
124
  end
127
125
 
128
126
  if @config_file
127
+ require_relative 'configuration'
128
+ require_relative 'log_writer'
129
+
129
130
  config = Puma::Configuration.new({ config_files: [@config_file] }, {})
130
131
  config.load
131
132
  @state ||= config.options[:state]
@@ -149,6 +150,8 @@ module Puma
149
150
  raise "State file not found: #{@state}"
150
151
  end
151
152
 
153
+ require_relative 'state_file'
154
+
152
155
  sf = Puma::StateFile.new
153
156
  sf.load @state
154
157
 
@@ -164,22 +167,26 @@ module Puma
164
167
  def send_request
165
168
  uri = URI.parse @control_url
166
169
 
170
+ host = uri.host
171
+
167
172
  # create server object by scheme
168
173
  server =
169
174
  case uri.scheme
170
175
  when 'ssl'
171
176
  require 'openssl'
177
+ host = host[1..-2] if host&.start_with? '['
172
178
  OpenSSL::SSL::SSLSocket.new(
173
- TCPSocket.new(uri.host, uri.port),
179
+ TCPSocket.new(host, uri.port),
174
180
  OpenSSL::SSL::SSLContext.new)
175
181
  .tap { |ssl| ssl.sync_close = true } # default is false
176
182
  .tap(&:connect)
177
183
  when 'tcp'
178
- TCPSocket.new uri.host, uri.port
184
+ host = host[1..-2] if host&.start_with? '['
185
+ TCPSocket.new host, uri.port
179
186
  when 'unix'
180
187
  # check for abstract UNIXSocket
181
188
  UNIXSocket.new(@control_url.start_with?('unix://@') ?
182
- "\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}")
189
+ "\0#{host}#{uri.path}" : "#{host}#{uri.path}")
183
190
  else
184
191
  raise "Invalid scheme: #{uri.scheme}"
185
192
  end
data/lib/puma/dsl.rb CHANGED
@@ -315,16 +315,22 @@ module Puma
315
315
  bind URI::Generic.build(scheme: 'tcp', host: host, port: Integer(port)).to_s
316
316
  end
317
317
 
318
+ # Define how long the tcp socket stays open, if no data has been received.
319
+ # @see Puma::Server.new
320
+ def first_data_timeout(seconds)
321
+ @options[:first_data_timeout] = Integer(seconds)
322
+ end
323
+
318
324
  # Define how long persistent connections can be idle before Puma closes them.
319
325
  # @see Puma::Server.new
320
326
  def persistent_timeout(seconds)
321
327
  @options[:persistent_timeout] = Integer(seconds)
322
328
  end
323
329
 
324
- # Define how long the tcp socket stays open, if no data has been received.
330
+ # If a new request is not received within this number of seconds, begin shutting down.
325
331
  # @see Puma::Server.new
326
- def first_data_timeout(seconds)
327
- @options[:first_data_timeout] = Integer(seconds)
332
+ def idle_timeout(seconds)
333
+ @options[:idle_timeout] = Integer(seconds)
328
334
  end
329
335
 
330
336
  # Work around leaky apps that leave garbage in Thread locals
@@ -510,6 +516,12 @@ module Puma
510
516
  # `true`, which sets reuse 'on' with default values, or a hash, with `:size`
511
517
  # and/or `:timeout` keys, each with integer values.
512
518
  #
519
+ # The `cert:` options hash parameter can be the path to a certificate
520
+ # file including all intermediate certificates in PEM format.
521
+ #
522
+ # The `cert_pem:` options hash parameter can be String containing the
523
+ # cerificate and all intermediate certificates in PEM format.
524
+ #
513
525
  # @example
514
526
  # ssl_bind '127.0.0.1', '9292', {
515
527
  # cert: path_to_cert,
@@ -717,6 +729,40 @@ module Puma
717
729
  process_hook :before_refork, key, block, 'on_refork'
718
730
  end
719
731
 
732
+ # Code to run immediately before a thread starts. The worker does not
733
+ # start new threads until this code finishes.
734
+ #
735
+ # This hook is useful for doing something when a thread
736
+ # starts.
737
+ #
738
+ # This can be called multiple times to add several hooks.
739
+ #
740
+ # @example
741
+ # on_thread_start do
742
+ # puts 'On thread start...'
743
+ # end
744
+ def on_thread_start(&block)
745
+ @options[:before_thread_start] ||= []
746
+ @options[:before_thread_start] << block
747
+ end
748
+
749
+ # Code to run immediately before a thread exits. The worker does not
750
+ # accept new requests until this code finishes.
751
+ #
752
+ # This hook is useful for cleaning up thread local resources when a thread
753
+ # is trimmed.
754
+ #
755
+ # This can be called multiple times to add several hooks.
756
+ #
757
+ # @example
758
+ # on_thread_exit do
759
+ # puts 'On thread exit...'
760
+ # end
761
+ def on_thread_exit(&block)
762
+ @options[:before_thread_exit] ||= []
763
+ @options[:before_thread_exit] << block
764
+ end
765
+
720
766
  # Code to run out-of-band when the worker is idle.
721
767
  # These hooks run immediately after a request has finished
722
768
  # processing and there are no busy threads on the worker.
@@ -848,7 +894,8 @@ module Puma
848
894
  # not a request timeout, it is to protect against a hung or dead process.
849
895
  # Setting this value will not protect against slow requests.
850
896
  #
851
- # The minimum value is 6 seconds, the default value is 60 seconds.
897
+ # This value must be greater than worker_check_interval.
898
+ # The default value is 60 seconds.
852
899
  #
853
900
  # @note Cluster mode only.
854
901
  # @example
Binary file
@@ -34,7 +34,7 @@ module Puma::Rack
34
34
  end
35
35
 
36
36
  location = location.chomp('/')
37
- match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
37
+ match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", Regexp::NOENCODING)
38
38
 
39
39
  [host, location, match, app]
40
40
  }.sort_by do |(host, location, _, _)|
data/lib/puma/runner.rb CHANGED
@@ -75,7 +75,11 @@ module Puma
75
75
  control = Puma::Server.new app, nil,
76
76
  { min_threads: 0, max_threads: 1, queue_requests: false, log_writer: @log_writer }
77
77
 
78
- control.binder.parse [str], nil, 'Starting control server'
78
+ begin
79
+ control.binder.parse [str], nil, 'Starting control server'
80
+ rescue Errno::EADDRINUSE, Errno::EACCES => e
81
+ raise e, "Error: Control server address '#{str}' is already in use. Original error: #{e.message}"
82
+ end
79
83
 
80
84
  control.run thread_name: 'ctl'
81
85
  @control = control
data/lib/puma/server.rb CHANGED
@@ -15,7 +15,6 @@ require_relative 'request'
15
15
 
16
16
  require 'socket'
17
17
  require 'io/wait' unless Puma::HAS_NATIVE_IO_WAIT
18
- require 'forwardable'
19
18
 
20
19
  module Puma
21
20
 
@@ -32,7 +31,6 @@ module Puma
32
31
  class Server
33
32
  include Puma::Const
34
33
  include Request
35
- extend Forwardable
36
34
 
37
35
  attr_reader :thread
38
36
  attr_reader :log_writer
@@ -48,9 +46,6 @@ module Puma
48
46
  attr_accessor :app
49
47
  attr_accessor :binder
50
48
 
51
- def_delegators :@binder, :add_tcp_listener, :add_ssl_listener,
52
- :add_unix_listener, :connected_ports
53
-
54
49
  THREAD_LOCAL_KEY = :puma_server
55
50
 
56
51
  # Create a server for the rack app +app+.
@@ -86,15 +81,16 @@ module Puma
86
81
  UserFileDefaultOptions.new(options, Configuration::DEFAULTS)
87
82
  end
88
83
 
89
- @log_writer = @options.fetch :log_writer, LogWriter.stdio
90
- @early_hints = @options[:early_hints]
91
- @first_data_timeout = @options[:first_data_timeout]
92
- @min_threads = @options[:min_threads]
93
- @max_threads = @options[:max_threads]
94
- @persistent_timeout = @options[:persistent_timeout]
95
- @queue_requests = @options[:queue_requests]
96
- @max_fast_inline = @options[:max_fast_inline]
97
- @io_selector_backend = @options[:io_selector_backend]
84
+ @log_writer = @options.fetch :log_writer, LogWriter.stdio
85
+ @early_hints = @options[:early_hints]
86
+ @first_data_timeout = @options[:first_data_timeout]
87
+ @persistent_timeout = @options[:persistent_timeout]
88
+ @idle_timeout = @options[:idle_timeout]
89
+ @min_threads = @options[:min_threads]
90
+ @max_threads = @options[:max_threads]
91
+ @queue_requests = @options[:queue_requests]
92
+ @max_fast_inline = @options[:max_fast_inline]
93
+ @io_selector_backend = @options[:io_selector_backend]
98
94
  @http_content_length_limit = @options[:http_content_length_limit]
99
95
 
100
96
  # make this a hash, since we prefer `key?` over `include?`
@@ -330,8 +326,12 @@ module Puma
330
326
 
331
327
  while @status == :run || (drain && shutting_down?)
332
328
  begin
333
- ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : nil)
334
- break unless ios
329
+ ios = IO.select sockets, nil, nil, (shutting_down? ? 0 : @idle_timeout)
330
+ unless ios
331
+ @status = :stop unless shutting_down?
332
+ break
333
+ end
334
+
335
335
  ios.first.each do |sock|
336
336
  if sock == check
337
337
  break if handle_check
@@ -476,7 +476,7 @@ module Puma
476
476
  end
477
477
  true
478
478
  rescue StandardError => e
479
- client_error(e, client)
479
+ client_error(e, client, requests)
480
480
  # The ensure tries to close +client+ down
481
481
  requests > 0
482
482
  ensure
@@ -504,22 +504,22 @@ module Puma
504
504
  # :nocov:
505
505
 
506
506
  # Handle various error types thrown by Client I/O operations.
507
- def client_error(e, client)
507
+ def client_error(e, client, requests = 1)
508
508
  # Swallow, do not log
509
509
  return if [ConnectionError, EOFError].include?(e.class)
510
510
 
511
- lowlevel_error(e, client.env)
512
511
  case e
513
512
  when MiniSSL::SSLError
513
+ lowlevel_error(e, client.env)
514
514
  @log_writer.ssl_error e, client.io
515
515
  when HttpParserError
516
- client.write_error(400)
516
+ response_to_error(client, requests, e, 400)
517
517
  @log_writer.parse_error e, client
518
518
  when HttpParserError501
519
- client.write_error(501)
519
+ response_to_error(client, requests, e, 501)
520
520
  @log_writer.parse_error e, client
521
521
  else
522
- client.write_error(500)
522
+ response_to_error(client, requests, e, 500)
523
523
  @log_writer.unknown_error e, nil, "Read"
524
524
  end
525
525
  end
@@ -541,10 +541,17 @@ module Puma
541
541
  backtrace = e.backtrace.nil? ? '<no backtrace available>' : e.backtrace.join("\n")
542
542
  [status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{backtrace}"]]
543
543
  else
544
- [status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
544
+ [status, {}, [""]]
545
545
  end
546
546
  end
547
547
 
548
+ def response_to_error(client, requests, err, status_code)
549
+ status, headers, res_body = lowlevel_error(err, client.env, status_code)
550
+ prepare_response(status, headers, res_body, requests, client)
551
+ client.write_error(status_code)
552
+ end
553
+ private :response_to_error
554
+
548
555
  # Wait for all outstanding requests to finish.
549
556
  #
550
557
  def graceful_shutdown
@@ -623,5 +630,27 @@ module Puma
623
630
  def stats
624
631
  STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
625
632
  end
633
+
634
+ # below are 'delegations' to binder
635
+ # remove in Puma 7?
636
+
637
+
638
+ def add_tcp_listener(host, port, optimize_for_latency = true, backlog = 1024)
639
+ @binder.add_tcp_listener host, port, optimize_for_latency, backlog
640
+ end
641
+
642
+ def add_ssl_listener(host, port, ctx, optimize_for_latency = true,
643
+ backlog = 1024)
644
+ @binder.add_ssl_listener host, port, ctx, optimize_for_latency, backlog
645
+ end
646
+
647
+ def add_unix_listener(path, umask = nil, mode = nil, backlog = 1024)
648
+ @binder.add_unix_listener path, umask, mode, backlog
649
+ end
650
+
651
+ # @!attribute [r] connected_ports
652
+ def connected_ports
653
+ @binder.connected_ports
654
+ end
626
655
  end
627
656
  end
@@ -51,6 +51,8 @@ module Puma
51
51
  @block = block
52
52
  @out_of_band = options[:out_of_band]
53
53
  @clean_thread_locals = options[:clean_thread_locals]
54
+ @before_thread_start = options[:before_thread_start]
55
+ @before_thread_exit = options[:before_thread_exit]
54
56
  @reaping_time = options[:reaping_time]
55
57
  @auto_trim_time = options[:auto_trim_time]
56
58
 
@@ -107,6 +109,7 @@ module Puma
107
109
  def spawn_thread
108
110
  @spawned += 1
109
111
 
112
+ trigger_before_thread_start_hooks
110
113
  th = Thread.new(@spawned) do |spawned|
111
114
  Puma.set_thread_name '%s tp %03i' % [@name, spawned]
112
115
  todo = @todo
@@ -125,6 +128,7 @@ module Puma
125
128
  @spawned -= 1
126
129
  @workers.delete th
127
130
  not_full.signal
131
+ trigger_before_thread_exit_hooks
128
132
  Thread.exit
129
133
  end
130
134
 
@@ -162,6 +166,36 @@ module Puma
162
166
 
163
167
  private :spawn_thread
164
168
 
169
+ def trigger_before_thread_start_hooks
170
+ return unless @before_thread_start&.any?
171
+
172
+ @before_thread_start.each do |b|
173
+ begin
174
+ b.call
175
+ rescue Exception => e
176
+ STDERR.puts "WARNING before_thread_start hook failed with exception (#{e.class}) #{e.message}"
177
+ end
178
+ end
179
+ nil
180
+ end
181
+
182
+ private :trigger_before_thread_start_hooks
183
+
184
+ def trigger_before_thread_exit_hooks
185
+ return unless @before_thread_exit&.any?
186
+
187
+ @before_thread_exit.each do |b|
188
+ begin
189
+ b.call
190
+ rescue Exception => e
191
+ STDERR.puts "WARNING before_thread_exit hook failed with exception (#{e.class}) #{e.message}"
192
+ end
193
+ end
194
+ nil
195
+ end
196
+
197
+ private :trigger_before_thread_exit_hooks
198
+
165
199
  # @version 5.0.0
166
200
  def trigger_out_of_band_hook
167
201
  return false unless @out_of_band&.any?
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puma
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.3.0
4
+ version: 6.4.0
5
5
  platform: java
6
6
  authors:
7
7
  - Evan Phoenix
@@ -17,8 +17,8 @@ dependencies:
17
17
  - !ruby/object:Gem::Version
18
18
  version: '2.0'
19
19
  name: nio4r
20
- prerelease: false
21
20
  type: :runtime
21
+ prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
@@ -145,7 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
145
  - !ruby/object:Gem::Version
146
146
  version: '0'
147
147
  requirements: []
148
- rubygems_version: 3.2.33
148
+ rubygems_version: 3.3.26
149
149
  signing_key:
150
150
  specification_version: 4
151
151
  summary: Puma is a simple, fast, threaded, and highly parallel HTTP 1.1 server for