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.
- checksums.yaml +4 -4
- data/History.md +1413 -439
- data/LICENSE +23 -20
- data/README.md +131 -60
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +24 -19
- data/docs/compile_options.md +19 -0
- data/docs/deployment.md +38 -13
- data/docs/fork_worker.md +33 -0
- data/docs/jungle/README.md +9 -0
- data/{tools → docs}/jungle/rc.d/README.md +1 -1
- data/{tools → docs}/jungle/rc.d/puma +2 -2
- data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +66 -0
- data/docs/nginx.md +1 -1
- data/docs/plugins.md +20 -10
- data/docs/rails_dev_mode.md +29 -0
- data/docs/restart.md +47 -22
- data/docs/signals.md +7 -6
- data/docs/stats.md +142 -0
- data/docs/systemd.md +48 -70
- data/ext/puma_http11/PumaHttp11Service.java +2 -2
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +27 -0
- data/ext/puma_http11/http11_parser.c +84 -109
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +22 -38
- data/ext/puma_http11/http11_parser.rl +4 -2
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/ext/puma_http11/mini_ssl.c +262 -87
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +89 -106
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +92 -22
- data/ext/puma_http11/puma_http11.c +34 -50
- data/lib/puma/app/status.rb +68 -49
- data/lib/puma/binder.rb +197 -144
- data/lib/puma/cli.rb +17 -15
- data/lib/puma/client.rb +257 -226
- data/lib/puma/cluster/worker.rb +176 -0
- data/lib/puma/cluster/worker_handle.rb +90 -0
- data/lib/puma/cluster.rb +223 -212
- data/lib/puma/commonlogger.rb +4 -2
- data/lib/puma/configuration.rb +58 -51
- data/lib/puma/const.rb +41 -19
- data/lib/puma/control_cli.rb +117 -73
- data/lib/puma/detect.rb +26 -3
- data/lib/puma/dsl.rb +531 -123
- data/lib/puma/error_logger.rb +104 -0
- data/lib/puma/events.rb +57 -31
- data/lib/puma/io_buffer.rb +9 -5
- data/lib/puma/jruby_restart.rb +2 -58
- data/lib/puma/json.rb +96 -0
- data/lib/puma/launcher.rb +182 -70
- data/lib/puma/minissl/context_builder.rb +79 -0
- data/lib/puma/minissl.rb +149 -48
- data/lib/puma/null_io.rb +15 -1
- data/lib/puma/plugin/tmp_restart.rb +2 -0
- data/lib/puma/plugin.rb +8 -12
- data/lib/puma/queue_close.rb +26 -0
- data/lib/puma/rack/builder.rb +4 -5
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +87 -316
- data/lib/puma/request.rb +456 -0
- data/lib/puma/runner.rb +33 -52
- data/lib/puma/server.rb +288 -679
- data/lib/puma/single.rb +13 -67
- data/lib/puma/state_file.rb +10 -3
- data/lib/puma/systemd.rb +46 -0
- data/lib/puma/thread_pool.rb +131 -81
- data/lib/puma/util.rb +14 -6
- data/lib/puma.rb +54 -0
- data/lib/rack/handler/puma.rb +8 -6
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +45 -29
- data/ext/puma_http11/io_buffer.c +0 -155
- data/lib/puma/accept_nonblock.rb +0 -23
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/convenient.rb +0 -23
- data/lib/puma/daemon_ext.rb +0 -31
- data/lib/puma/delegation.rb +0 -11
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
- data/lib/puma/tcp_logger.rb +0 -39
- data/tools/jungle/README.md +0 -19
- data/tools/jungle/init.d/README.md +0 -61
- data/tools/jungle/init.d/puma +0 -421
- data/tools/jungle/init.d/run-puma +0 -18
- data/tools/jungle/upstart/README.md +0 -61
- data/tools/jungle/upstart/puma-manager.conf +0 -31
- 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.
|
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
|
-
#
|
29
|
-
#
|
30
|
-
#
|
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
|
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
|
-
|
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 =
|
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
|
-
|
116
|
-
|
117
|
-
|
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
|
-
|
130
|
-
|
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
|
-
|
143
|
-
|
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
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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 @
|
158
|
-
|
184
|
+
if @buffer
|
185
|
+
@buffer << data
|
159
186
|
else
|
160
|
-
|
161
|
-
@prev_chunk = ""
|
187
|
+
@buffer = data
|
162
188
|
end
|
163
189
|
|
164
|
-
|
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
|
-
|
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
|
-
|
182
|
-
|
183
|
-
next
|
184
|
-
end
|
199
|
+
false
|
200
|
+
end
|
185
201
|
|
186
|
-
|
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
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
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
|
-
|
213
|
+
def timeout!
|
214
|
+
write_error(408) if in_data_phase
|
215
|
+
raise ConnectionError
|
205
216
|
end
|
206
217
|
|
207
|
-
def
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
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
|
-
|
218
|
-
|
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
|
-
|
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
|
245
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
437
|
-
|
438
|
-
|
439
|
-
|
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
|
444
|
-
|
445
|
-
|
446
|
-
|
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
|
-
|
451
|
-
|
452
|
-
|
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
|
458
|
-
|
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 @
|
461
|
-
|
462
|
-
|
463
|
-
|
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
|
-
|
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
|