puma 5.0.0.beta1-java → 5.0.0.beta2-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: '09ef3cad103c15def59126249335a202c405d8dee7cdc57bc62552bcea8fba22'
4
- data.tar.gz: ce3bb06f5c36c12af2d699654f59f59ed6696e0139f8e518c1063315b7b476ae
3
+ metadata.gz: 35a07b62e45c13573cba434caa78d336c981104d65448bf2cba63cc7fe87ebe5
4
+ data.tar.gz: d05d3e5a89a5dec3f71a133024d596975bb5fb0a8b4e2a8c0342479f061132fc
5
5
  SHA512:
6
- metadata.gz: 0e8960a5b91862adc3ad60c801846c230de779c6c09e1151be1e22096721067a087e55f3b10055448feaa45950629348fab3ef836ca4aa3aebb9898797bfcd41
7
- data.tar.gz: ebd533e470660608154f34a13cdbda6513ac02062a1fcaaa1525c80dd0380cbbf524c09e5a7c985e484dc30f5d30080de1045d25aec4a0f1adfe4d1d7c2e42f5
6
+ metadata.gz: 18498ab4e516ea2b386a8515c5ced3f2f5572e72f6281c87433f24f69b0ad94b55db2466a8f77df24b611c054353641ecce50b397b9cd698370bf81e3ba66369
7
+ data.tar.gz: 82efab2a81aebbd2eb50de04f906bb5f607f83121fe484f31feacd09cd481433d3f008278e8dcaf844a9b8ed3915e5985fa1a11fcf6d4f556eef24c87e9c6b3d
data/History.md CHANGED
@@ -6,11 +6,12 @@
6
6
  * EXPERIMENTAL: Added `nakayoshi_fork` option. Reduce memory usage in preloaded cluster-mode apps by GCing before fork and compacting, where available. (#2093, #2256)
7
7
  * Added pumactl `thread-backtraces` command to print thread backtraces (#2054)
8
8
  * Added incrementing `requests_count` to `Puma.stats`. (#2106)
9
- * Increased maximum URI path length from 2048 to 8196 bytes (#2167)
9
+ * Increased maximum URI path length from 2048 to 8192 bytes (#2167, #2344)
10
10
  * `lowlevel_error_handler` is now called during a forced threadpool shutdown, and if a callable with 3 arguments is set, we now also pass the status code (#2203)
11
11
  * Faster phased restart and worker timeout (#2220)
12
12
  * Added `state_permission` to config DSL to set state file permissions (#2238)
13
13
  * Added `Puma.stats_hash`, which returns a stats in Hash instead of a JSON string (#2086, #2253)
14
+ * `rack.multithread` and `rack.multiprocess` now dynamically resolved by `max_thread` and `workers` respectively (#2288)
14
15
 
15
16
  * Deprecations, Removals and Breaking API Changes
16
17
  * `--control` has been removed. Use `--control-url` (#1487)
@@ -24,8 +25,12 @@
24
25
  * Daemonization has been removed without replacement. (#2170)
25
26
  * Changed #connected_port to #connected_ports (#2076)
26
27
  * Configuration: `environment` is read from `RAILS_ENV`, if `RACK_ENV` can't be found (#2022)
28
+ * Log binding on http:// for TCP bindings to make it clickable
27
29
 
28
30
  * Bugfixes
31
+ * Fix JSON loading issues on phased-restarts (#2269)
32
+ * Improve shutdown reliability (#2312, #2338)
33
+ * Close client http connections made to an ssl server with TLSv1.3 (#2116)
29
34
  * Do not set user_config to quiet by default to allow for file config (#2074)
30
35
  * Always close SSL connection in Puma::ControlCLI (#2211)
31
36
  * Windows update extconf.rb for use with ssp and varied Ruby/MSYS2 combinations (#2069)
@@ -44,6 +49,15 @@
44
49
  * Fix `UserFileDefaultOptions#fetch` to properly use `default` (#2233)
45
50
  * Improvements to `out_of_band` hook (#2234)
46
51
  * Prefer the rackup file specified by the CLI (#2225)
52
+ * Fix for spawning subprocesses with fork_worker option (#2267)
53
+ * Set `CONTENT_LENGTH` for chunked requests (#2287)
54
+ * JRuby - Add Puma::MiniSSL::Engine#init? and #teardown methods, run all SSL tests (#2317)
55
+ * Improve shutdown reliability (#2312)
56
+ * Resolve issue with threadpool waiting counter decrement when thread is killed
57
+ * Constrain rake-compiler version to 0.9.4 to fix `ClassNotFound` exception when using MiniSSL with Java8.
58
+ * Fix recursive `prune_bundler` (#2319).
59
+ * Ensure that TCP_CORK is usable
60
+ * Fix corner case when request body is chunked (#2326)
47
61
 
48
62
  * Refactor
49
63
  * Remove unused loader argument from Plugin initializer (#2095)
@@ -54,6 +68,14 @@
54
68
  * ThreadPool concurrency refactoring (#2220)
55
69
  * JSON parse cluster worker stats instead of regex (#2124)
56
70
  * Support parallel tests in verbose progress reporting (#2223)
71
+ * Refactor error handling in server accept loop (#2239)
72
+
73
+ ## 4.3.4/4.3.5 and 3.12.5/3.12.6 / 2020-05-22
74
+
75
+ Each patchlevel release contains a separate security fix. We recommend simply upgrading to 4.3.5/3.12.6.
76
+
77
+ * Security
78
+ * Fix: Fixed two separate HTTP smuggling vulnerabilities that used the Transfer-Encoding header. CVE-2020-11076 and CVE-2020-11077.
57
79
 
58
80
  ## 4.3.3 and 3.12.4 / 2020-02-28
59
81
 
data/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  [![Code Climate](https://codeclimate.com/github/puma/puma.svg)](https://codeclimate.com/github/puma/puma)
10
10
  [![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=puma&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=puma&package-manager=bundler&version-scheme=semver)
11
- [![StackOverflow](http://img.shields.io/badge/stackoverflow-Puma-blue.svg)]( http://stackoverflow.com/questions/tagged/puma )
11
+ [![StackOverflow](https://img.shields.io/badge/stackoverflow-Puma-blue.svg)]( https://stackoverflow.com/questions/tagged/puma )
12
12
 
13
13
  Puma is a **simple, fast, multi-threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications**.
14
14
 
@@ -27,7 +27,7 @@ $ gem install puma
27
27
  $ puma
28
28
  ```
29
29
 
30
- Without arguments, puma will look for a rackup (.ru) file in
30
+ Without arguments, puma will look for a rackup (.ru) file in
31
31
  working directory called `config.ru`.
32
32
 
33
33
  ## Frameworks
@@ -135,7 +135,7 @@ Preloading can’t be used with phased restart, since phased restart kills and r
135
135
  If puma encounters an error outside of the context of your application, it will respond with a 500 and a simple
136
136
  textual error message (see `lowlevel_error` in [this file](https://github.com/puma/puma/blob/master/lib/puma/server.rb)).
137
137
  You can specify custom behavior for this scenario. For example, you can report the error to your third-party
138
- error-tracking service (in this example, [rollbar](http://rollbar.com)):
138
+ error-tracking service (in this example, [rollbar](https://rollbar.com)):
139
139
 
140
140
  ```ruby
141
141
  lowlevel_error_handler do |e|
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- ![http://bit.ly/2iJuFky](images/puma-general-arch.png)
5
+ ![https://bit.ly/2iJuFky](images/puma-general-arch.png)
6
6
 
7
7
  Puma is a threaded web server, processing requests across a TCP or UNIX socket.
8
8
 
@@ -12,7 +12,7 @@ Clustered mode is shown/discussed here. Single mode is analogous to having a sin
12
12
 
13
13
  ## Connection pipeline
14
14
 
15
- ![http://bit.ly/2zwzhEK](images/puma-connection-flow.png)
15
+ ![https://bit.ly/2zwzhEK](images/puma-connection-flow.png)
16
16
 
17
17
  * Upon startup, Puma listens on a TCP or UNIX socket.
18
18
  * The backlog of this socket is configured (with a default of 1024), determining how many established but unaccepted connections can exist concurrently.
@@ -29,7 +29,7 @@ Clustered mode is shown/discussed here. Single mode is analogous to having a sin
29
29
 
30
30
  ### Disabling `queue_requests`
31
31
 
32
- ![http://bit.ly/2zxCJ1Z](images/puma-connection-flow-no-reactor.png)
32
+ ![https://bit.ly/2zxCJ1Z](images/puma-connection-flow-no-reactor.png)
33
33
 
34
34
  The `queue_requests` option is `true` by default, enabling the separate thread used to buffer requests as described above.
35
35
 
@@ -20,7 +20,10 @@ Welcome back!
20
20
  Puma was originally conceived as a thread-only webserver, but grew the ability to
21
21
  also use processes in version 2.
22
22
 
23
- Here are some rules of thumb:
23
+ To run puma in single mode (e.g. for a development environment) you will need to
24
+ set the number of workers to 0, anything above will run in cluster mode.
25
+
26
+ Here are some rules of thumb for cluster mode:
24
27
 
25
28
  ### MRI
26
29
 
@@ -66,7 +69,8 @@ thread to become available.
66
69
 
67
70
  * Have your upstream proxy set a header with the time it received the request:
68
71
  * nginx: `proxy_set_header X-Request-Start "${msec}";`
69
- * haproxy: `http-request set-header X-Request-Start "%t";`
72
+ * haproxy >= 1.9: `http-request set-header X-Request-Start t=%[date()]%[date_us()]`
73
+ * haproxy < 1.9: `http-request set-header X-Request-Start t=%[date()]`
70
74
  * In your Rack middleware, determine the amount of time elapsed since `X-Request-Start`.
71
75
  * To improve accuracy, you will want to subtract time spent waiting for slow clients:
72
76
  * `env['puma.request_body_wait']` contains the number of milliseconds Puma spent
@@ -1,8 +1,8 @@
1
- The [unix signal](http://en.wikipedia.org/wiki/Unix_signal) is a method of sending messages between [processes](http://en.wikipedia.org/wiki/Process_(computing)). When a signal is sent, the operating system interrupts the target process's normal flow of execution. There are standard signals that are used to stop a process but there are also custom signals that can be used for other purposes. This document is an attempt to list all supported signals that Puma will respond to. In general, signals need only be sent to the master process of a cluster.
1
+ The [unix signal](https://en.wikipedia.org/wiki/Unix_signal) is a method of sending messages between [processes](https://en.wikipedia.org/wiki/Process_(computing)). When a signal is sent, the operating system interrupts the target process's normal flow of execution. There are standard signals that are used to stop a process but there are also custom signals that can be used for other purposes. This document is an attempt to list all supported signals that Puma will respond to. In general, signals need only be sent to the master process of a cluster.
2
2
 
3
3
  ## Sending Signals
4
4
 
5
- If you are new to signals it can be useful to see how they can be used. When a process is created in a *nix like operating system it will have a [PID - or process identifier](http://en.wikipedia.org/wiki/Process_identifier) that can be used to send signals to the process. For demonstration we will create an infinitely running process by tailing a file:
5
+ If you are new to signals it can be useful to see how they can be used. When a process is created in a *nix like operating system it will have a [PID - or process identifier](https://en.wikipedia.org/wiki/Process_identifier) that can be used to send signals to the process. For demonstration we will create an infinitely running process by tailing a file:
6
6
 
7
7
  ```sh
8
8
  $ echo "foo" >> my.log
@@ -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](http://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://www.ruby-doc.org/core-2.1.1/Process.html#kill-method):
21
21
 
22
22
  ```
23
23
  $ irb
24
24
  > puts pid
25
25
  => 87152
26
- Process.detach(pid) # http://ruby-doc.org/core-2.1.1/Process.html#method-c-detach
26
+ Process.detach(pid) # https://ruby-doc.org/core-2.1.1/Process.html#method-c-detach
27
27
  Process.kill("TERM", pid)
28
28
  ```
29
29
 
@@ -14,12 +14,14 @@
14
14
 
15
15
  /*
16
16
  * capitalizes all lower-case ASCII characters,
17
- * converts dashes to underscores.
17
+ * converts dashes to underscores, and underscores to commas.
18
18
  */
19
19
  static void snake_upcase_char(char *c)
20
20
  {
21
21
  if (*c >= 'a' && *c <= 'z')
22
22
  *c &= ~0x20;
23
+ else if (*c == '_')
24
+ *c = ',';
23
25
  else if (*c == '-')
24
26
  *c = '_';
25
27
  }
@@ -12,12 +12,14 @@
12
12
 
13
13
  /*
14
14
  * capitalizes all lower-case ASCII characters,
15
- * converts dashes to underscores.
15
+ * converts dashes to underscores, and underscores to commas.
16
16
  */
17
17
  static void snake_upcase_char(char *c)
18
18
  {
19
19
  if (*c >= 'a' && *c <= 'z')
20
20
  *c &= ~0x20;
21
+ else if (*c == '_')
22
+ *c = ',';
21
23
  else if (*c == '-')
22
24
  *c = '_';
23
25
  }
@@ -301,6 +301,7 @@ void raise_error(SSL* ssl, int result) {
301
301
  char msg[512];
302
302
  const char* err_str;
303
303
  int err = errno;
304
+ int mask = 4095;
304
305
  int ssl_err = SSL_get_error(ssl, result);
305
306
  int verify_err = (int) SSL_get_verify_result(ssl);
306
307
 
@@ -317,8 +318,8 @@ void raise_error(SSL* ssl, int result) {
317
318
  } else {
318
319
  err = (int) ERR_get_error();
319
320
  ERR_error_string_n(err, buf, sizeof(buf));
320
- snprintf(msg, sizeof(msg), "OpenSSL error: %s - %d", buf, err);
321
-
321
+ int errexp = err & mask;
322
+ snprintf(msg, sizeof(msg), "OpenSSL error: %s - %d", buf, errexp);
322
323
  }
323
324
  } else {
324
325
  snprintf(msg, sizeof(msg), "Unknown OpenSSL error: %d", ssl_err);
@@ -462,6 +463,13 @@ VALUE engine_peercert(VALUE self) {
462
463
  return rb_cert_buf;
463
464
  }
464
465
 
466
+ static VALUE
467
+ engine_ssl_vers_st(VALUE self) {
468
+ ms_conn* conn;
469
+ Data_Get_Struct(self, ms_conn, conn);
470
+ return rb_ary_new3(2, rb_str_new2(SSL_get_version(conn->ssl)), rb_str_new2(SSL_state_string(conn->ssl)));
471
+ }
472
+
465
473
  VALUE noop(VALUE self) {
466
474
  return Qnil;
467
475
  }
@@ -533,6 +541,8 @@ void Init_mini_ssl(VALUE puma) {
533
541
  rb_define_method(eng, "init?", engine_init, 0);
534
542
 
535
543
  rb_define_method(eng, "peercert", engine_peercert, 0);
544
+
545
+ rb_define_method(eng, "ssl_vers_st", engine_ssl_vers_st, 0);
536
546
  }
537
547
 
538
548
  #else
@@ -120,6 +120,8 @@ public class MiniSSL extends RubyObject {
120
120
  }
121
121
 
122
122
  private SSLEngine engine;
123
+ private boolean closed;
124
+ private boolean handshake;
123
125
  private MiniSSLBuffer inboundNetData;
124
126
  private MiniSSLBuffer outboundAppData;
125
127
  private MiniSSLBuffer outboundNetData;
@@ -157,6 +159,8 @@ public class MiniSSL extends RubyObject {
157
159
  SSLContext sslCtx = SSLContext.getInstance("TLS");
158
160
 
159
161
  sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
162
+ closed = false;
163
+ handshake = false;
160
164
  engine = sslCtx.createSSLEngine();
161
165
 
162
166
  String[] protocols;
@@ -173,7 +177,7 @@ public class MiniSSL extends RubyObject {
173
177
  engine.setEnabledProtocols(protocols);
174
178
  engine.setUseClientMode(false);
175
179
 
176
- long verify_mode = miniSSLContext.callMethod(threadContext, "verify_mode").convertToInteger().getLongValue();
180
+ long verify_mode = miniSSLContext.callMethod(threadContext, "verify_mode").convertToInteger("to_i").getLongValue();
177
181
  if ((verify_mode & 0x1) != 0) { // 'peer'
178
182
  engine.setWantClientAuth(true);
179
183
  }
@@ -240,14 +244,21 @@ public class MiniSSL extends RubyObject {
240
244
  // need to wait for more data to come in before we retry
241
245
  retryOp = false;
242
246
  break;
247
+ case CLOSED:
248
+ closed = true;
249
+ retryOp = false;
250
+ break;
243
251
  default:
244
- // other cases are OK and CLOSED. We're done here.
252
+ // other case is OK. We're done here.
245
253
  retryOp = false;
246
254
  }
255
+ if (res.getHandshakeStatus() == HandshakeStatus.FINISHED) {
256
+ handshake = true;
257
+ }
247
258
  }
248
259
 
249
260
  // after each op, run any delegated tasks if needed
250
- if(engine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
261
+ if(res.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
251
262
  Runnable runnable;
252
263
  while ((runnable = engine.getDelegatedTask()) != null) {
253
264
  runnable.run();
@@ -271,13 +282,14 @@ public class MiniSSL extends RubyObject {
271
282
 
272
283
  HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
273
284
  boolean done = false;
285
+ SSLEngineResult res = null;
274
286
  while (!done) {
275
287
  switch (handshakeStatus) {
276
288
  case NEED_WRAP:
277
- doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
289
+ res = doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
278
290
  break;
279
291
  case NEED_UNWRAP:
280
- SSLEngineResult res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
292
+ res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
281
293
  if (res.getStatus() == Status.BUFFER_UNDERFLOW) {
282
294
  // need more data before we can shake more hands
283
295
  done = true;
@@ -286,7 +298,9 @@ public class MiniSSL extends RubyObject {
286
298
  default:
287
299
  done = true;
288
300
  }
289
- handshakeStatus = engine.getHandshakeStatus();
301
+ if (!done) {
302
+ handshakeStatus = res.getHandshakeStatus();
303
+ }
290
304
  }
291
305
 
292
306
  if (inboundNetData.hasRemaining()) {
@@ -360,4 +374,21 @@ public class MiniSSL extends RubyObject {
360
374
  return getRuntime().getNil();
361
375
  }
362
376
  }
377
+
378
+ @JRubyMethod(name = "init?")
379
+ public IRubyObject isInit(ThreadContext context) {
380
+ return handshake ? getRuntime().getFalse() : getRuntime().getTrue();
381
+ }
382
+
383
+ @JRubyMethod
384
+ public IRubyObject shutdown() {
385
+ if (closed || engine.isInboundDone() && engine.isOutboundDone()) {
386
+ if (engine.isOutboundDone()) {
387
+ engine.closeOutbound();
388
+ }
389
+ return getRuntime().getTrue();
390
+ } else {
391
+ return getRuntime().getFalse();
392
+ }
393
+ }
363
394
  }
@@ -54,7 +54,7 @@ DEF_MAX_LENGTH(FIELD_NAME, 256);
54
54
  DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
55
55
  DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
56
56
  DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
57
- DEF_MAX_LENGTH(REQUEST_PATH, 8196);
57
+ DEF_MAX_LENGTH(REQUEST_PATH, 8192);
58
58
  DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
59
59
  DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
60
60
 
@@ -20,6 +20,7 @@ module Puma
20
20
  end
21
21
 
22
22
  def self.stats
23
+ require 'json'
23
24
  @get_stats.stats.to_json
24
25
  end
25
26
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
-
5
3
  module Puma
6
4
  module App
7
5
  # Check out {#call}'s source code to see what actions this web application
@@ -19,6 +17,10 @@ module Puma
19
17
  return rack_response(403, 'Invalid auth token', 'text/plain')
20
18
  end
21
19
 
20
+ if env['PATH_INFO'] =~ /\/(gc-stats|stats|thread-backtraces)$/
21
+ require 'json'
22
+ end
23
+
22
24
  case env['PATH_INFO']
23
25
  when /\/stop$/
24
26
  @cli.stop
@@ -6,6 +6,7 @@ require 'socket'
6
6
  require 'puma/const'
7
7
  require 'puma/util'
8
8
  require 'puma/minissl/context_builder'
9
+ require 'puma/configuration'
9
10
 
10
11
  module Puma
11
12
  class Binder
@@ -13,7 +14,7 @@ module Puma
13
14
 
14
15
  RACK_VERSION = [1,6].freeze
15
16
 
16
- def initialize(events)
17
+ def initialize(events, conf = Configuration.new)
17
18
  @events = events
18
19
  @listeners = []
19
20
  @inherited_fds = {}
@@ -23,8 +24,8 @@ module Puma
23
24
  @proto_env = {
24
25
  "rack.version".freeze => RACK_VERSION,
25
26
  "rack.errors".freeze => events.stderr,
26
- "rack.multithread".freeze => true,
27
- "rack.multiprocess".freeze => false,
27
+ "rack.multithread".freeze => conf.options[:max_threads] > 1,
28
+ "rack.multiprocess".freeze => conf.options[:workers] >= 1,
28
29
  "rack.run_once".freeze => false,
29
30
  "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
30
31
 
@@ -113,7 +114,7 @@ module Puma
113
114
  i.local_address.ip_unpack.join(':')
114
115
  end
115
116
 
116
- logger.log "* #{log_msg} on tcp://#{addr}"
117
+ logger.log "* #{log_msg} on http://#{addr}"
117
118
  end
118
119
  end
119
120
 
@@ -308,8 +308,16 @@ module Puma
308
308
 
309
309
  te = @env[TRANSFER_ENCODING2]
310
310
 
311
- if te && CHUNKED.casecmp(te) == 0
312
- return setup_chunked_body(body)
311
+ if te
312
+ if te.include?(",")
313
+ te.split(",").each do |part|
314
+ if CHUNKED.casecmp(part.strip) == 0
315
+ return setup_chunked_body(body)
316
+ end
317
+ end
318
+ elsif CHUNKED.casecmp(te) == 0
319
+ return setup_chunked_body(body)
320
+ end
313
321
  end
314
322
 
315
323
  @chunked_body = false
@@ -412,7 +420,10 @@ module Puma
412
420
  raise EOFError
413
421
  end
414
422
 
415
- return true if decode_chunk(chunk)
423
+ if decode_chunk(chunk)
424
+ @env[CONTENT_LENGTH] = @chunked_content_length
425
+ return true
426
+ end
416
427
  end
417
428
  end
418
429
 
@@ -424,20 +435,36 @@ module Puma
424
435
  @body = Tempfile.new(Const::PUMA_TMP_BASE)
425
436
  @body.binmode
426
437
  @tempfile = @body
438
+ @chunked_content_length = 0
439
+
440
+ if decode_chunk(body)
441
+ @env[CONTENT_LENGTH] = @chunked_content_length
442
+ return true
443
+ end
444
+ end
427
445
 
428
- return decode_chunk(body)
446
+ def write_chunk(str)
447
+ @chunked_content_length += @body.write(str)
429
448
  end
430
449
 
431
450
  def decode_chunk(chunk)
432
451
  if @partial_part_left > 0
433
452
  if @partial_part_left <= chunk.size
434
453
  if @partial_part_left > 2
435
- @body << chunk[0..(@partial_part_left-3)] # skip the \r\n
454
+ write_chunk(chunk[0..(@partial_part_left-3)]) # skip the \r\n
436
455
  end
437
456
  chunk = chunk[@partial_part_left..-1]
438
457
  @partial_part_left = 0
439
458
  else
440
- @body << chunk if @partial_part_left > 2 # don't include the last \r\n
459
+ if @partial_part_left > 2
460
+ if @partial_part_left == chunk.size + 1
461
+ # Don't include the last \r
462
+ write_chunk(chunk[0..(@partial_part_left-3)])
463
+ else
464
+ # don't include the last \r\n
465
+ write_chunk(chunk)
466
+ end
467
+ end
441
468
  @partial_part_left -= chunk.size
442
469
  return false
443
470
  end
@@ -484,12 +511,12 @@ module Puma
484
511
 
485
512
  case
486
513
  when got == len
487
- @body << part[0..-3] # to skip the ending \r\n
514
+ write_chunk(part[0..-3]) # to skip the ending \r\n
488
515
  when got <= len - 2
489
- @body << part
516
+ write_chunk(part)
490
517
  @partial_part_left = len - part.size
491
518
  when got == len - 1 # edge where we get just \r but not \n
492
- @body << part[0..-2]
519
+ write_chunk(part[0..-2])
493
520
  @partial_part_left = len - part.size
494
521
  end
495
522
  else
@@ -5,7 +5,6 @@ require 'puma/util'
5
5
  require 'puma/plugin'
6
6
 
7
7
  require 'time'
8
- require 'json'
9
8
 
10
9
  module Puma
11
10
  # This class is instantiated by the `Puma::Launcher` and used
@@ -95,6 +94,7 @@ module Puma
95
94
 
96
95
  def ping!(status)
97
96
  @last_checkin = Time.now
97
+ require 'json'
98
98
  @last_status = JSON.parse(status, symbolize_names: true)
99
99
  end
100
100
 
@@ -248,6 +248,7 @@ module Puma
248
248
  $0 = title
249
249
 
250
250
  Signal.trap "SIGINT", "IGNORE"
251
+ Signal.trap "SIGCHLD", "DEFAULT"
251
252
 
252
253
  fork_worker = @options[:fork_worker] && index == 0
253
254
 
@@ -284,9 +285,11 @@ module Puma
284
285
 
285
286
  if fork_worker
286
287
  restart_server.clear
288
+ worker_pids = []
287
289
  Signal.trap "SIGCHLD" do
288
- Process.wait(-1, Process::WNOHANG) rescue nil
289
- wakeup!
290
+ wakeup! if worker_pids.reject! do |p|
291
+ Process.wait(p, Process::WNOHANG) rescue true
292
+ end
290
293
  end
291
294
 
292
295
  Thread.new do
@@ -303,7 +306,7 @@ module Puma
303
306
  elsif idx == 0 # restart server
304
307
  restart_server << true << false
305
308
  else # fork worker
306
- pid = spawn_worker(idx, master)
309
+ worker_pids << pid = spawn_worker(idx, master)
307
310
  @worker_write << "f#{pid}:#{idx}\n" rescue nil
308
311
  end
309
312
  end
@@ -330,6 +333,7 @@ module Puma
330
333
  while true
331
334
  sleep Const::WORKER_CHECK_INTERVAL
332
335
  begin
336
+ require 'json'
333
337
  io << "p#{Process.pid}#{server.stats.to_json}\n"
334
338
  rescue IOError
335
339
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
@@ -3,7 +3,7 @@
3
3
  module Puma
4
4
  # Rack::CommonLogger forwards every request to the given +app+, and
5
5
  # logs a line in the
6
- # {Apache common log format}[http://httpd.apache.org/docs/1.3/logs.html#common]
6
+ # {Apache common log format}[https://httpd.apache.org/docs/1.3/logs.html#common]
7
7
  # to the +logger+.
8
8
  #
9
9
  # If +logger+ is nil, CommonLogger will fall back +rack.errors+, which is
@@ -16,7 +16,7 @@ module Puma
16
16
  # (which is called without arguments in order to make the error appear for
17
17
  # sure)
18
18
  class CommonLogger
19
- # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
19
+ # Common Log Format: https://httpd.apache.org/docs/1.3/logs.html#common
20
20
  #
21
21
  # lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 -
22
22
  #
@@ -100,7 +100,7 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "5.0.0.beta1".freeze
103
+ PUMA_VERSION = VERSION = "5.0.0.beta2".freeze
104
104
  CODE_NAME = "Spoony Bard".freeze
105
105
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
106
106
 
@@ -443,8 +443,8 @@ module Puma
443
443
  #
444
444
  # @note Cluster mode only.
445
445
  # @example
446
- # on_worker_fork do
447
- # puts 'Before worker fork...'
446
+ # on_worker_boot do
447
+ # puts 'Before worker boot...'
448
448
  # end
449
449
  def on_worker_boot(&block)
450
450
  @options[:before_worker_boot] ||= []
@@ -769,7 +769,7 @@ module Puma
769
769
  # also increase time to boot and fork. See your logs for details on how much
770
770
  # time this adds to your boot process. For most apps, it will be less than one
771
771
  # second.
772
- def nakayoshi_fork(enabled=false)
772
+ def nakayoshi_fork(enabled=true)
773
773
  @options[:nakayoshi_fork] = enabled
774
774
  end
775
775
  end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma/const'
4
+
5
+ module Puma
6
+ # The implementation of a detailed error logging.
7
+ #
8
+ class ErrorLogger
9
+ include Const
10
+
11
+ attr_reader :ioerr
12
+
13
+ REQUEST_FORMAT = %{"%s %s%s" - (%s)}
14
+
15
+ def initialize(ioerr)
16
+ @ioerr = ioerr
17
+ @ioerr.sync = true
18
+
19
+ @debug = ENV.key? 'PUMA_DEBUG'
20
+ end
21
+
22
+ def self.stdio
23
+ new $stderr
24
+ end
25
+
26
+ # Print occured error details.
27
+ # +options+ hash with additional options:
28
+ # - +error+ is an exception object
29
+ # - +req+ the http request
30
+ # - +text+ (default nil) custom string to print in title
31
+ # and before all remaining info.
32
+ #
33
+ def info(options={})
34
+ ioerr.puts title(options)
35
+ end
36
+
37
+ # Print occured error details only if
38
+ # environment variable PUMA_DEBUG is defined.
39
+ # +options+ hash with additional options:
40
+ # - +error+ is an exception object
41
+ # - +req+ the http request
42
+ # - +text+ (default nil) custom string to print in title
43
+ # and before all remaining info.
44
+ #
45
+ def debug(options={})
46
+ return unless @debug
47
+
48
+ error = options[:error]
49
+ req = options[:req]
50
+
51
+ string_block = []
52
+ string_block << title(options)
53
+ string_block << request_dump(req) if req
54
+ string_block << error_backtrace(options) if error
55
+
56
+ ioerr.puts string_block.join("\n")
57
+ end
58
+
59
+ def title(options={})
60
+ text = options[:text]
61
+ req = options[:req]
62
+ error = options[:error]
63
+
64
+ string_block = ["#{Time.now}"]
65
+ string_block << " #{text}" if text
66
+ string_block << " (#{request_title(req)})" if request_parsed?(req)
67
+ string_block << ": #{error.inspect}" if error
68
+ string_block.join('')
69
+ end
70
+
71
+ def request_dump(req)
72
+ "Headers: #{request_headers(req)}\n" \
73
+ "Body: #{req.body}"
74
+ end
75
+
76
+ def request_title(req)
77
+ env = req.env
78
+
79
+ REQUEST_FORMAT % [
80
+ env[REQUEST_METHOD],
81
+ env[REQUEST_PATH] || env[PATH_INFO],
82
+ env[QUERY_STRING] || "",
83
+ env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-"
84
+ ]
85
+ end
86
+
87
+ def request_headers(req)
88
+ headers = req.env.select { |key, _| key.start_with?('HTTP_') }
89
+ headers.map { |key, value| [key[5..-1], value] }.to_h.inspect
90
+ end
91
+
92
+ def request_parsed?(req)
93
+ req && req.env[REQUEST_METHOD]
94
+ end
95
+ end
96
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/const'
4
3
  require "puma/null_io"
4
+ require 'puma/error_logger'
5
5
  require 'stringio'
6
6
 
7
7
  module Puma
@@ -23,8 +23,6 @@ module Puma
23
23
  end
24
24
  end
25
25
 
26
- include Const
27
-
28
26
  # Create an Events object that prints to +stdout+ and +stderr+.
29
27
  #
30
28
  def initialize(stdout, stderr)
@@ -36,6 +34,7 @@ module Puma
36
34
  @stderr.sync = true
37
35
 
38
36
  @debug = ENV.key? 'PUMA_DEBUG'
37
+ @error_logger = ErrorLogger.new(@stderr)
39
38
 
40
39
  @hooks = Hash.new { |h,k| h[k] = [] }
41
40
  end
@@ -66,7 +65,8 @@ module Puma
66
65
  # Write +str+ to +@stdout+
67
66
  #
68
67
  def log(str)
69
- @stdout.puts format(str)
68
+ @stdout.puts format(str) if @stdout.respond_to? :puts
69
+ rescue Errno::EPIPE
70
70
  end
71
71
 
72
72
  def write(str)
@@ -80,7 +80,7 @@ module Puma
80
80
  # Write +str+ to +@stderr+
81
81
  #
82
82
  def error(str)
83
- @stderr.puts format("ERROR: #{str}")
83
+ @error_logger.info(text: format("ERROR: #{str}"))
84
84
  exit 1
85
85
  end
86
86
 
@@ -88,43 +88,45 @@ module Puma
88
88
  formatter.call(str)
89
89
  end
90
90
 
91
+ # An HTTP connection error has occurred.
92
+ # +error+ a connection exception, +req+ the request,
93
+ # and +text+ additional info
94
+ #
95
+ def connection_error(error, req, text="HTTP connection error")
96
+ @error_logger.info(error: error, req: req, text: text)
97
+ end
98
+
91
99
  # An HTTP parse error has occurred.
92
- # +server+ is the Server object, +env+ the request, and +error+ a
93
- # parsing exception.
100
+ # +error+ a parsing exception,
101
+ # and +req+ the request.
94
102
  #
95
- def parse_error(server, env, error)
96
- @stderr.puts "#{Time.now}: HTTP parse error, malformed request " \
97
- "(#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}#{env[REQUEST_PATH]}): " \
98
- "#{error.inspect}" \
99
- "\n---\n"
103
+ def parse_error(error, req)
104
+ @error_logger.info(error: error, req: req, text: 'HTTP parse error, malformed request')
100
105
  end
101
106
 
102
107
  # An SSL error has occurred.
103
- # +server+ is the Server object, +peeraddr+ peer address, +peercert+
104
- # any peer certificate (if present), and +error+ an exception object.
108
+ # +error+ an exception object, +peeraddr+ peer address,
109
+ # and +peercert+ any peer certificate (if present).
105
110
  #
106
- def ssl_error(server, peeraddr, peercert, error)
111
+ def ssl_error(error, peeraddr, peercert)
107
112
  subject = peercert ? peercert.subject : nil
108
- @stderr.puts "#{Time.now}: SSL error, peer: #{peeraddr}, peer cert: #{subject}, #{error.inspect}"
113
+ @error_logger.info(error: error, text: "SSL error, peer: #{peeraddr}, peer cert: #{subject}")
109
114
  end
110
115
 
111
116
  # An unknown error has occurred.
112
- # +server+ is the Server object, +error+ an exception object,
113
- # +kind+ some additional info, and +env+ the request.
117
+ # +error+ an exception object, +req+ the request,
118
+ # and +text+ additional info
114
119
  #
115
- def unknown_error(server, error, kind="Unknown", env=nil)
116
- if error.respond_to? :render
117
- error.render "#{Time.now}: #{kind} error", @stderr
118
- else
119
- if env
120
- string_block = [ "#{Time.now}: #{kind} error handling request { #{env['REQUEST_METHOD']} #{env['PATH_INFO']} }" ]
121
- string_block << error.inspect
122
- else
123
- string_block = [ "#{Time.now}: #{kind} error: #{error.inspect}" ]
124
- end
125
- string_block << error.backtrace
126
- @stderr.puts string_block.join("\n")
127
- end
120
+ def unknown_error(error, req=nil, text="Unknown error")
121
+ @error_logger.info(error: error, req: req, text: text)
122
+ end
123
+
124
+ # Log occurred error debug dump.
125
+ # +error+ an exception object, +req+ the request,
126
+ # and +text+ additional info
127
+ #
128
+ def debug_error(error, req=nil, text="")
129
+ @error_logger.debug(error: error, req: req, text: text)
128
130
  end
129
131
 
130
132
  def on_booted(&block)
@@ -47,7 +47,7 @@ module Puma
47
47
  @original_argv = @argv.dup
48
48
  @config = conf
49
49
 
50
- @binder = Binder.new(@events)
50
+ @binder = Binder.new(@events, conf)
51
51
  @binder.create_inherited_fds(ENV).each { |k| ENV.delete k }
52
52
  @binder.create_activated_fds(ENV).each { |k| ENV.delete k }
53
53
 
@@ -111,6 +111,7 @@ module Puma
111
111
  sf.pid = Process.pid
112
112
  sf.control_url = @options[:control_url]
113
113
  sf.control_auth_token = @options[:control_auth_token]
114
+ sf.running_from = File.expand_path('.')
114
115
 
115
116
  sf.save path, permission
116
117
  end
@@ -172,12 +173,13 @@ module Puma
172
173
  case @status
173
174
  when :halt
174
175
  log "* Stopping immediately!"
176
+ @runner.stop_control
175
177
  when :run, :stop
176
178
  graceful_stop
177
179
  when :restart
178
180
  log "* Restarting..."
179
181
  ENV.replace(previous_env)
180
- @runner.before_restart
182
+ @runner.stop_control
181
183
  restart!
182
184
  when :exit
183
185
  # nothing
@@ -286,6 +288,7 @@ module Puma
286
288
  end
287
289
 
288
290
  def prune_bundler
291
+ return if ENV['PUMA_BUNDLER_PRUNED']
289
292
  return unless defined?(Bundler)
290
293
  require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
291
294
  unless puma_wild_location
@@ -5,8 +5,18 @@ begin
5
5
  rescue LoadError
6
6
  end
7
7
 
8
+ # need for Puma::MiniSSL::OPENSSL constants used in `HAS_TLS1_3`
9
+ require 'puma/puma_http11'
10
+
8
11
  module Puma
9
12
  module MiniSSL
13
+
14
+ # define constant at runtime, as it's easy to determine at built time,
15
+ # but Puma could (it shouldn't) be loaded with an older OpenSSL version
16
+ HAS_TLS1_3 = !IS_JRUBY &&
17
+ (OPENSSL_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) != -1 &&
18
+ (OPENSSL_LIBRARY_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) !=-1
19
+
10
20
  class Socket
11
21
  def initialize(socket, engine)
12
22
  @socket = socket
@@ -22,6 +32,24 @@ module Puma
22
32
  @socket.closed?
23
33
  end
24
34
 
35
+ # returns a two element array
36
+ # first is protocol version (SSL_get_version)
37
+ # second is 'handshake' state (SSL_state_string)
38
+ #
39
+ # used for dropping tcp connections to ssl
40
+ # see OpenSSL ssl/ssl_stat.c SSL_state_string for info
41
+ #
42
+ def ssl_version_state
43
+ IS_JRUBY ? [nil, nil] : @engine.ssl_vers_st
44
+ end
45
+
46
+ # used to check the handshake status, in particular when a TCP connection
47
+ # is made with TLSv1.3 as an available protocol
48
+ def bad_tlsv1_3?
49
+ HAS_TLS1_3 && @engine.ssl_vers_st == ['TLSv1.3', 'SSLERR']
50
+ end
51
+ private :bad_tlsv1_3?
52
+
25
53
  def readpartial(size)
26
54
  while true
27
55
  output = @engine.read
@@ -41,6 +69,7 @@ module Puma
41
69
 
42
70
  def engine_read_all
43
71
  output = @engine.read
72
+ raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
44
73
  while output and additional_output = @engine.read
45
74
  output << additional_output
46
75
  end
@@ -167,7 +196,10 @@ module Puma
167
196
  end
168
197
  end
169
198
 
170
- if defined?(JRUBY_VERSION)
199
+ if IS_JRUBY
200
+ OPENSSL_NO_SSL3 = false
201
+ OPENSSL_NO_TLS1 = false
202
+
171
203
  class SSLError < StandardError
172
204
  # Define this for jruby even though it isn't used.
173
205
  end
@@ -184,7 +216,7 @@ module Puma
184
216
  @no_tlsv1_1 = false
185
217
  end
186
218
 
187
- if defined?(JRUBY_VERSION)
219
+ if IS_JRUBY
188
220
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
189
221
  attr_reader :keystore
190
222
  attr_accessor :keystore_pass
Binary file
@@ -252,7 +252,7 @@ module Puma
252
252
  c.close
253
253
  clear_monitor mon
254
254
 
255
- @events.ssl_error @server, addr, cert, e
255
+ @events.ssl_error e, addr, cert
256
256
 
257
257
  # The client doesn't know HTTP well
258
258
  rescue HttpParserError => e
@@ -263,7 +263,7 @@ module Puma
263
263
 
264
264
  clear_monitor mon
265
265
 
266
- @events.parse_error @server, c.env, e
266
+ @events.parse_error e, c
267
267
  rescue StandardError => e
268
268
  @server.lowlevel_error(e, c.env)
269
269
 
@@ -30,7 +30,7 @@ module Puma
30
30
  @events.log str
31
31
  end
32
32
 
33
- def before_restart
33
+ def stop_control
34
34
  @control.stop(true) if @control
35
35
  end
36
36
 
@@ -98,10 +98,22 @@ module Puma
98
98
  @binder = bind
99
99
  end
100
100
 
101
+ class << self
102
+ # :nodoc:
103
+ def tcp_cork_supported?
104
+ RbConfig::CONFIG['host_os'] =~ /linux/ &&
105
+ Socket.const_defined?(:IPPROTO_TCP) &&
106
+ Socket.const_defined?(:TCP_CORK) &&
107
+ Socket.const_defined?(:SOL_TCP) &&
108
+ Socket.const_defined?(:TCP_INFO)
109
+ end
110
+ private :tcp_cork_supported?
111
+ end
112
+
101
113
  # On Linux, use TCP_CORK to better control how the TCP stack
102
114
  # packetizes our stream. This improves both latency and throughput.
103
115
  #
104
- if RUBY_PLATFORM =~ /linux/
116
+ if tcp_cork_supported?
105
117
  UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
106
118
 
107
119
  # 6 == Socket::IPPROTO_TCP
@@ -109,7 +121,7 @@ module Puma
109
121
  # 1/0 == turn on/off
110
122
  def cork_socket(socket)
111
123
  begin
112
- socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
124
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if socket.kind_of? TCPSocket
113
125
  rescue IOError, SystemCallError
114
126
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
115
127
  end
@@ -117,7 +129,7 @@ module Puma
117
129
 
118
130
  def uncork_socket(socket)
119
131
  begin
120
- socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
132
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if socket.kind_of? TCPSocket
121
133
  rescue IOError, SystemCallError
122
134
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
123
135
  end
@@ -207,14 +219,16 @@ module Puma
207
219
 
208
220
  client.close
209
221
 
210
- @events.ssl_error self, addr, cert, e
222
+ @events.ssl_error e, addr, cert
211
223
  rescue HttpParserError => e
212
224
  client.write_error(400)
213
225
  client.close
214
226
 
215
- @events.parse_error self, client.env, e
216
- rescue ConnectionError, EOFError
227
+ @events.parse_error e, client
228
+ rescue ConnectionError, EOFError => e
217
229
  client.close
230
+
231
+ @events.connection_error e, client
218
232
  else
219
233
  if process_now
220
234
  process_client client, buffer
@@ -281,35 +295,26 @@ module Puma
281
295
  if sock == check
282
296
  break if handle_check
283
297
  else
284
- begin
285
- pool.wait_until_not_full
286
- pool.wait_for_less_busy_worker(
287
- @options[:wait_for_less_busy_worker].to_f)
288
-
289
- if io = sock.accept_nonblock
290
- client = Client.new io, @binder.env(sock)
291
- if remote_addr_value
292
- client.peerip = remote_addr_value
293
- elsif remote_addr_header
294
- client.remote_addr_header = remote_addr_header
295
- end
296
-
297
- pool << client
298
- end
299
- rescue SystemCallError
300
- # nothing
301
- rescue Errno::ECONNABORTED
302
- # client closed the socket even before accept
303
- begin
304
- io.close
305
- rescue
306
- Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
307
- end
298
+ pool.wait_until_not_full
299
+ pool.wait_for_less_busy_worker(
300
+ @options[:wait_for_less_busy_worker].to_f)
301
+
302
+ io = begin
303
+ sock.accept_nonblock
304
+ rescue IO::WaitReadable
305
+ next
306
+ end
307
+ client = Client.new io, @binder.env(sock)
308
+ if remote_addr_value
309
+ client.peerip = remote_addr_value
310
+ elsif remote_addr_header
311
+ client.remote_addr_header = remote_addr_header
308
312
  end
313
+ pool << client
309
314
  end
310
315
  end
311
316
  rescue Object => e
312
- @events.unknown_error self, e, "Listen loop"
317
+ @events.unknown_error e, nil, "Listen loop"
313
318
  end
314
319
  end
315
320
 
@@ -322,10 +327,14 @@ module Puma
322
327
  end
323
328
  graceful_shutdown if @status == :stop || @status == :restart
324
329
  rescue Exception => e
325
- STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
326
- STDERR.puts e.backtrace
330
+ @events.unknown_error e, nil, "Exception handling servers"
327
331
  ensure
328
- @check.close unless @check.closed? # Ruby 2.2 issue
332
+ begin
333
+ @check.close unless @check.closed?
334
+ rescue Errno::EBADF, RuntimeError
335
+ # RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
336
+ # Errno::EBADF is infrequently raised
337
+ end
329
338
  @notify.close
330
339
  @notify = nil
331
340
  @check = nil
@@ -415,7 +424,7 @@ module Puma
415
424
 
416
425
  close_socket = true
417
426
 
418
- @events.ssl_error self, addr, cert, e
427
+ @events.ssl_error e, addr, cert
419
428
 
420
429
  # The client doesn't know HTTP well
421
430
  rescue HttpParserError => e
@@ -423,7 +432,7 @@ module Puma
423
432
 
424
433
  client.write_error(400)
425
434
 
426
- @events.parse_error self, client.env, e
435
+ @events.parse_error e, client
427
436
 
428
437
  # Server error
429
438
  rescue StandardError => e
@@ -431,8 +440,7 @@ module Puma
431
440
 
432
441
  client.write_error(500)
433
442
 
434
- @events.unknown_error self, e, "Read"
435
-
443
+ @events.unknown_error e, nil, "Read"
436
444
  ensure
437
445
  buffer.reset
438
446
 
@@ -442,7 +450,7 @@ module Puma
442
450
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
443
451
  # Already closed
444
452
  rescue StandardError => e
445
- @events.unknown_error self, e, "Client"
453
+ @events.unknown_error e, nil, "Client"
446
454
  end
447
455
  end
448
456
  end
@@ -478,7 +486,7 @@ module Puma
478
486
 
479
487
  env[PATH_INFO] = env[REQUEST_PATH]
480
488
 
481
- # From http://www.ietf.org/rfc/rfc3875 :
489
+ # From https://www.ietf.org/rfc/rfc3875 :
482
490
  # "Script authors should be aware that the REMOTE_ADDR and
483
491
  # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
484
492
  # may not identify the ultimate source of the request.
@@ -567,12 +575,44 @@ module Puma
567
575
  end
568
576
 
569
577
  fast_write client, "\r\n".freeze
570
- rescue ConnectionError
578
+ rescue ConnectionError => e
579
+ @events.debug_error e
571
580
  # noop, if we lost the socket we just won't send the early hints
572
581
  end
573
582
  }
574
583
  end
575
584
 
585
+ # Fixup any headers with , in the name to have _ now. We emit
586
+ # headers with , in them during the parse phase to avoid ambiguity
587
+ # with the - to _ conversion for critical headers. But here for
588
+ # compatibility, we'll convert them back. This code is written to
589
+ # avoid allocation in the common case (ie there are no headers
590
+ # with , in their names), that's why it has the extra conditionals.
591
+
592
+ to_delete = nil
593
+ to_add = nil
594
+
595
+ env.each do |k,v|
596
+ if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
597
+ if to_delete
598
+ to_delete << k
599
+ else
600
+ to_delete = [k]
601
+ end
602
+
603
+ unless to_add
604
+ to_add = {}
605
+ end
606
+
607
+ to_add[k.tr(",", "_")] = v
608
+ end
609
+ end
610
+
611
+ if to_delete
612
+ to_delete.each { |k| env.delete(k) }
613
+ env.merge! to_add
614
+ end
615
+
576
616
  # A rack extension. If the app writes #call'ables to this
577
617
  # array, we will invoke them when the request is done.
578
618
  #
@@ -594,12 +634,12 @@ module Puma
594
634
  return :async
595
635
  end
596
636
  rescue ThreadPool::ForceShutdown => e
597
- @events.unknown_error self, e, "Rack app", env
637
+ @events.unknown_error e, req, "Rack app"
598
638
  @events.log "Detected force shutdown of a thread"
599
639
 
600
640
  status, headers, res_body = lowlevel_error(e, env, 503)
601
641
  rescue Exception => e
602
- @events.unknown_error self, e, "Rack app", env
642
+ @events.unknown_error e, req, "Rack app"
603
643
 
604
644
  status, headers, res_body = lowlevel_error(e, env, 500)
605
645
  end
@@ -889,7 +929,7 @@ module Puma
889
929
  @check, @notify = Puma::Util.pipe unless @notify
890
930
  begin
891
931
  @notify << message
892
- rescue IOError
932
+ rescue IOError, NoMethodError, Errno::EPIPE
893
933
  # The server, in another thread, is shutting down
894
934
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
895
935
  rescue RuntimeError => e
@@ -19,7 +19,7 @@ module Puma
19
19
  @options = YAML.load File.read(path)
20
20
  end
21
21
 
22
- FIELDS = %w!control_url control_auth_token pid!
22
+ FIELDS = %w!control_url control_auth_token pid running_from!
23
23
 
24
24
  FIELDS.each do |f|
25
25
  define_method f do
@@ -122,8 +122,11 @@ module Puma
122
122
  @out_of_band_pending = false
123
123
  end
124
124
  not_full.signal
125
- not_empty.wait mutex
126
- @waiting -= 1
125
+ begin
126
+ not_empty.wait mutex
127
+ ensure
128
+ @waiting -= 1
129
+ end
127
130
  end
128
131
 
129
132
  work = todo.shift
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puma
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.0.beta1
4
+ version: 5.0.0.beta2
5
5
  platform: java
6
6
  authors:
7
7
  - Evan Phoenix
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-12 00:00:00.000000000 Z
11
+ date: 2020-09-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -86,6 +86,7 @@ files:
86
86
  - lib/puma/control_cli.rb
87
87
  - lib/puma/detect.rb
88
88
  - lib/puma/dsl.rb
89
+ - lib/puma/error_logger.rb
89
90
  - lib/puma/events.rb
90
91
  - lib/puma/io_buffer.rb
91
92
  - lib/puma/jruby_restart.rb
@@ -109,13 +110,13 @@ files:
109
110
  - lib/rack/handler/puma.rb
110
111
  - tools/Dockerfile
111
112
  - tools/trickletest.rb
112
- homepage: http://puma.io
113
+ homepage: https://puma.io
113
114
  licenses:
114
115
  - BSD-3-Clause
115
116
  metadata:
116
117
  bug_tracker_uri: https://github.com/puma/puma/issues
117
118
  changelog_uri: https://github.com/puma/puma/blob/master/History.md
118
- homepage_uri: http://puma.io
119
+ homepage_uri: https://puma.io
119
120
  source_code_uri: https://github.com/puma/puma
120
121
  post_install_message:
121
122
  rdoc_options: []