puma 3.12.1 → 4.1.0

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.

data/docs/systemd.md CHANGED
@@ -32,21 +32,26 @@ Type=simple
32
32
  # Preferably configure a non-privileged user
33
33
  # User=
34
34
 
35
- # The path to the puma application root
36
- # Also replace the "<WD>" place holders below with this path.
37
- WorkingDirectory=
35
+ # The path to the your application code root directory.
36
+ # Also replace the "<YOUR_APP_PATH>" place holders below with this path.
37
+ # Example /home/username/myapp
38
+ WorkingDirectory=<YOUR_APP_PATH>
38
39
 
39
40
  # Helpful for debugging socket activation, etc.
40
41
  # Environment=PUMA_DEBUG=1
41
42
 
42
- # The command to start Puma. This variant uses a binstub generated via
43
- # `bundle binstubs puma --path ./sbin` in the WorkingDirectory
44
- # (replace "<WD>" below)
45
- ExecStart=<WD>/sbin/puma -b tcp://0.0.0.0:9292 -b ssl://0.0.0.0:9293?key=key.pem&cert=cert.pem
43
+ # SystemD will not run puma even if it is in your path. You must specify
44
+ # an absolute URL to puma. For example /usr/local/bin/puma
45
+ # Alternatively, create a binstub with `bundle binstubs puma --path ./sbin` in the WorkingDirectory
46
+ ExecStart=/<FULLPATH>/bin/puma -C <YOUR_APP_PATH>/puma.rb
47
+
48
+ # Variant: Rails start.
49
+ # ExecStart=/<FULLPATH>/bin/puma -C <YOUR_APP_PATH>/config/puma.rb ../config.ru
46
50
 
47
- # Variant: Use config file with `bind` directives instead:
48
- # ExecStart=<WD>/sbin/puma -C config.rb
49
51
  # Variant: Use `bundle exec --keep-file-descriptors puma` instead of binstub
52
+ # Variant: Specify directives inline.
53
+ # ExecStart=/<FULLPATH>/puma -b tcp://0.0.0.0:9292 -b ssl://0.0.0.0:9293?key=key.pem&cert=cert.pem
54
+
50
55
 
51
56
  Restart=always
52
57
 
@@ -66,6 +71,13 @@ listening sockets open across puma restarts and achieves graceful
66
71
  restarts, including when upgraded puma, and is compatible with both
67
72
  clustered mode and application preload.
68
73
 
