puma 4.3.12 → 5.0.0.beta1
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 +58 -41
- data/LICENSE +23 -20
- data/README.md +17 -11
- data/bin/puma-wild +0 -0
- data/docs/architecture.md +0 -0
- data/docs/deployment.md +3 -1
- data/docs/fork_worker.md +31 -0
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/jungle/README.md +13 -0
- data/{tools → docs}/jungle/rc.d/README.md +0 -0
- data/{tools → docs}/jungle/rc.d/puma +0 -0
- data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
- data/{tools → docs}/jungle/upstart/README.md +0 -0
- data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
- data/{tools → docs}/jungle/upstart/puma.conf +0 -0
- data/docs/nginx.md +0 -0
- data/docs/plugins.md +0 -0
- data/docs/restart.md +0 -0
- data/docs/signals.md +1 -0
- data/docs/systemd.md +1 -63
- data/ext/puma_http11/PumaHttp11Service.java +2 -4
- data/ext/puma_http11/ext_help.h +0 -0
- data/ext/puma_http11/extconf.rb +3 -10
- data/ext/puma_http11/http11_parser.c +11 -26
- data/ext/puma_http11/http11_parser.h +0 -0
- data/ext/puma_http11/http11_parser.java.rl +0 -0
- data/ext/puma_http11/http11_parser.rl +1 -3
- data/ext/puma_http11/http11_parser_common.rl +1 -1
- data/ext/puma_http11/mini_ssl.c +47 -82
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +46 -48
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +0 -0
- data/ext/puma_http11/puma_http11.c +2 -38
- data/lib/puma/accept_nonblock.rb +0 -0
- data/lib/puma/app/status.rb +16 -5
- data/lib/puma/binder.rb +62 -60
- data/lib/puma/cli.rb +7 -15
- data/lib/puma/client.rb +38 -78
- data/lib/puma/cluster.rb +179 -74
- data/lib/puma/commonlogger.rb +0 -0
- data/lib/puma/configuration.rb +30 -42
- data/lib/puma/const.rb +5 -8
- data/lib/puma/control_cli.rb +27 -17
- data/lib/puma/detect.rb +8 -0
- data/lib/puma/dsl.rb +70 -34
- data/lib/puma/events.rb +0 -0
- data/lib/puma/io_buffer.rb +9 -2
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/launcher.rb +41 -29
- data/lib/puma/minissl/context_builder.rb +0 -0
- data/lib/puma/minissl.rb +13 -8
- data/lib/puma/null_io.rb +1 -1
- data/lib/puma/plugin/tmp_restart.rb +0 -0
- data/lib/puma/plugin.rb +1 -10
- data/lib/puma/rack/builder.rb +0 -4
- data/lib/puma/rack/urlmap.rb +0 -0
- data/lib/puma/rack_default.rb +0 -0
- data/lib/puma/reactor.rb +6 -1
- data/lib/puma/runner.rb +5 -34
- data/lib/puma/server.rb +74 -206
- data/lib/puma/single.rb +7 -64
- data/lib/puma/state_file.rb +5 -2
- data/lib/puma/thread_pool.rb +85 -47
- data/lib/puma/util.rb +0 -0
- data/lib/puma.rb +4 -0
- data/lib/rack/handler/puma.rb +1 -3
- data/tools/{docker/Dockerfile → Dockerfile} +0 -0
- data/tools/trickletest.rb +0 -0
- metadata +20 -24
- data/docs/tcp_mode.md +0 -96
- data/ext/puma_http11/io_buffer.c +0 -155
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
- data/lib/puma/tcp_logger.rb +0 -41
- 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/lib/puma/binder.rb
CHANGED
@@ -11,7 +11,7 @@ module Puma
|
|
11
11
|
class Binder
|
12
12
|
include Puma::Const
|
13
13
|
|
14
|
-
RACK_VERSION = [1,
|
14
|
+
RACK_VERSION = [1,6].freeze
|
15
15
|
|
16
16
|
def initialize(events)
|
17
17
|
@events = events
|
@@ -43,7 +43,8 @@ module Puma
|
|
43
43
|
@ios = []
|
44
44
|
end
|
45
45
|
|
46
|
-
attr_reader :ios
|
46
|
+
attr_reader :ios, :listeners, :unix_paths, :proto_env, :envs, :activated_sockets, :inherited_fds
|
47
|
+
attr_writer :ios, :listeners
|
47
48
|
|
48
49
|
def env(sock)
|
49
50
|
@envs.fetch(sock, @proto_env)
|
@@ -53,40 +54,39 @@ module Puma
|
|
53
54
|
@ios.each { |i| i.close }
|
54
55
|
end
|
55
56
|
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
fd = num + 3
|
67
|
-
sock = TCPServer.for_fd(fd)
|
68
|
-
begin
|
69
|
-
key = [ :unix, Socket.unpack_sockaddr_un(sock.getsockname) ]
|
70
|
-
rescue ArgumentError
|
71
|
-
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
|
72
|
-
if addr =~ /\:/
|
73
|
-
addr = "[#{addr}]"
|
74
|
-
end
|
75
|
-
key = [ :tcp, addr, port ]
|
76
|
-
end
|
77
|
-
@activated_sockets[key] = sock
|
78
|
-
@events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
|
79
|
-
end
|
80
|
-
remove << k << 'LISTEN_PID'
|
81
|
-
end
|
82
|
-
end
|
57
|
+
def connected_ports
|
58
|
+
ios.map { |io| io.addr[1] }.uniq
|
59
|
+
end
|
60
|
+
|
61
|
+
def create_inherited_fds(env_hash)
|
62
|
+
env_hash.select {|k,v| k =~ /PUMA_INHERIT_\d+/}.each do |_k, v|
|
63
|
+
fd, url = v.split(":", 2)
|
64
|
+
@inherited_fds[url] = fd.to_i
|
65
|
+
end.keys # pass keys back for removal
|
66
|
+
end
|
83
67
|
|
84
|
-
|
85
|
-
|
68
|
+
# systemd socket activation.
|
69
|
+
# LISTEN_FDS = number of listening sockets. e.g. 2 means accept on 2 sockets w/descriptors 3 and 4.
|
70
|
+
# LISTEN_PID = PID of the service process, aka us
|
71
|
+
# see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
|
72
|
+
def create_activated_fds(env_hash)
|
73
|
+
return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
|
74
|
+
env_hash['LISTEN_FDS'].to_i.times do |index|
|
75
|
+
sock = TCPServer.for_fd(socket_activation_fd(index))
|
76
|
+
key = begin # Try to parse as a path
|
77
|
+
[:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
|
78
|
+
rescue ArgumentError # Try to parse as a port/ip
|
79
|
+
port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
|
80
|
+
addr = "[#{addr}]" if addr =~ /\:/
|
81
|
+
[:tcp, addr, port]
|
82
|
+
end
|
83
|
+
@activated_sockets[key] = sock
|
84
|
+
@events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
|
86
85
|
end
|
86
|
+
["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
|
87
87
|
end
|
88
88
|
|
89
|
-
def parse(binds, logger)
|
89
|
+
def parse(binds, logger, log_msg = 'Listening')
|
90
90
|
binds.each do |str|
|
91
91
|
uri = URI.parse str
|
92
92
|
case uri.scheme
|
@@ -113,7 +113,7 @@ module Puma
|
|
113
113
|
i.local_address.ip_unpack.join(':')
|
114
114
|
end
|
115
115
|
|
116
|
-
logger.log "*
|
116
|
+
logger.log "* #{log_msg} on tcp://#{addr}"
|
117
117
|
end
|
118
118
|
end
|
119
119
|
|
@@ -149,7 +149,7 @@ module Puma
|
|
149
149
|
end
|
150
150
|
|
151
151
|
io = add_unix_listener path, umask, mode, backlog
|
152
|
-
logger.log "*
|
152
|
+
logger.log "* #{log_msg} on #{str}"
|
153
153
|
end
|
154
154
|
|
155
155
|
@listeners << [str, io]
|
@@ -204,12 +204,6 @@ module Puma
|
|
204
204
|
end
|
205
205
|
end
|
206
206
|
|
207
|
-
def loopback_addresses
|
208
|
-
Socket.ip_address_list.select do |addrinfo|
|
209
|
-
addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
|
210
|
-
end.map { |addrinfo| addrinfo.ip_address }.uniq
|
211
|
-
end
|
212
|
-
|
213
207
|
# Tell the server to listen on host +host+, port +port+.
|
214
208
|
# If +optimize_for_latency+ is true (the default) then clients connecting
|
215
209
|
# will be optimized for latency over throughput.
|
@@ -226,20 +220,17 @@ module Puma
|
|
226
220
|
end
|
227
221
|
|
228
222
|
host = host[1..-2] if host and host[0..0] == '['
|
229
|
-
|
223
|
+
tcp_server = TCPServer.new(host, port)
|
230
224
|
if optimize_for_latency
|
231
|
-
|
225
|
+
tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
232
226
|
end
|
233
|
-
|
234
|
-
|
235
|
-
@connected_port = s.addr[1]
|
227
|
+
tcp_server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
|
228
|
+
tcp_server.listen backlog
|
236
229
|
|
237
|
-
@ios <<
|
238
|
-
|
230
|
+
@ios << tcp_server
|
231
|
+
tcp_server
|
239
232
|
end
|
240
233
|
|
241
|
-
attr_reader :connected_port
|
242
|
-
|
243
234
|
def inherit_tcp_listener(host, port, fd)
|
244
235
|
if fd.kind_of? TCPServer
|
245
236
|
s = fd
|
@@ -360,26 +351,37 @@ module Puma
|
|
360
351
|
end
|
361
352
|
|
362
353
|
def close_listeners
|
363
|
-
|
364
|
-
io.close
|
354
|
+
listeners.each do |l, io|
|
355
|
+
io.close unless io.closed? # Ruby 2.2 issue
|
365
356
|
uri = URI.parse(l)
|
366
357
|
next unless uri.scheme == 'unix'
|
367
358
|
unix_path = "#{uri.host}#{uri.path}"
|
368
|
-
File.unlink unix_path if
|
359
|
+
File.unlink unix_path if unix_paths.include? unix_path
|
369
360
|
end
|
370
361
|
end
|
371
362
|
|
372
|
-
def
|
373
|
-
|
363
|
+
def redirects_for_restart
|
364
|
+
redirects = listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
|
365
|
+
redirects[:close_others] = true
|
366
|
+
redirects
|
374
367
|
end
|
375
368
|
|
376
|
-
def
|
377
|
-
|
378
|
-
|
379
|
-
ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
|
380
|
-
redirects[io.to_i] = io.to_i
|
369
|
+
def redirects_for_restart_env
|
370
|
+
listeners.each_with_object({}).with_index do |(listen, memo), i|
|
371
|
+
memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
|
381
372
|
end
|
382
|
-
|
373
|
+
end
|
374
|
+
|
375
|
+
private
|
376
|
+
|
377
|
+
def loopback_addresses
|
378
|
+
Socket.ip_address_list.select do |addrinfo|
|
379
|
+
addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
|
380
|
+
end.map { |addrinfo| addrinfo.ip_address }.uniq
|
381
|
+
end
|
382
|
+
|
383
|
+
def socket_activation_fd(int)
|
384
|
+
int + 3 # 3 is the magic number you add to follow the SA protocol
|
383
385
|
end
|
384
386
|
end
|
385
387
|
end
|
data/lib/puma/cli.rb
CHANGED
@@ -80,7 +80,7 @@ module Puma
|
|
80
80
|
@launcher.run
|
81
81
|
end
|
82
82
|
|
83
|
-
|
83
|
+
private
|
84
84
|
def unsupported(str)
|
85
85
|
@events.error(str)
|
86
86
|
raise UnsupportedOption
|
@@ -112,21 +112,11 @@ module Puma
|
|
112
112
|
configure_control_url(arg)
|
113
113
|
end
|
114
114
|
|
115
|
-
# alias --control-url for backwards-compatibility
|
116
|
-
o.on "--control URL", "DEPRECATED alias for --control-url" do |arg|
|
117
|
-
configure_control_url(arg)
|
118
|
-
end
|
119
|
-
|
120
115
|
o.on "--control-token TOKEN",
|
121
116
|
"The token to use as authentication for the control server" do |arg|
|
122
117
|
@control_options[:auth_token] = arg
|
123
118
|
end
|
124
119
|
|
125
|
-
o.on "-d", "--daemon", "Daemonize the server into the background" do
|
126
|
-
user_config.daemonize
|
127
|
-
user_config.quiet
|
128
|
-
end
|
129
|
-
|
130
120
|
o.on "--debug", "Log lowlevel debugging information" do
|
131
121
|
user_config.debug
|
132
122
|
end
|
@@ -140,6 +130,12 @@ module Puma
|
|
140
130
|
user_config.environment arg
|
141
131
|
end
|
142
132
|
|
133
|
+
o.on "-f", "--fork-worker=[REQUESTS]", OptionParser::DecimalInteger,
|
134
|
+
"Fork new workers from existing worker. Cluster mode only",
|
135
|
+
"Auto-refork after REQUESTS (default 1000)" do |*args|
|
136
|
+
user_config.fork_worker(*args.compact)
|
137
|
+
end
|
138
|
+
|
143
139
|
o.on "-I", "--include PATH", "Specify $LOAD_PATH directories" do |arg|
|
144
140
|
$LOAD_PATH.unshift(*arg.split(':'))
|
145
141
|
end
|
@@ -192,10 +188,6 @@ module Puma
|
|
192
188
|
end
|
193
189
|
end
|
194
190
|
|
195
|
-
o.on "--tcp-mode", "Run the app in raw TCP mode instead of HTTP mode" do
|
196
|
-
user_config.tcp_mode!
|
197
|
-
end
|
198
|
-
|
199
191
|
o.on "--early-hints", "Enable early hints support" do
|
200
192
|
user_config.early_hints
|
201
193
|
end
|
data/lib/puma/client.rb
CHANGED
@@ -23,8 +23,6 @@ module Puma
|
|
23
23
|
|
24
24
|
class ConnectionError < RuntimeError; end
|
25
25
|
|
26
|
-
class HttpParserError501 < IOError; end
|
27
|
-
|
28
26
|
# An instance of this class represents a unique request from a client.
|
29
27
|
# For example, this could be a web request from a browser or from CURL.
|
30
28
|
#
|
@@ -37,21 +35,7 @@ module Puma
|
|
37
35
|
# Instances of this class are responsible for knowing if
|
38
36
|
# the header and body are fully buffered via the `try_to_finish` method.
|
39
37
|
# They can be used to "time out" a response via the `timeout_at` reader.
|
40
|
-
#
|
41
38
|
class Client
|
42
|
-
|
43
|
-
# this tests all values but the last, which must be chunked
|
44
|
-
ALLOWED_TRANSFER_ENCODING = %w[compress deflate gzip].freeze
|
45
|
-
|
46
|
-
# chunked body validation
|
47
|
-
CHUNK_SIZE_INVALID = /[^\h]/.freeze
|
48
|
-
CHUNK_VALID_ENDING = "\r\n".freeze
|
49
|
-
|
50
|
-
# Content-Length header value validation
|
51
|
-
CONTENT_LENGTH_VALUE_INVALID = /[^\d]/.freeze
|
52
|
-
|
53
|
-
TE_ERR_MSG = 'Invalid Transfer-Encoding'
|
54
|
-
|
55
39
|
# The object used for a request with no body. All requests with
|
56
40
|
# no body share this one object since it has no state.
|
57
41
|
EmptyBody = NullIO.new
|
@@ -254,12 +238,23 @@ module Puma
|
|
254
238
|
return false unless IO.select([@to_io], nil, nil, 0)
|
255
239
|
try_to_finish
|
256
240
|
end
|
241
|
+
|
242
|
+
# For documentation, see https://github.com/puma/puma/issues/1754
|
243
|
+
send(:alias_method, :jruby_eagerly_finish, :eagerly_finish)
|
257
244
|
end # IS_JRUBY
|
258
245
|
|
259
|
-
def finish
|
246
|
+
def finish(timeout)
|
260
247
|
return true if @ready
|
261
248
|
until try_to_finish
|
262
|
-
|
249
|
+
can_read = begin
|
250
|
+
IO.select([@to_io], nil, nil, timeout)
|
251
|
+
rescue ThreadPool::ForceShutdown
|
252
|
+
nil
|
253
|
+
end
|
254
|
+
unless can_read
|
255
|
+
write_error(408) if in_data_phase
|
256
|
+
raise ConnectionError
|
257
|
+
end
|
263
258
|
end
|
264
259
|
true
|
265
260
|
end
|
@@ -275,7 +270,7 @@ module Puma
|
|
275
270
|
return @peerip if @peerip
|
276
271
|
|
277
272
|
if @remote_addr_header
|
278
|
-
hdr = (@env[@remote_addr_header] ||
|
273
|
+
hdr = (@env[@remote_addr_header] || LOCALHOST_IP).split(/[\s,]/).first
|
279
274
|
@peerip = hdr
|
280
275
|
return hdr
|
281
276
|
end
|
@@ -283,6 +278,18 @@ module Puma
|
|
283
278
|
@peerip ||= @io.peeraddr.last
|
284
279
|
end
|
285
280
|
|
281
|
+
# Returns true if the persistent connection can be closed immediately
|
282
|
+
# without waiting for the configured idle/shutdown timeout.
|
283
|
+
def can_close?
|
284
|
+
# Allow connection to close if it's received at least one full request
|
285
|
+
# and hasn't received any data for a future request.
|
286
|
+
#
|
287
|
+
# From RFC 2616 section 8.1.4:
|
288
|
+
# Servers SHOULD always respond to at least one request per connection,
|
289
|
+
# if at all possible.
|
290
|
+
@requests_served > 0 && @parsed_bytes == 0
|
291
|
+
end
|
292
|
+
|
286
293
|
private
|
287
294
|
|
288
295
|
def setup_body
|
@@ -300,40 +307,16 @@ module Puma
|
|
300
307
|
body = @parser.body
|
301
308
|
|
302
309
|
te = @env[TRANSFER_ENCODING2]
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
te_ary = te_lwr.split ','
|
307
|
-
te_count = te_ary.count CHUNKED
|
308
|
-
te_valid = te_ary[0..-2].all? { |e| ALLOWED_TRANSFER_ENCODING.include? e }
|
309
|
-
if te_ary.last == CHUNKED && te_count == 1 && te_valid
|
310
|
-
@env.delete TRANSFER_ENCODING2
|
311
|
-
return setup_chunked_body body
|
312
|
-
elsif te_count >= 1
|
313
|
-
raise HttpParserError , "#{TE_ERR_MSG}, multiple chunked: '#{te}'"
|
314
|
-
elsif !te_valid
|
315
|
-
raise HttpParserError501, "#{TE_ERR_MSG}, unknown value: '#{te}'"
|
316
|
-
end
|
317
|
-
elsif te_lwr == CHUNKED
|
318
|
-
@env.delete TRANSFER_ENCODING2
|
319
|
-
return setup_chunked_body body
|
320
|
-
elsif ALLOWED_TRANSFER_ENCODING.include? te_lwr
|
321
|
-
raise HttpParserError , "#{TE_ERR_MSG}, single value must be chunked: '#{te}'"
|
322
|
-
else
|
323
|
-
raise HttpParserError501 , "#{TE_ERR_MSG}, unknown value: '#{te}'"
|
324
|
-
end
|
310
|
+
|
311
|
+
if te && CHUNKED.casecmp(te) == 0
|
312
|
+
return setup_chunked_body(body)
|
325
313
|
end
|
326
314
|
|
327
315
|
@chunked_body = false
|
328
316
|
|
329
317
|
cl = @env[CONTENT_LENGTH]
|
330
318
|
|
331
|
-
|
332
|
-
# cannot contain characters that are not \d
|
333
|
-
if cl =~ CONTENT_LENGTH_VALUE_INVALID
|
334
|
-
raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
|
335
|
-
end
|
336
|
-
else
|
319
|
+
unless cl
|
337
320
|
@buffer = body.empty? ? nil : body
|
338
321
|
@body = EmptyBody
|
339
322
|
set_ready
|
@@ -429,10 +412,7 @@ module Puma
|
|
429
412
|
raise EOFError
|
430
413
|
end
|
431
414
|
|
432
|
-
if decode_chunk(chunk)
|
433
|
-
@env[CONTENT_LENGTH] = @chunked_content_length
|
434
|
-
return true
|
435
|
-
end
|
415
|
+
return true if decode_chunk(chunk)
|
436
416
|
end
|
437
417
|
end
|
438
418
|
|
@@ -445,28 +425,19 @@ module Puma
|
|
445
425
|
@body.binmode
|
446
426
|
@tempfile = @body
|
447
427
|
|
448
|
-
|
449
|
-
|
450
|
-
if decode_chunk(body)
|
451
|
-
@env[CONTENT_LENGTH] = @chunked_content_length
|
452
|
-
return true
|
453
|
-
end
|
454
|
-
end
|
455
|
-
|
456
|
-
def write_chunk(str)
|
457
|
-
@chunked_content_length += @body.write(str)
|
428
|
+
return decode_chunk(body)
|
458
429
|
end
|
459
430
|
|
460
431
|
def decode_chunk(chunk)
|
461
432
|
if @partial_part_left > 0
|
462
433
|
if @partial_part_left <= chunk.size
|
463
434
|
if @partial_part_left > 2
|
464
|
-
|
435
|
+
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
|
465
436
|
end
|
466
437
|
chunk = chunk[@partial_part_left..-1]
|
467
438
|
@partial_part_left = 0
|
468
439
|
else
|
469
|
-
|
440
|
+
@body << chunk if @partial_part_left > 2 # don't include the last \r\n
|
470
441
|
@partial_part_left -= chunk.size
|
471
442
|
return false
|
472
443
|
end
|
@@ -482,13 +453,7 @@ module Puma
|
|
482
453
|
while !io.eof?
|
483
454
|
line = io.gets
|
484
455
|
if line.end_with?("\r\n")
|
485
|
-
|
486
|
-
# present, which is the reason for the semicolon regex
|
487
|
-
chunk_hex = line.strip[/\A[^;]+/]
|
488
|
-
if chunk_hex =~ CHUNK_SIZE_INVALID
|
489
|
-
raise HttpParserError, "Invalid chunk size: '#{chunk_hex}'"
|
490
|
-
end
|
491
|
-
len = chunk_hex.to_i(16)
|
456
|
+
len = line.strip.to_i(16)
|
492
457
|
if len == 0
|
493
458
|
@in_last_chunk = true
|
494
459
|
@body.rewind
|
@@ -519,17 +484,12 @@ module Puma
|
|
519
484
|
|
520
485
|
case
|
521
486
|
when got == len
|
522
|
-
|
523
|
-
if part.end_with? CHUNK_VALID_ENDING
|
524
|
-
write_chunk(part[0..-3]) # to skip the ending \r\n
|
525
|
-
else
|
526
|
-
raise HttpParserError, "Chunk size mismatch"
|
527
|
-
end
|
487
|
+
@body << part[0..-3] # to skip the ending \r\n
|
528
488
|
when got <= len - 2
|
529
|
-
|
489
|
+
@body << part
|
530
490
|
@partial_part_left = len - part.size
|
531
491
|
when got == len - 1 # edge where we get just \r but not \n
|
532
|
-
|
492
|
+
@body << part[0..-2]
|
533
493
|
@partial_part_left = len - part.size
|
534
494
|
end
|
535
495
|
else
|