puma 3.12.0 → 5.3.1

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.

Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1413 -439
  3. data/LICENSE +23 -20
  4. data/README.md +131 -60
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +24 -19
  7. data/docs/compile_options.md +19 -0
  8. data/docs/deployment.md +38 -13
  9. data/docs/fork_worker.md +33 -0
  10. data/docs/jungle/README.md +9 -0
  11. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  12. data/{tools → docs}/jungle/rc.d/puma +2 -2
  13. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  14. data/docs/kubernetes.md +66 -0
  15. data/docs/nginx.md +1 -1
  16. data/docs/plugins.md +20 -10
  17. data/docs/rails_dev_mode.md +29 -0
  18. data/docs/restart.md +47 -22
  19. data/docs/signals.md +7 -6
  20. data/docs/stats.md +142 -0
  21. data/docs/systemd.md +48 -70
  22. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  23. data/ext/puma_http11/ext_help.h +1 -1
  24. data/ext/puma_http11/extconf.rb +27 -0
  25. data/ext/puma_http11/http11_parser.c +84 -109
  26. data/ext/puma_http11/http11_parser.h +1 -1
  27. data/ext/puma_http11/http11_parser.java.rl +22 -38
  28. data/ext/puma_http11/http11_parser.rl +4 -2
  29. data/ext/puma_http11/http11_parser_common.rl +3 -3
  30. data/ext/puma_http11/mini_ssl.c +262 -87
  31. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  32. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  33. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +89 -106
  34. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +92 -22
  35. data/ext/puma_http11/puma_http11.c +34 -50
  36. data/lib/puma/app/status.rb +68 -49
  37. data/lib/puma/binder.rb +197 -144
  38. data/lib/puma/cli.rb +17 -15
  39. data/lib/puma/client.rb +257 -226
  40. data/lib/puma/cluster/worker.rb +176 -0
  41. data/lib/puma/cluster/worker_handle.rb +90 -0
  42. data/lib/puma/cluster.rb +223 -212
  43. data/lib/puma/commonlogger.rb +4 -2
  44. data/lib/puma/configuration.rb +58 -51
  45. data/lib/puma/const.rb +41 -19
  46. data/lib/puma/control_cli.rb +117 -73
  47. data/lib/puma/detect.rb +26 -3
  48. data/lib/puma/dsl.rb +531 -123
  49. data/lib/puma/error_logger.rb +104 -0
  50. data/lib/puma/events.rb +57 -31
  51. data/lib/puma/io_buffer.rb +9 -5
  52. data/lib/puma/jruby_restart.rb +2 -58
  53. data/lib/puma/json.rb +96 -0
  54. data/lib/puma/launcher.rb +182 -70
  55. data/lib/puma/minissl/context_builder.rb +79 -0
  56. data/lib/puma/minissl.rb +149 -48
  57. data/lib/puma/null_io.rb +15 -1
  58. data/lib/puma/plugin/tmp_restart.rb +2 -0
  59. data/lib/puma/plugin.rb +8 -12
  60. data/lib/puma/queue_close.rb +26 -0
  61. data/lib/puma/rack/builder.rb +4 -5
  62. data/lib/puma/rack/urlmap.rb +2 -0
  63. data/lib/puma/rack_default.rb +2 -0
  64. data/lib/puma/reactor.rb +87 -316
  65. data/lib/puma/request.rb +456 -0
  66. data/lib/puma/runner.rb +33 -52
  67. data/lib/puma/server.rb +288 -679
  68. data/lib/puma/single.rb +13 -67
  69. data/lib/puma/state_file.rb +10 -3
  70. data/lib/puma/systemd.rb +46 -0
  71. data/lib/puma/thread_pool.rb +131 -81
  72. data/lib/puma/util.rb +14 -6
  73. data/lib/puma.rb +54 -0
  74. data/lib/rack/handler/puma.rb +8 -6
  75. data/tools/Dockerfile +16 -0
  76. data/tools/trickletest.rb +0 -1
  77. metadata +45 -29
  78. data/ext/puma_http11/io_buffer.c +0 -155
  79. data/lib/puma/accept_nonblock.rb +0 -23
  80. data/lib/puma/compat.rb +0 -14
  81. data/lib/puma/convenient.rb +0 -23
  82. data/lib/puma/daemon_ext.rb +0 -31
  83. data/lib/puma/delegation.rb +0 -11
  84. data/lib/puma/java_io_buffer.rb +0 -45
  85. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  86. data/lib/puma/tcp_logger.rb +0 -39
  87. data/tools/jungle/README.md +0 -19
  88. data/tools/jungle/init.d/README.md +0 -61
  89. data/tools/jungle/init.d/puma +0 -421
  90. data/tools/jungle/init.d/run-puma +0 -18
  91. data/tools/jungle/upstart/README.md +0 -61
  92. data/tools/jungle/upstart/puma-manager.conf +0 -31
  93. data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/client.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class IO