74
+ **Note:** Any wrapper scripts which `exec`, or other indirections in
75
+ `ExecStart`, may result in activated socket file descriptors being closed
76
+ before they reach the puma master process. For example, if using `bundle exec`,
77
+ pass the `--keep-file-descriptors` flag. `bundle exec` can be avoided by using a
78
+ `puma` executable generated by `bundle binstubs puma`. This is tracked in
79
+ [#1499].
80
+
69
81
  **Note:** Socket activation doesn't currently work on jruby. This is
70
82
  tracked in [#1367].
71
83
 
@@ -247,6 +259,12 @@ PIDFile=<WD>/shared/tmp/pids/puma.pid
247
259
  # reconsider if you actually need the forking config.
248
260
  Restart=no
249
261
 
262
+ # `puma_ctl restart` wouldn't work without this. It's because `pumactl`
263
+ # changes PID on restart and systemd stops the service afterwards
264
+ # because of the PID change. This option prevents stopping after PID
265
+ # change.
266
+ RemainAfterExit=yes
267
+
250
268
  [Install]
251
269
  WantedBy=multi-user.target
252
270
  ~~~~
@@ -6,11 +6,13 @@ import org.jruby.Ruby;
6
6
  import org.jruby.runtime.load.BasicLibraryService;
7
7
 
8
8
  import org.jruby.puma.Http11;
9
+ import org.jruby.puma.IOBuffer;
9
10
  import org.jruby.puma.MiniSSL;
10
11
 
11
12
  public class PumaHttp11Service implements BasicLibraryService {
12
13
  public boolean basicLoad(final Ruby runtime) throws IOException {
13
14
  Http11.createHttp11(runtime);
15
+ IOBuffer.createIOBuffer(runtime);
14
16
  MiniSSL.createMiniSSL(runtime);
15
17
  return true;
16
18
  }
@@ -9,6 +9,14 @@ unless ENV["DISABLE_SSL"]
9
9
  %w'ssl ssleay32'.find {|ssl| have_library(ssl, 'SSL_CTX_new')}
10
10
 
11
11
  have_header "openssl/bio.h"
12
+
13
+ # below is yes for 1.0.2 & later
14
+ have_func "DTLS_method" , "openssl/ssl.h"
15
+
16
+ # below are yes for 1.1.0 & later, may need to check func rather than macro
17
+ # with versions after 1.1.1
18
+ have_func "TLS_server_method" , "openssl/ssl.h"
19
+ have_macro "SSL_CTX_set_min_proto_version", "openssl/ssl.h"
12
20
  end
13
21
  end
14
22
 
@@ -142,6 +142,7 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
142
142
  VALUE obj;
143
143
  SSL_CTX* ctx;
144
144
  SSL* ssl;
145
+ int min, ssl_options;
145
146
 
146
147
  ms_conn* conn = engine_alloc(self, &obj);
147
148
 
@@ -164,7 +165,17 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
164
165
  ID sym_ssl_cipher_filter = rb_intern("ssl_cipher_filter");
165
166
  VALUE ssl_cipher_filter = rb_funcall(mini_ssl_ctx, sym_ssl_cipher_filter, 0);
166
167
 
168
+ ID sym_no_tlsv1 = rb_intern("no_tlsv1");
169
+ VALUE no_tlsv1 = rb_funcall(mini_ssl_ctx, sym_no_tlsv1, 0);
170
+
171
+ ID sym_no_tlsv1_1 = rb_intern("no_tlsv1_1");
172
+ VALUE no_tlsv1_1 = rb_funcall(mini_ssl_ctx, sym_no_tlsv1_1, 0);
173
+
174
+ #ifdef HAVE_TLS_SERVER_METHOD
175
+ ctx = SSL_CTX_new(TLS_server_method());
176
+ #else
167
177
  ctx = SSL_CTX_new(SSLv23_server_method());
178
+ #endif
168
179
  conn->ctx = ctx;
169
180
 
170
181
  SSL_CTX_use_certificate_chain_file(ctx, RSTRING_PTR(cert));
@@ -175,7 +186,36 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
175
186
  SSL_CTX_load_verify_locations(ctx, RSTRING_PTR(ca), NULL);
176
187
  }
177
188
 
178
- SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_SINGLE_DH_USE | SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_COMPRESSION);
189
+ ssl_options = SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_COMPRESSION;
190
+
191
+ #ifdef HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
192
+ if (RTEST(no_tlsv1_1)) {
193
+ min = TLS1_2_VERSION;
194
+ }
195
+ else if (RTEST(no_tlsv1)) {
196
+ min = TLS1_1_VERSION;
197
+ }
198
+ else {
199
+ min = TLS1_VERSION;
200
+ }
201
+
202
+ SSL_CTX_set_min_proto_version(ctx, min);
203
+
204
+ SSL_CTX_set_options(ctx, ssl_options);
205
+
206
+ #else
207
+ /* As of 1.0.2f, SSL_OP_SINGLE_DH_USE key use is always on */
208
+ ssl_options |= SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_SINGLE_DH_USE;
209
+
210
+ if (RTEST(no_tlsv1)) {
211
+ ssl_options |= SSL_OP_NO_TLSv1;
212
+ }
213
+ if(RTEST(no_tlsv1_1)) {
214
+ ssl_options |= SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1;
215
+ }
216
+ SSL_CTX_set_options(ctx, ssl_options);
217
+ #endif
218
+
179
219
  SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
180
220
 
181
221
  if (!NIL_P(ssl_cipher_filter)) {
@@ -189,12 +229,18 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
189
229
  DH *dh = get_dh1024();
190
230
  SSL_CTX_set_tmp_dh(ctx, dh);
191
231
 
192
- #ifndef OPENSSL_NO_ECDH
193
- EC_KEY *ecdh = EC_KEY_new_by_curve_name(NID_secp521r1);
232
+ #if OPENSSL_VERSION_NUMBER < 0x10002000L
233
+ // Remove this case if OpenSSL 1.0.1 (now EOL) support is no
234
+ // longer needed.
235
+ EC_KEY *ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
194
236
  if (ecdh) {
195
237
  SSL_CTX_set_tmp_ecdh(ctx, ecdh);
196
238
  EC_KEY_free(ecdh);
197
239
  }
240
+ #elif OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
241
+ // Prior to OpenSSL 1.1.0, servers must manually enable server-side ECDH
242
+ // negotiation.
243
+ SSL_CTX_set_ecdh_auto(ctx, 1);
198
244
  #endif
199
245
 
200
246
  ssl = SSL_new(ctx);
@@ -216,8 +262,11 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
216
262
  VALUE engine_init_client(VALUE klass) {
217
263
  VALUE obj;
218
264
  ms_conn* conn = engine_alloc(klass, &obj);
219
-
265
+ #ifdef HAVE_DTLS_METHOD
266
+ conn->ctx = SSL_CTX_new(DTLS_method());
267
+ #else
220
268
  conn->ctx = SSL_CTX_new(DTLSv1_method());
269
+ #endif
221
270
  conn->ssl = SSL_new(conn->ctx);
222
271
  SSL_set_app_data(conn->ssl, NULL);
223
272
  SSL_set_verify(conn->ssl, SSL_VERIFY_NONE, NULL);
@@ -436,14 +485,35 @@ void Init_mini_ssl(VALUE puma) {
436
485
  // OpenSSL Build / Runtime/Load versions
437
486
 
438
487
  /* Version of OpenSSL that Puma was compiled with */
439
- rb_define_const(mod, "OPENSSL_VERSION", rb_str_new2(OPENSSL_VERSION_TEXT));
488
+ rb_define_const(mod, "OPENSSL_VERSION", rb_str_new2(OPENSSL_VERSION_TEXT));
440
489
 
441
490
  #if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100000
442
- /* Version of OpenSSL that Puma loaded with */
443
- rb_define_const(mod, "OPENSSL_LIBRARY_VERSION", rb_str_new2(OpenSSL_version(OPENSSL_VERSION)));
491
+ /* Version of OpenSSL that Puma loaded with */
492
+ rb_define_const(mod, "OPENSSL_LIBRARY_VERSION", rb_str_new2(OpenSSL_version(OPENSSL_VERSION)));
444
493
  #else
445
- rb_define_const(mod, "OPENSSL_LIBRARY_VERSION", rb_str_new2(SSLeay_version(SSLEAY_VERSION)));
494
+ rb_define_const(mod, "OPENSSL_LIBRARY_VERSION", rb_str_new2(SSLeay_version(SSLEAY_VERSION)));
446
495
  #endif
496
+
497
+ #if defined(OPENSSL_NO_SSL3) || defined(OPENSSL_NO_SSL3_METHOD)
498
+ /* True if SSL3 is not available */
499
+ rb_define_const(mod, "OPENSSL_NO_SSL3", Qtrue);
500
+ #else
501
+ rb_define_const(mod, "OPENSSL_NO_SSL3", Qfalse);
502
+ #endif
503
+
504
+ #if defined(OPENSSL_NO_TLS1) || defined(OPENSSL_NO_TLS1_METHOD)
505
+ /* True if TLS1 is not available */
506
+ rb_define_const(mod, "OPENSSL_NO_TLS1", Qtrue);
507
+ #else
508
+ rb_define_const(mod, "OPENSSL_NO_TLS1", Qfalse);
509
+ #endif
510
+
511
+ #if defined(OPENSSL_NO_TLS1_1) || defined(OPENSSL_NO_TLS1_1_METHOD)
512
+ /* True if TLS1_1 is not available */
513
+ rb_define_const(mod, "OPENSSL_NO_TLS1_1", Qtrue);
514
+ #else
515
+ rb_define_const(mod, "OPENSSL_NO_TLS1_1", Qfalse);
516
+ #endif
447
517
 
448
518
  rb_define_singleton_method(mod, "check", noop, 0);
449
519
 
@@ -0,0 +1,72 @@
1
+ package org.jruby.puma;
2
+
3
+ import org.jruby.*;
4
+ import org.jruby.anno.JRubyMethod;
5
+ import org.jruby.runtime.ObjectAllocator;
6
+ import org.jruby.runtime.ThreadContext;
7
+ import org.jruby.runtime.builtin.IRubyObject;
8
+ import org.jruby.util.ByteList;
9
+
10
+ /**
11
+ * @author kares
12
+ */
13
+ public class IOBuffer extends RubyObject {
14
+
15
+ private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
16
+ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
17
+ return new IOBuffer(runtime, klass);
18
+ }
19
+ };
20
+
21
+ public static void createIOBuffer(Ruby runtime) {
22
+ RubyModule mPuma = runtime.defineModule("Puma");
23
+ RubyClass cIOBuffer = mPuma.defineClassUnder("IOBuffer", runtime.getObject(), ALLOCATOR);
24
+ cIOBuffer.defineAnnotatedMethods(IOBuffer.class);
25
+ }
26
+
27
+ private static final int DEFAULT_SIZE = 4096;
28
+
29
+ final ByteList buffer = new ByteList(DEFAULT_SIZE);
30
+
31
+ IOBuffer(Ruby runtime, RubyClass klass) {
32
+ super(runtime, klass);
33
+ }
34
+
35
+ @JRubyMethod
36
+ public RubyInteger used(ThreadContext context) {
37
+ return context.runtime.newFixnum(buffer.getRealSize());
38
+ }
39
+
40
+ @JRubyMethod
41
+ public RubyInteger capacity(ThreadContext context) {
42
+ return context.runtime.newFixnum(buffer.unsafeBytes().length);
43
+ }
44
+
45
+ @JRubyMethod
46
+ public IRubyObject reset() {
47
+ buffer.setRealSize(0);
48
+ return this;
49
+ }
50
+
51
+ @JRubyMethod(name = { "to_s", "to_str" })
52
+ public RubyString to_s(ThreadContext context) {
53
+ return RubyString.newStringShared(context.runtime, buffer.unsafeBytes(), 0, buffer.getRealSize());
54
+ }
55
+
56
+ @JRubyMethod(name = "<<")
57
+ public IRubyObject add(IRubyObject str) {
58
+ addImpl(str.convertToString());
59
+ return this;
60
+ }
61
+
62
+ @JRubyMethod(rest = true)
63
+ public IRubyObject append(IRubyObject[] strs) {
64
+ for (IRubyObject str : strs) addImpl(str.convertToString());
65
+ return this;
66
+ }
67
+
68
+ private void addImpl(RubyString str) {
69
+ buffer.append(str.getByteList());
70
+ }
71
+
72
+ }
@@ -23,6 +23,7 @@ import javax.net.ssl.SSLPeerUnverifiedException;
23
23
  import javax.net.ssl.SSLSession;
24
24
  import java.io.FileInputStream;
25
25
  import java.io.IOException;
26
+ import java.nio.Buffer;
26
27
  import java.nio.ByteBuffer;
27
28
  import java.security.KeyManagementException;
28
29
  import java.security.KeyStore;
@@ -65,7 +66,7 @@ public class MiniSSL extends RubyObject {
65
66
 
66
67
  public void clear() { buffer.clear(); }
67
68
  public void compact() { buffer.compact(); }
68
- public void flip() { buffer.flip(); }
69
+ public void flip() { ((Buffer) buffer).flip(); }
69
70
  public boolean hasRemaining() { return buffer.hasRemaining(); }
70
71
  public int position() { return buffer.position(); }
71
72
 
@@ -89,7 +90,7 @@ public class MiniSSL extends RubyObject {
89
90
  public void resize(int newCapacity) {
90
91
  if (newCapacity > buffer.capacity()) {
91
92
  ByteBuffer dstTmp = ByteBuffer.allocate(newCapacity);
92
- buffer.flip();
93
+ flip();
93
94
  dstTmp.put(buffer);
94
95
  buffer = dstTmp;
95
96
  } else {
@@ -101,7 +102,7 @@ public class MiniSSL extends RubyObject {
101
102
  * Drains the buffer to a ByteList, or returns null for an empty buffer
102
103
  */
103
104
  public ByteList asByteList() {
104
- buffer.flip();
105
+ flip();
105
106
  if (!buffer.hasRemaining()) {
106
107
  buffer.clear();
107
108
  return null;
@@ -158,7 +159,17 @@ public class MiniSSL extends RubyObject {
158
159
  sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
159
160
  engine = sslCtx.createSSLEngine();
160
161
 
161
- String[] protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" };
162
+ String[] protocols;
163
+ if(miniSSLContext.callMethod(threadContext, "no_tlsv1").isTrue()) {
164
+ protocols = new String[] { "TLSv1.1", "TLSv1.2" };
165
+ } else {
166
+ protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" };
167
+ }
168
+
169
+ if(miniSSLContext.callMethod(threadContext, "no_tlsv1_1").isTrue()) {
170
+ protocols = new String[] { "TLSv1.2" };
171
+ }
172
+
162
173
  engine.setEnabledProtocols(protocols);
163
174
  engine.setUseClientMode(false);
164
175
 
data/lib/puma.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Standard libraries
2
4
  require 'socket'
3
5
  require 'tempfile'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'openssl'
2
4
 
3
5
  module OpenSSL
@@ -1,5 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
1
5
  module Puma
2
6
  module App
7
+ # Check out {#call}'s source code to see what actions this web application
8
+ # can respond to.
3
9
  class Status
4
10
  def initialize(cli)
5
11
  @cli = cli
@@ -60,8 +66,7 @@ module Puma
60
66
  return rack_response(200, OK_STATUS)
61
67
 
62
68
  when /\/gc-stats$/
63
- json = "{" + GC.stat.map { |k, v| "\"#{k}\": #{v}" }.join(",") + "}"
64
- return rack_response(200, json)
69
+ return rack_response(200, GC.stat.to_json)
65
70
 
66
71
  when /\/stats$/
67
72
  return rack_response(200, @cli.stats)
data/lib/puma/binder.rb CHANGED
@@ -50,7 +50,14 @@ module Puma
50
50
 
51
51
  def close
52
52
  @ios.each { |i| i.close }
53
- @unix_paths.each { |i| File.unlink i }
53
+ @unix_paths.each do |i|
54
+ # Errno::ENOENT is intermittently raised
55
+ begin
56
+ unix_socket = UNIXSocket.new i
57
+ unix_socket.close
58
+ rescue Errno::ENOENT
59
+ end
60
+ end
54
61
  end
55
62
 
56
63
  def import_from_env
@@ -187,6 +194,9 @@ module Puma
187
194
  ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
188
195
  end
189
196
 
197
+ ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
198
+ ctx.no_tlsv1_1 = true if params['no_tlsv1_1'] == 'true'
199
+
190
200
  if params['verify_mode']
191
201
  ctx.verify_mode = case params['verify_mode']
192
202
  when "peer"
data/lib/puma/client.rb CHANGED
@@ -27,9 +27,10 @@ module Puma
27
27
  # For example a web request from a browser or from CURL. This
28
28
  #
29
29
  # An instance of `Puma::Client` can be used as if it were an IO object
30
- # for example it is passed into `IO.select` inside of the `Puma::Reactor`.
31
- # This is accomplished by the `to_io` method which gets called on any
32
- # non-IO objects being used with the IO api such as `IO.select.
30
+ # by the reactor, that's because the latter is expected to call `#to_io`
31
+ # on any non-IO objects it polls. For example nio4r internally calls
32
+ # `IO::try_convert` (which may call `#to_io`) when a new socket is
33
+ # registered.
33
34
  #
34
35
  # Instances of this class are responsible for knowing if
35
36
  # the header and body are fully buffered via the `try_to_finish` method.
@@ -54,6 +55,7 @@ module Puma
54
55
  @ready = false
55
56
 
56
57
  @body = nil
58
+ @body_read_start = nil
57
59
  @buffer = nil
58
60
  @tempfile = nil
59
61
 
@@ -64,6 +66,10 @@ module Puma
64
66
 
65
67
  @peerip = nil
66
68
  @remote_addr_header = nil
69
+
70
+ @body_remain = 0
71
+
72
+ @in_last_chunk = false
67
73
  end
68
74
 
69
75
  attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
@@ -102,6 +108,9 @@ module Puma
102
108
  @tempfile = nil
103
109
  @parsed_bytes = 0
104
110
  @ready = false
111
+ @body_remain = 0
112
+ @peerip = nil
113
+ @in_last_chunk = false
105
114
 
106
115
  if @buffer
107
116
  @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
@@ -114,9 +123,16 @@ module Puma
114
123
  end
115
124
 
116
125
  return false
117
- elsif fast_check &&
118
- IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
119
- return try_to_finish
126
+ else
127
+ begin
128
+ if fast_check &&
129
+ IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
130
+ return try_to_finish
131
+ end
132
+ rescue IOError
133
+ # swallow it
134
+ end
135
+
120
136
  end
121
137
  end
122
138
 
@@ -147,10 +163,13 @@ module Puma
147
163
  def decode_chunk(chunk)
148
164
  if @partial_part_left > 0
149
165
  if @partial_part_left <= chunk.size
150
- @body << chunk[0..(@partial_part_left-3)] # skip the \r\n
166
+ if @partial_part_left > 2
167
+ @body << chunk[0..(@partial_part_left-3)] # skip the \r\n
168
+ end
151
169
  chunk = chunk[@partial_part_left..-1]
170
+ @partial_part_left = 0
152
171
  else
153
- @body << chunk
172
+ @body << chunk if @partial_part_left > 2 # don't include the last \r\n
154
173
  @partial_part_left -= chunk.size
155
174
  return false
156
175
  end
@@ -168,13 +187,20 @@ module Puma
168
187
  if line.end_with?("\r\n")
169
188
  len = line.strip.to_i(16)
170
189
  if len == 0
190
+ @in_last_chunk = true
171
191
  @body.rewind
172
192
  rest = io.read
173
- rest = rest[2..-1] if rest.start_with?("\r\n")
174
- @buffer = rest.empty? ? nil : rest
175
- @requests_served += 1
176
- @ready = true
177
- return true
193
+ last_crlf_size = "\r\n".bytesize
194
+ if rest.bytesize < last_crlf_size
195
+ @buffer = nil
196
+ @partial_part_left = last_crlf_size - rest.bytesize
197
+ return false
198
+ else
199
+ @buffer = rest[last_crlf_size..-1]
200
+ @buffer = nil if @buffer.empty?
201
+ set_ready
202
+ return true
203
+ end
178
204
  end
179
205
 
180
206
  len += 2
@@ -204,14 +230,19 @@ module Puma
204
230
  end
205
231
  end
206
232
 
207
- return false
233
+ if @in_last_chunk
234
+ set_ready
235
+ true
236
+ else
237
+ false
238
+ end
208
239
  end
209
240
 
210
241
  def read_chunked_body
211
242
  while true
212
243
  begin
213
244
  chunk = @io.read_nonblock(4096)
214
- rescue Errno::EAGAIN
245
+ rescue IO::WaitReadable
215
246
  return false
216
247
  rescue SystemCallError, IOError
217
248
  raise ConnectionError, "Connection error detected during read"
@@ -221,8 +252,7 @@ module Puma
221
252
  unless chunk
222
253
  @body.close
223
254
  @buffer = nil
224
- @requests_served += 1
225
- @ready = true
255
+ set_ready
226
256
  raise EOFError
227
257
  end
228
258
 
@@ -231,6 +261,8 @@ module Puma
231
261
  end
232
262
 
233
263
  def setup_body
264
+ @body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
265
+
234
266
  if @env[HTTP_EXPECT] == CONTINUE
235
267
  # TODO allow a hook here to check the headers before
236
268
  # going forward
@@ -255,8 +287,7 @@ module Puma
255
287
  unless cl
256
288
  @buffer = body.empty? ? nil : body
257
289
  @body = EmptyBody
258
- @requests_served += 1
259
- @ready = true
290
+ set_ready
260
291
  return true
261
292
  end
262
293
 
@@ -265,8 +296,7 @@ module Puma
265
296
  if remain <= 0
266
297
  @body = StringIO.new(body)
267
298
  @buffer = nil
268
- @requests_served += 1
269
- @ready = true
299
+ set_ready
270
300
  return true
271
301
  end
272
302
 
@@ -294,15 +324,14 @@ module Puma
294
324
  data = @io.read_nonblock(CHUNK_SIZE)
295
325
  rescue Errno::EAGAIN
296
326
  return false
297
- rescue SystemCallError, IOError
327
+ rescue SystemCallError, IOError, EOFError
298
328
  raise ConnectionError, "Connection error detected during read"
299
329
  end
300
330
 
301
331
  # No data means a closed socket
302
332
  unless data
303
333
  @buffer = nil
304
- @requests_served += 1
305
- @ready = true
334
+ set_ready
306
335
  raise EOFError
307
336
  end
308
337
 
@@ -338,8 +367,7 @@ module Puma
338
367
  # No data means a closed socket
339
368
  unless data
340
369
  @buffer = nil
341
- @requests_served += 1
342
- @ready = true
370
+ set_ready
343
371
  raise EOFError
344
372
  end
345
373
 
@@ -416,8 +444,7 @@ module Puma
416
444
  unless chunk
417
445
  @body.close
418
446
  @buffer = nil
419
- @requests_served += 1
420
- @ready = true
447
+ set_ready
421
448
  raise EOFError
422
449
  end
423
450
 
@@ -426,8 +453,7 @@ module Puma
426
453
  if remain <= 0
427
454
  @body.rewind
428
455
  @buffer = nil
429
- @requests_served += 1
430
- @ready = true
456
+ set_ready
431
457
  return true
432
458
  end
433
459
 
@@ -436,6 +462,14 @@ module Puma
436
462
  false
437
463
  end
438
464
 
465
+ def set_ready
466
+ if @body_read_start
467
+ @env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @body_read_start
468
+ end
469
+ @requests_served += 1
470
+ @ready = true
471
+ end
472
+
439
473
  def write_400
440
474
  begin
441
475
  @io << ERROR_400_RESPONSE