2
4
  # We need to use this for a jruby work around on both 1.8 and 1.9.
3
5
  # So this either creates the constant (on 1.8), or harmlessly
@@ -7,8 +9,8 @@ class IO
7
9
  end
8
10
 
9
11
  require 'puma/detect'
10
- require 'puma/delegation'
11
12
  require 'tempfile'
13
+ require 'forwardable'
12
14
 
13
15
  if Puma::IS_JRUBY
14
16
  # We have to work around some OpenSSL buffer/io-readiness bugs
@@ -22,19 +24,24 @@ module Puma
22
24
  class ConnectionError < RuntimeError; end
23
25
 
24
26
  # An instance of this class represents a unique request from a client.
25
- # For example a web request from a browser or from CURL. This
27
+ # For example, this could be a web request from a browser or from CURL.
26
28
  #
27
29
  # An instance of `Puma::Client` can be used as if it were an IO object
28
- # for example it is passed into `IO.select` inside of the `Puma::Reactor`.
29
- # This is accomplished by the `to_io` method which gets called on any
30
- # non-IO objects being used with the IO api such as `IO.select.
30
+ # by the reactor. The reactor 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.
31
34
  #
32
35
  # Instances of this class are responsible for knowing if
33
36
  # the header and body are fully buffered via the `try_to_finish` method.
34
37
  # They can be used to "time out" a response via the `timeout_at` reader.
35
38
  class Client
39
+ # The object used for a request with no body. All requests with
40
+ # no body share this one object since it has no state.
41
+ EmptyBody = NullIO.new
42
+
36
43
  include Puma::Const
37
- extend Puma::Delegation
44
+ extend Forwardable
38
45
 
39
46
  def initialize(io, env=nil)
40
47
  @io = io
@@ -52,6 +59,7 @@ module Puma
52
59
  @ready = false
53
60
 
54
61
  @body = nil
62
+ @body_read_start = nil
55
63
  @buffer = nil
56
64
  @tempfile = nil
57
65
 
@@ -62,6 +70,10 @@ module Puma
62
70
 
63
71
  @peerip = nil
64
72
  @remote_addr_header = nil
73
+
74
+ @body_remain = 0
75
+
76
+ @in_last_chunk = false
65
77
  end
66
78
 
67
79
  attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
@@ -71,8 +83,15 @@ module Puma
71
83
 
72
84
  attr_accessor :remote_addr_header
73
85
 
74
- forward :closed?, :@io
86
+ def_delegators :@io, :closed?
75
87
 
88
+ # Test to see if io meets a bare minimum of functioning, @to_io needs to be
89
+ # used for MiniSSL::Socket
90
+ def io_ok?
91
+ @to_io.is_a?(::BasicSocket) && !closed?
92
+ end
93
+
94
+ # @!attribute [r] inspect
76
95
  def inspect
77
96
  "#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
78
97
  end
@@ -84,12 +103,18 @@ module Puma
84
103
  env[HIJACK_IO] ||= @io
85
104
  end
86
105
 
106
+ # @!attribute [r] in_data_phase
87
107
  def in_data_phase
88
108
  !@read_header
89
109
  end
90
110
 
91
111
  def set_timeout(val)
92
- @timeout_at = Time.now + val
112
+ @timeout_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + val
113
+ end
114
+
115
+ # Number of seconds until the timeout elapses.
116
+ def timeout
117
+ [@timeout_at - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0].max
93
118
  end
94
119
 
95
120
  def reset(fast_check=true)
@@ -100,6 +125,9 @@ module Puma
100
125
  @tempfile = nil
101
126
  @parsed_bytes = 0
102
127
  @ready = false
128
+ @body_remain = 0
129
+ @peerip = nil if @remote_addr_header
130
+ @in_last_chunk = false
103
131
 
104
132
  if @buffer
105
133
  @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
@@ -112,9 +140,16 @@ module Puma
112
140
  end
113
141
 
114
142
  return false
115
- elsif fast_check &&
116
- IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
117
- return try_to_finish
143
+ else
144
+ begin
145
+ if fast_check &&
146
+ IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
147
+ return try_to_finish
148
+ end
149
+ rescue IOError
150
+ # swallow it
151
+ end
152
+
118
153
  end
119
154
  end
120
155
 
@@ -126,108 +161,93 @@ module Puma
126
161
  end
127
162
  end
128
163
 
129
- # The object used for a request with no body. All requests with
130
- # no body share this one object since it has no state.
131
- EmptyBody = NullIO.new
132
-
133
- def setup_chunked_body(body)
134
- @chunked_body = true
135
- @partial_part_left = 0
136
- @prev_chunk = ""
137
-
138
- @body = Tempfile.new(Const::PUMA_TMP_BASE)
139
- @body.binmode
140
- @tempfile = @body
164
+ def try_to_finish
165
+ return read_body unless @read_header
141
166
 
142
- return decode_chunk(body)
143
- end
167
+ begin
168
+ data = @io.read_nonblock(CHUNK_SIZE)
169
+ rescue IO::WaitReadable
170
+ return false
171
+ rescue EOFError
172
+ # Swallow error, don't log
173
+ rescue SystemCallError, IOError
174
+ raise ConnectionError, "Connection error detected during read"
175
+ end
144
176
 
145
- def decode_chunk(chunk)
146
- if @partial_part_left > 0
147
- if @partial_part_left <= chunk.size
148
- @body << chunk[0..(@partial_part_left-3)] # skip the \r\n
149
- chunk = chunk[@partial_part_left..-1]
150
- else
151
- @body << chunk
152
- @partial_part_left -= chunk.size
153
- return false
154
- end
177
+ # No data means a closed socket
178
+ unless data
179
+ @buffer = nil
180
+ set_ready
181
+ raise EOFError
155
182
  end
156
183
 
157
- if @prev_chunk.empty?
158
- io = StringIO.new(chunk)
184
+ if @buffer
185
+ @buffer << data
159
186
  else
160
- io = StringIO.new(@prev_chunk+chunk)
161
- @prev_chunk = ""
187
+ @buffer = data
162
188
  end
163
189
 
164
- while !io.eof?
165
- line = io.gets
166
- if line.end_with?("\r\n")
167
- len = line.strip.to_i(16)
168
- if len == 0
169
- @body.rewind
170
- rest = io.read
171
- @buffer = rest.empty? ? nil : rest
172
- @requests_served += 1
173
- @ready = true
174
- return true
175
- end
176
-
177
- len += 2
190
+ @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
178
191
 
179
- part = io.read(len)
192
+ if @parser.finished?
193
+ return setup_body
194
+ elsif @parsed_bytes >= MAX_HEADER
195
+ raise HttpParserError,
196
+ "HEADER is longer than allowed, aborting client early."
197
+ end
180
198
 
181
- unless part
182
- @partial_part_left = len
183
- next
184
- end
199
+ false
200
+ end
185
201
 
186
- got = part.size
202
+ def eagerly_finish
203
+ return true if @ready
204
+ return false unless IO.select([@to_io], nil, nil, 0)
205
+ try_to_finish
206
+ end
187
207
 
188
- case
189
- when got == len
190
- @body << part[0..-3] # to skip the ending \r\n
191
- when got <= len - 2
192
- @body << part
193
- @partial_part_left = len - part.size
194
- when got == len - 1 # edge where we get just \r but not \n
195
- @body << part[0..-2]
196
- @partial_part_left = len - part.size
197
- end
198
- else
199
- @prev_chunk = line
200
- return false
201
- end
202
- end
208
+ def finish(timeout)
209
+ return if @ready
210
+ IO.select([@to_io], nil, nil, timeout) || timeout! until try_to_finish
211
+ end
203
212
 
204
- return false
213
+ def timeout!
214
+ write_error(408) if in_data_phase
215
+ raise ConnectionError
205
216
  end
206
217
 
207
- def read_chunked_body
208
- while true
209
- begin
210
- chunk = @io.read_nonblock(4096)
211
- rescue Errno::EAGAIN
212
- return false
213
- rescue SystemCallError, IOError
214
- raise ConnectionError, "Connection error detected during read"
215
- end
218
+ def write_error(status_code)
219
+ begin
220
+ @io << ERROR_RESPONSE[status_code]
221
+ rescue StandardError
222
+ end
223
+ end
216
224
 
217
- # No chunk means a closed socket
218
- unless chunk
219
- @body.close
220
- @buffer = nil
221
- @requests_served += 1
222
- @ready = true
223
- raise EOFError
224
- end
225
+ def peerip
226
+ return @peerip if @peerip
225
227
 
226
- return true if decode_chunk(chunk)
228
+ if @remote_addr_header
229
+ hdr = (@env[@remote_addr_header] || LOCALHOST_IP).split(/[\s,]/).first
230
+ @peerip = hdr
231
+ return hdr
227
232
  end
233
+
234
+ @peerip ||= @io.peeraddr.last
235
+ end
236
+
237
+ # Returns true if the persistent connection can be closed immediately
238
+ # without waiting for the configured idle/shutdown timeout.
239
+ # @version 5.0.0
240
+ #
241
+ def can_close?
242
+ # Allow connection to close if we're not in the middle of parsing a request.
243
+ @parsed_bytes == 0
228
244
  end
229
245
 
246
+ private
247
+
230
248
  def setup_body
249
+ @body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
250
+
231
251
  if @env[HTTP_EXPECT] == CONTINUE
232
252
  # TODO allow a hook here to check the headers before
233
253
  # going forward
@@ -241,8 +261,16 @@ module Puma
241
261
 
242
262
  te = @env[TRANSFER_ENCODING2]
243
263
 
244
- if te && CHUNKED.casecmp(te) == 0
245
- return setup_chunked_body(body)
264
+ if te
265
+ if te.include?(",")
266
+ te.split(",").each do |part|
267
+ if CHUNKED.casecmp(part.strip) == 0
268
+ return setup_chunked_body(body)
269
+ end
270
+ end
271
+ elsif CHUNKED.casecmp(te) == 0
272
+ return setup_chunked_body(body)
273
+ end
246
274
  end
247
275
 
248
276
  @chunked_body = false
@@ -252,8 +280,7 @@ module Puma
252
280
  unless cl
253
281
  @buffer = body.empty? ? nil : body
254
282
  @body = EmptyBody
255
- @requests_served += 1
256
- @ready = true
283
+ set_ready
257
284
  return true
258
285
  end
259
286
 
@@ -262,13 +289,13 @@ module Puma
262
289
  if remain <= 0
263
290
  @body = StringIO.new(body)
264
291
  @buffer = nil
265
- @requests_served += 1
266
- @ready = true
292
+ set_ready
267
293
  return true
268
294
  end
269
295
 
270
296
  if remain > MAX_BODY
271
297
  @body = Tempfile.new(Const::PUMA_TMP_BASE)
298
+ @body.unlink
272
299
  @body.binmode
273
300
  @tempfile = @body
274
301
  else
@@ -284,108 +311,6 @@ module Puma
284
311
  return false
285
312
  end
286
313
 
287
- def try_to_finish
288
- return read_body unless @read_header
289
-
290
- begin
291
- data = @io.read_nonblock(CHUNK_SIZE)
292
- rescue Errno::EAGAIN
293
- return false
294
- rescue SystemCallError, IOError
295
- raise ConnectionError, "Connection error detected during read"
296
- end
297
-
298
- # No data means a closed socket
299
- unless data
300
- @buffer = nil
301
- @requests_served += 1
302
- @ready = true
303
- raise EOFError
304
- end
305
-
306
- if @buffer
307
- @buffer << data
308
- else
309
- @buffer = data
310
- end
311
-
312
- @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
313
-
314
- if @parser.finished?
315
- return setup_body
316
- elsif @parsed_bytes >= MAX_HEADER
317
- raise HttpParserError,
318
- "HEADER is longer than allowed, aborting client early."
319
- end
320
-
321
- false
322
- end
323
-
324
- if IS_JRUBY
325
- def jruby_start_try_to_finish
326
- return read_body unless @read_header
327
-
328
- begin
329
- data = @io.sysread_nonblock(CHUNK_SIZE)
330
- rescue OpenSSL::SSL::SSLError => e
331
- return false if e.kind_of? IO::WaitReadable
332
- raise e
333
- end
334
-
335
- # No data means a closed socket
336
- unless data
337
- @buffer = nil
338
- @requests_served += 1
339
- @ready = true
340
- raise EOFError
341
- end
342
-
343
- if @buffer
344
- @buffer << data
345
- else
346
- @buffer = data
347
- end
348
-
349
- @parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
350
-
351
- if @parser.finished?
352
- return setup_body
353
- elsif @parsed_bytes >= MAX_HEADER
354
- raise HttpParserError,
355
- "HEADER is longer than allowed, aborting client early."
356
- end
357
-
358
- false
359
- end
360
-
361
- def eagerly_finish
362
- return true if @ready
363
-
364
- if @io.kind_of? OpenSSL::SSL::SSLSocket
365
- return true if jruby_start_try_to_finish
366
- end
367
-
368
- return false unless IO.select([@to_io], nil, nil, 0)
369
- try_to_finish
370
- end
371
-
372
- else
373
-
374
- def eagerly_finish
375
- return true if @ready
376
- return false unless IO.select([@to_io], nil, nil, 0)
377
- try_to_finish
378
- end
379
- end # IS_JRUBY
380
-
381
- def finish
382
- return true if @ready
383
- until try_to_finish
384
- IO.select([@to_io], nil, nil)
385
- end
386
- true
387
- end
388
-
389
314
  def read_body
390
315
  if @chunked_body
391
316
  return read_chunked_body
@@ -403,7 +328,7 @@ module Puma
403
328
 
404
329
  begin
405
330
  chunk = @io.read_nonblock(want)
406
- rescue Errno::EAGAIN
331
+ rescue IO::WaitReadable
407
332
  return false
408
333
  rescue SystemCallError, IOError
409
334
  raise ConnectionError, "Connection error detected during read"
@@ -413,8 +338,7 @@ module Puma
413
338
  unless chunk
414
339
  @body.close
415
340
  @buffer = nil
416
- @requests_served += 1
417
- @ready = true
341
+ set_ready
418
342
  raise EOFError
419
343
  end
420
344
 
@@ -423,8 +347,7 @@ module Puma
423
347
  if remain <= 0
424
348
  @body.rewind
425
349
  @buffer = nil
426
- @requests_served += 1
427
- @ready = true
350
+ set_ready
428
351
  return true
429
352
  end
430
353
 
@@ -433,37 +356,145 @@ module Puma
433
356
  false
434
357
  end
435
358
 
436
- def write_400
437
- begin
438
- @io << ERROR_400_RESPONSE
439
- rescue StandardError
359
+ def read_chunked_body
360
+ while true
361
+ begin
362
+ chunk = @io.read_nonblock(4096)
363
+ rescue IO::WaitReadable
364
+ return false
365
+ rescue SystemCallError, IOError
366
+ raise ConnectionError, "Connection error detected during read"
367
+ end
368
+
369
+ # No chunk means a closed socket
370
+ unless chunk
371
+ @body.close
372
+ @buffer = nil
373
+ set_ready
374
+ raise EOFError
375
+ end
376
+
377
+ if decode_chunk(chunk)
378
+ @env[CONTENT_LENGTH] = @chunked_content_length.to_s
379
+ return true
380
+ end
440
381
  end
441
382
  end
442
383
 
443
- def write_408
444
- begin
445
- @io << ERROR_408_RESPONSE
446
- rescue StandardError
384
+ def setup_chunked_body(body)
385
+ @chunked_body = true
386
+ @partial_part_left = 0
387
+ @prev_chunk = ""
388
+
389
+ @body = Tempfile.new(Const::PUMA_TMP_BASE)
390
+ @body.unlink
391
+ @body.binmode
392
+ @tempfile = @body
393
+ @chunked_content_length = 0
394
+
395
+ if decode_chunk(body)
396
+ @env[CONTENT_LENGTH] = @chunked_content_length.to_s
397
+ return true
447
398
  end
448
399
  end
449
400
 
450
- def write_500
451
- begin
452
- @io << ERROR_500_RESPONSE
453
- rescue StandardError
454
- end
401
+ # @version 5.0.0
402
+ def write_chunk(str)
403
+ @chunked_content_length += @body.write(str)
455
404
  end
456
405
 
457
- def peerip
458
- return @peerip if @peerip
406
+ def decode_chunk(chunk)
407
+ if @partial_part_left > 0
408
+ if @partial_part_left <= chunk.size
409
+ if @partial_part_left > 2
410
+ write_chunk(chunk[0..(@partial_part_left-3)]) # skip the \r\n
411
+ end
412
+ chunk = chunk[@partial_part_left..-1]
413
+ @partial_part_left = 0
414
+ else
415
+ if @partial_part_left > 2
416
+ if @partial_part_left == chunk.size + 1
417
+ # Don't include the last \r
418
+ write_chunk(chunk[0..(@partial_part_left-3)])
419
+ else
420
+ # don't include the last \r\n
421
+ write_chunk(chunk)
422
+ end
423
+ end
424
+ @partial_part_left -= chunk.size
425
+ return false
426
+ end
427
+ end
459
428
 
460
- if @remote_addr_header
461
- hdr = (@env[@remote_addr_header] || LOCALHOST_ADDR).split(/[\s,]/).first
462
- @peerip = hdr
463
- return hdr
429
+ if @prev_chunk.empty?
430
+ io = StringIO.new(chunk)
431
+ else
432
+ io = StringIO.new(@prev_chunk+chunk)
433
+ @prev_chunk = ""
464
434
  end
465
435
 
466
- @peerip ||= @io.peeraddr.last
436
+ while !io.eof?
437
+ line = io.gets
438
+ if line.end_with?("\r\n")
439
+ len = line.strip.to_i(16)
440
+ if len == 0
441
+ @in_last_chunk = true
442
+ @body.rewind
443
+ rest = io.read
444
+ last_crlf_size = "\r\n".bytesize
445
+ if rest.bytesize < last_crlf_size
446
+ @buffer = nil
447
+ @partial_part_left = last_crlf_size - rest.bytesize
448
+ return false
449
+ else
450
+ @buffer = rest[last_crlf_size..-1]
451
+ @buffer = nil if @buffer.empty?
452
+ set_ready
453
+ return true
454
+ end
455
+ end
456
+
457
+ len += 2
458
+
459
+ part = io.read(len)
460
+
461
+ unless part
462
+ @partial_part_left = len
463
+ next
464
+ end
465
+
466
+ got = part.size
467
+
468
+ case
469
+ when got == len
470
+ write_chunk(part[0..-3]) # to skip the ending \r\n
471
+ when got <= len - 2
472
+ write_chunk(part)
473
+ @partial_part_left = len - part.size
474
+ when got == len - 1 # edge where we get just \r but not \n
475
+ write_chunk(part[0..-2])
476
+ @partial_part_left = len - part.size
477
+ end
478
+ else
479
+ @prev_chunk = line
480
+ return false
481
+ end
482
+ end
483
+
484
+ if @in_last_chunk
485
+ set_ready
486
+ true
487
+ else
488
+ false
489
+ end
490
+ end
491
+
492
+ def set_ready
493
+ if @body_read_start
494
+ @env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @body_read_start
495
+ end
496
+ @requests_served += 1
497
+ @ready = true
467
498
  end
468
499
  end
469
500
  end