puma 3.11.4 → 4.2.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.
- checksums.yaml +4 -4
- data/History.md +130 -1
- data/README.md +100 -44
- data/docs/architecture.md +1 -0
- data/docs/deployment.md +24 -4
- data/docs/restart.md +4 -2
- data/docs/systemd.md +27 -9
- data/ext/puma_http11/PumaHttp11Service.java +2 -0
- data/ext/puma_http11/extconf.rb +8 -0
- data/ext/puma_http11/http11_parser.c +37 -62
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/ext/puma_http11/mini_ssl.c +96 -5
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +21 -4
- data/lib/puma/accept_nonblock.rb +7 -1
- data/lib/puma/app/status.rb +35 -29
- data/lib/puma/binder.rb +47 -11
- data/lib/puma/cli.rb +21 -7
- data/lib/puma/client.rb +227 -191
- data/lib/puma/cluster.rb +70 -31
- data/lib/puma/commonlogger.rb +2 -0
- data/lib/puma/configuration.rb +6 -3
- data/lib/puma/const.rb +24 -18
- data/lib/puma/control_cli.rb +33 -14
- data/lib/puma/convenient.rb +2 -0
- data/lib/puma/delegation.rb +2 -0
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +308 -76
- data/lib/puma/events.rb +6 -1
- data/lib/puma/io_buffer.rb +3 -6
- data/lib/puma/jruby_restart.rb +2 -0
- data/lib/puma/launcher.rb +102 -55
- data/lib/puma/minissl.rb +41 -19
- data/lib/puma/null_io.rb +2 -0
- data/lib/puma/plugin/tmp_restart.rb +2 -0
- data/lib/puma/plugin.rb +7 -2
- data/lib/puma/rack/builder.rb +4 -1
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +220 -34
- data/lib/puma/runner.rb +14 -4
- data/lib/puma/server.rb +82 -40
- data/lib/puma/single.rb +15 -3
- data/lib/puma/state_file.rb +2 -0
- data/lib/puma/tcp_logger.rb +2 -0
- data/lib/puma/thread_pool.rb +59 -36
- data/lib/puma/util.rb +2 -6
- data/lib/puma.rb +8 -0
- data/lib/rack/handler/puma.rb +6 -3
- data/tools/docker/Dockerfile +16 -0
- data/tools/jungle/init.d/puma +6 -6
- data/tools/trickletest.rb +0 -1
- metadata +22 -10
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/daemon_ext.rb +0 -31
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
data/lib/puma/binder.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'uri'
|
2
4
|
require 'socket'
|
3
5
|
|
@@ -40,7 +42,7 @@ module Puma
|
|
40
42
|
@ios = []
|
41
43
|
end
|
42
44
|
|
43
|
-
attr_reader :
|
45
|
+
attr_reader :ios
|
44
46
|
|
45
47
|
def env(sock)
|
46
48
|
@envs.fetch(sock, @proto_env)
|
@@ -48,7 +50,6 @@ module Puma
|
|
48
50
|
|
49
51
|
def close
|
50
52
|
@ios.each { |i| i.close }
|
51
|
-
@unix_paths.each { |i| File.unlink i }
|
52
53
|
end
|
53
54
|
|
54
55
|
def import_from_env
|
@@ -90,19 +91,28 @@ module Puma
|
|
90
91
|
case uri.scheme
|
91
92
|
when "tcp"
|
92
93
|
if fd = @inherited_fds.delete(str)
|
93
|
-
logger.log "* Inherited #{str}"
|
94
94
|
io = inherit_tcp_listener uri.host, uri.port, fd
|
95
|
+
logger.log "* Inherited #{str}"
|
95
96
|
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
96
|
-
logger.log "* Activated #{str}"
|
97
97
|
io = inherit_tcp_listener uri.host, uri.port, sock
|
98
|
+
logger.log "* Activated #{str}"
|
98
99
|
else
|
99
100
|
params = Util.parse_query uri.query
|
100
101
|
|
101
102
|
opt = params.key?('low_latency')
|
102
103
|
bak = params.fetch('backlog', 1024).to_i
|
103
104
|
|
104
|
-
logger.log "* Listening on #{str}"
|
105
105
|
io = add_tcp_listener uri.host, uri.port, opt, bak
|
106
|
+
|
107
|
+
@ios.each do |i|
|
108
|
+
addr = if i.local_address.ipv6?
|
109
|
+
"[#{i.local_address.ip_unpack[0]}]:#{i.local_address.ip_unpack[1]}"
|
110
|
+
else
|
111
|
+
i.local_address.ip_unpack.join(':')
|
112
|
+
end
|
113
|
+
|
114
|
+
logger.log "* Listening on tcp://#{addr}"
|
115
|
+
end
|
106
116
|
end
|
107
117
|
|
108
118
|
@listeners << [str, io] if io
|
@@ -110,14 +120,12 @@ module Puma
|
|
110
120
|
path = "#{uri.host}#{uri.path}".gsub("%20", " ")
|
111
121
|
|
112
122
|
if fd = @inherited_fds.delete(str)
|
113
|
-
logger.log "* Inherited #{str}"
|
114
123
|
io = inherit_unix_listener path, fd
|
124
|
+
logger.log "* Inherited #{str}"
|
115
125
|
elsif sock = @activated_sockets.delete([ :unix, path ])
|
116
|
-
logger.log "* Activated #{str}"
|
117
126
|
io = inherit_unix_listener path, sock
|
127
|
+
logger.log "* Activated #{str}"
|
118
128
|
else
|
119
|
-
logger.log "* Listening on #{str}"
|
120
|
-
|
121
129
|
umask = nil
|
122
130
|
mode = nil
|
123
131
|
backlog = 1024
|
@@ -139,6 +147,7 @@ module Puma
|
|
139
147
|
end
|
140
148
|
|
141
149
|
io = add_unix_listener path, umask, mode, backlog
|
150
|
+
logger.log "* Listening on #{str}"
|
142
151
|
end
|
143
152
|
|
144
153
|
@listeners << [str, io]
|
@@ -162,6 +171,7 @@ module Puma
|
|
162
171
|
end
|
163
172
|
|
164
173
|
ctx.keystore_pass = params['keystore-pass']
|
174
|
+
ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
|
165
175
|
else
|
166
176
|
unless params['key']
|
167
177
|
@events.error "Please specify the SSL key via 'key='"
|
@@ -182,8 +192,12 @@ module Puma
|
|
182
192
|
end
|
183
193
|
|
184
194
|
ctx.ca = params['ca'] if params['ca']
|
195
|
+
ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
|
185
196
|
end
|
186
197
|
|
198
|
+
ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
|
199
|
+
ctx.no_tlsv1_1 = true if params['no_tlsv1_1'] == 'true'
|
200
|
+
|
187
201
|
if params['verify_mode']
|
188
202
|
ctx.verify_mode = case params['verify_mode']
|
189
203
|
when "peer"
|
@@ -202,11 +216,11 @@ module Puma
|
|
202
216
|
logger.log "* Inherited #{str}"
|
203
217
|
io = inherit_ssl_listener fd, ctx
|
204
218
|
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
205
|
-
logger.log "* Activated #{str}"
|
206
219
|
io = inherit_ssl_listener sock, ctx
|
220
|
+
logger.log "* Activated #{str}"
|
207
221
|
else
|
208
|
-
logger.log "* Listening on #{str}"
|
209
222
|
io = add_ssl_listener uri.host, uri.port, ctx
|
223
|
+
logger.log "* Listening on #{str}"
|
210
224
|
end
|
211
225
|
|
212
226
|
@listeners << [str, io] if io
|
@@ -313,6 +327,7 @@ module Puma
|
|
313
327
|
s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
|
314
328
|
s.listen backlog
|
315
329
|
|
330
|
+
|
316
331
|
ssl = MiniSSL::Server.new s, ctx
|
317
332
|
env = @proto_env.dup
|
318
333
|
env[HTTPS_KEY] = HTTPS
|
@@ -399,5 +414,26 @@ module Puma
|
|
399
414
|
s
|
400
415
|
end
|
401
416
|
|
417
|
+
def close_listeners
|
418
|
+
@listeners.each do |l, io|
|
419
|
+
io.close
|
420
|
+
uri = URI.parse(l)
|
421
|
+
next unless uri.scheme == 'unix'
|
422
|
+
File.unlink("#{uri.host}#{uri.path}")
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
def close_unix_paths
|
427
|
+
@unix_paths.each { |up| File.unlink(up) if File.exist? up }
|
428
|
+
end
|
429
|
+
|
430
|
+
def redirects_for_restart
|
431
|
+
redirects = {:close_others => true}
|
432
|
+
@listeners.each_with_index do |(l, io), i|
|
433
|
+
ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
|
434
|
+
redirects[io.to_i] = io.to_i
|
435
|
+
end
|
436
|
+
redirects
|
437
|
+
end
|
402
438
|
end
|
403
439
|
end
|
data/lib/puma/cli.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'optparse'
|
2
4
|
require 'uri'
|
3
5
|
|
@@ -84,6 +86,14 @@ module Puma
|
|
84
86
|
raise UnsupportedOption
|
85
87
|
end
|
86
88
|
|
89
|
+
def configure_control_url(command_line_arg)
|
90
|
+
if command_line_arg
|
91
|
+
@control_url = command_line_arg
|
92
|
+
elsif Puma.jruby?
|
93
|
+
unsupported "No default url available on JRuby"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
87
97
|
# Build the OptionParser object to handle the available options.
|
88
98
|
#
|
89
99
|
|
@@ -98,13 +108,13 @@ module Puma
|
|
98
108
|
file_config.load arg
|
99
109
|
end
|
100
110
|
|
101
|
-
o.on "--control URL", "The bind url to use for the control server"
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
111
|
+
o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg|
|
112
|
+
configure_control_url(arg)
|
113
|
+
end
|
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)
|
108
118
|
end
|
109
119
|
|
110
120
|
o.on "--control-token TOKEN",
|
@@ -151,6 +161,10 @@ module Puma
|
|
151
161
|
user_config.prune_bundler
|
152
162
|
end
|
153
163
|
|
164
|
+
o.on "--extra-runtime-dependencies GEM1,GEM2", "Defines any extra needed gems when using --prune-bundler" do |arg|
|
165
|
+
c.extra_runtime_dependencies arg.split(',')
|
166
|
+
end
|
167
|
+
|
154
168
|
o.on "-q", "--quiet", "Do not log requests internally (default true)" do
|
155
169
|
user_config.quiet
|
156
170
|
end
|
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
|
@@ -21,7 +23,23 @@ module Puma
|
|
21
23
|
|
22
24
|
class ConnectionError < RuntimeError; end
|
23
25
|
|
26
|
+
# An instance of this class represents a unique request from a client.
|
27
|
+
# For example, this could be a web request from a browser or from CURL.
|
28
|
+
#
|
29
|
+
# An instance of `Puma::Client` can be used as if it were an IO object
|
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.
|
34
|
+
#
|
35
|
+
# Instances of this class are responsible for knowing if
|
36
|
+
# the header and body are fully buffered via the `try_to_finish` method.
|
37
|
+
# They can be used to "time out" a response via the `timeout_at` reader.
|
24
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
|
+
|
25
43
|
include Puma::Const
|
26
44
|
extend Puma::Delegation
|
27
45
|
|
@@ -41,6 +59,7 @@ module Puma
|
|
41
59
|
@ready = false
|
42
60
|
|
43
61
|
@body = nil
|
62
|
+
@body_read_start = nil
|
44
63
|
@buffer = nil
|
45
64
|
@tempfile = nil
|
46
65
|
|
@@ -51,6 +70,10 @@ module Puma
|
|
51
70
|
|
52
71
|
@peerip = nil
|
53
72
|
@remote_addr_header = nil
|
73
|
+
|
74
|
+
@body_remain = 0
|
75
|
+
|
76
|
+
@in_last_chunk = false
|
54
77
|
end
|
55
78
|
|
56
79
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
@@ -89,6 +112,9 @@ module Puma
|
|
89
112
|
@tempfile = nil
|
90
113
|
@parsed_bytes = 0
|
91
114
|
@ready = false
|
115
|
+
@body_remain = 0
|
116
|
+
@peerip = nil
|
117
|
+
@in_last_chunk = false
|
92
118
|
|
93
119
|
if @buffer
|
94
120
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
@@ -101,9 +127,16 @@ module Puma
|
|
101
127
|
end
|
102
128
|
|
103
129
|
return false
|
104
|
-
|
105
|
-
|
106
|
-
|
130
|
+
else
|
131
|
+
begin
|
132
|
+
if fast_check &&
|
133
|
+
IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
|
134
|
+
return try_to_finish
|
135
|
+
end
|
136
|
+
rescue IOError
|
137
|
+
# swallow it
|
138
|
+
end
|
139
|
+
|
107
140
|
end
|
108
141
|
end
|
109
142
|
|
@@ -115,164 +148,6 @@ module Puma
|
|
115
148
|
end
|
116
149
|
end
|
117
150
|
|
118
|
-
# The object used for a request with no body. All requests with
|
119
|
-
# no body share this one object since it has no state.
|
120
|
-
EmptyBody = NullIO.new
|
121
|
-
|
122
|
-
def setup_chunked_body(body)
|
123
|
-
@chunked_body = true
|
124
|
-
@partial_part_left = 0
|
125
|
-
@prev_chunk = ""
|
126
|
-
|
127
|
-
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
128
|
-
@body.binmode
|
129
|
-
@tempfile = @body
|
130
|
-
|
131
|
-
return decode_chunk(body)
|
132
|
-
end
|
133
|
-
|
134
|
-
def decode_chunk(chunk)
|
135
|
-
if @partial_part_left > 0
|
136
|
-
if @partial_part_left <= chunk.size
|
137
|
-
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
|
138
|
-
chunk = chunk[@partial_part_left..-1]
|
139
|
-
else
|
140
|
-
@body << chunk
|
141
|
-
@partial_part_left -= chunk.size
|
142
|
-
return false
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
if @prev_chunk.empty?
|
147
|
-
io = StringIO.new(chunk)
|
148
|
-
else
|
149
|
-
io = StringIO.new(@prev_chunk+chunk)
|
150
|
-
@prev_chunk = ""
|
151
|
-
end
|
152
|
-
|
153
|
-
while !io.eof?
|
154
|
-
line = io.gets
|
155
|
-
if line.end_with?("\r\n")
|
156
|
-
len = line.strip.to_i(16)
|
157
|
-
if len == 0
|
158
|
-
@body.rewind
|
159
|
-
rest = io.read
|
160
|
-
@buffer = rest.empty? ? nil : rest
|
161
|
-
@requests_served += 1
|
162
|
-
@ready = true
|
163
|
-
return true
|
164
|
-
end
|
165
|
-
|
166
|
-
len += 2
|
167
|
-
|
168
|
-
part = io.read(len)
|
169
|
-
|
170
|
-
unless part
|
171
|
-
@partial_part_left = len
|
172
|
-
next
|
173
|
-
end
|
174
|
-
|
175
|
-
got = part.size
|
176
|
-
|
177
|
-
case
|
178
|
-
when got == len
|
179
|
-
@body << part[0..-3] # to skip the ending \r\n
|
180
|
-
when got <= len - 2
|
181
|
-
@body << part
|
182
|
-
@partial_part_left = len - part.size
|
183
|
-
when got == len - 1 # edge where we get just \r but not \n
|
184
|
-
@body << part[0..-2]
|
185
|
-
@partial_part_left = len - part.size
|
186
|
-
end
|
187
|
-
else
|
188
|
-
@prev_chunk = line
|
189
|
-
return false
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
return false
|
194
|
-
end
|
195
|
-
|
196
|
-
def read_chunked_body
|
197
|
-
while true
|
198
|
-
begin
|
199
|
-
chunk = @io.read_nonblock(4096)
|
200
|
-
rescue Errno::EAGAIN
|
201
|
-
return false
|
202
|
-
rescue SystemCallError, IOError
|
203
|
-
raise ConnectionError, "Connection error detected during read"
|
204
|
-
end
|
205
|
-
|
206
|
-
# No chunk means a closed socket
|
207
|
-
unless chunk
|
208
|
-
@body.close
|
209
|
-
@buffer = nil
|
210
|
-
@requests_served += 1
|
211
|
-
@ready = true
|
212
|
-
raise EOFError
|
213
|
-
end
|
214
|
-
|
215
|
-
return true if decode_chunk(chunk)
|
216
|
-
end
|
217
|
-
end
|
218
|
-
|
219
|
-
def setup_body
|
220
|
-
if @env[HTTP_EXPECT] == CONTINUE
|
221
|
-
# TODO allow a hook here to check the headers before
|
222
|
-
# going forward
|
223
|
-
@io << HTTP_11_100
|
224
|
-
@io.flush
|
225
|
-
end
|
226
|
-
|
227
|
-
@read_header = false
|
228
|
-
|
229
|
-
body = @parser.body
|
230
|
-
|
231
|
-
te = @env[TRANSFER_ENCODING2]
|
232
|
-
|
233
|
-
if te && CHUNKED.casecmp(te) == 0
|
234
|
-
return setup_chunked_body(body)
|
235
|
-
end
|
236
|
-
|
237
|
-
@chunked_body = false
|
238
|
-
|
239
|
-
cl = @env[CONTENT_LENGTH]
|
240
|
-
|
241
|
-
unless cl
|
242
|
-
@buffer = body.empty? ? nil : body
|
243
|
-
@body = EmptyBody
|
244
|
-
@requests_served += 1
|
245
|
-
@ready = true
|
246
|
-
return true
|
247
|
-
end
|
248
|
-
|
249
|
-
remain = cl.to_i - body.bytesize
|
250
|
-
|
251
|
-
if remain <= 0
|
252
|
-
@body = StringIO.new(body)
|
253
|
-
@buffer = nil
|
254
|
-
@requests_served += 1
|
255
|
-
@ready = true
|
256
|
-
return true
|
257
|
-
end
|
258
|
-
|
259
|
-
if remain > MAX_BODY
|
260
|
-
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
261
|
-
@body.binmode
|
262
|
-
@tempfile = @body
|
263
|
-
else
|
264
|
-
# The body[0,0] trick is to get an empty string in the same
|
265
|
-
# encoding as body.
|
266
|
-
@body = StringIO.new body[0,0]
|
267
|
-
end
|
268
|
-
|
269
|
-
@body.write body
|
270
|
-
|
271
|
-
@body_remain = remain
|
272
|
-
|
273
|
-
return false
|
274
|
-
end
|
275
|
-
|
276
151
|
def try_to_finish
|
277
152
|
return read_body unless @read_header
|
278
153
|
|
@@ -280,15 +155,14 @@ module Puma
|
|
280
155
|
data = @io.read_nonblock(CHUNK_SIZE)
|
281
156
|
rescue Errno::EAGAIN
|
282
157
|
return false
|
283
|
-
rescue SystemCallError, IOError
|
158
|
+
rescue SystemCallError, IOError, EOFError
|
284
159
|
raise ConnectionError, "Connection error detected during read"
|
285
160
|
end
|
286
161
|
|
287
162
|
# No data means a closed socket
|
288
163
|
unless data
|
289
164
|
@buffer = nil
|
290
|
-
|
291
|
-
@ready = true
|
165
|
+
set_ready
|
292
166
|
raise EOFError
|
293
167
|
end
|
294
168
|
|
@@ -324,8 +198,7 @@ module Puma
|
|
324
198
|
# No data means a closed socket
|
325
199
|
unless data
|
326
200
|
@buffer = nil
|
327
|
-
|
328
|
-
@ready = true
|
201
|
+
set_ready
|
329
202
|
raise EOFError
|
330
203
|
end
|
331
204
|
|
@@ -375,6 +248,84 @@ module Puma
|
|
375
248
|
true
|
376
249
|
end
|
377
250
|
|
251
|
+
def write_error(status_code)
|
252
|
+
begin
|
253
|
+
@io << ERROR_RESPONSE[status_code]
|
254
|
+
rescue StandardError
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def peerip
|
259
|
+
return @peerip if @peerip
|
260
|
+
|
261
|
+
if @remote_addr_header
|
262
|
+
hdr = (@env[@remote_addr_header] || LOCALHOST_ADDR).split(/[\s,]/).first
|
263
|
+
@peerip = hdr
|
264
|
+
return hdr
|
265
|
+
end
|
266
|
+
|
267
|
+
@peerip ||= @io.peeraddr.last
|
268
|
+
end
|
269
|
+
|
270
|
+
private
|
271
|
+
|
272
|
+
def setup_body
|
273
|
+
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
274
|
+
|
275
|
+
if @env[HTTP_EXPECT] == CONTINUE
|
276
|
+
# TODO allow a hook here to check the headers before
|
277
|
+
# going forward
|
278
|
+
@io << HTTP_11_100
|
279
|
+
@io.flush
|
280
|
+
end
|
281
|
+
|
282
|
+
@read_header = false
|
283
|
+
|
284
|
+
body = @parser.body
|
285
|
+
|
286
|
+
te = @env[TRANSFER_ENCODING2]
|
287
|
+
|
288
|
+
if te && CHUNKED.casecmp(te) == 0
|
289
|
+
return setup_chunked_body(body)
|
290
|
+
end
|
291
|
+
|
292
|
+
@chunked_body = false
|
293
|
+
|
294
|
+
cl = @env[CONTENT_LENGTH]
|
295
|
+
|
296
|
+
unless cl
|
297
|
+
@buffer = body.empty? ? nil : body
|
298
|
+
@body = EmptyBody
|
299
|
+
set_ready
|
300
|
+
return true
|
301
|
+
end
|
302
|
+
|
303
|
+
remain = cl.to_i - body.bytesize
|
304
|
+
|
305
|
+
if remain <= 0
|
306
|
+
@body = StringIO.new(body)
|
307
|
+
@buffer = nil
|
308
|
+
set_ready
|
309
|
+
return true
|
310
|
+
end
|
311
|
+
|
312
|
+
if remain > MAX_BODY
|
313
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
314
|
+
@body.binmode
|
315
|
+
@tempfile = @body
|
316
|
+
else
|
317
|
+
# The body[0,0] trick is to get an empty string in the same
|
318
|
+
# encoding as body.
|
319
|
+
@body = StringIO.new body[0,0]
|
320
|
+
end
|
321
|
+
|
322
|
+
@body.write body
|
323
|
+
|
324
|
+
@body_remain = remain
|
325
|
+
|
326
|
+
return false
|
327
|
+
end
|
328
|
+
|
378
329
|
def read_body
|
379
330
|
if @chunked_body
|
380
331
|
return read_chunked_body
|
@@ -402,8 +353,7 @@ module Puma
|
|
402
353
|
unless chunk
|
403
354
|
@body.close
|
404
355
|
@buffer = nil
|
405
|
-
|
406
|
-
@ready = true
|
356
|
+
set_ready
|
407
357
|
raise EOFError
|
408
358
|
end
|
409
359
|
|
@@ -412,8 +362,7 @@ module Puma
|
|
412
362
|
if remain <= 0
|
413
363
|
@body.rewind
|
414
364
|
@buffer = nil
|
415
|
-
|
416
|
-
@ready = true
|
365
|
+
set_ready
|
417
366
|
return true
|
418
367
|
end
|
419
368
|
|
@@ -422,37 +371,124 @@ module Puma
|
|
422
371
|
false
|
423
372
|
end
|
424
373
|
|
425
|
-
def
|
426
|
-
|
427
|
-
|
428
|
-
|
374
|
+
def read_chunked_body
|
375
|
+
while true
|
376
|
+
begin
|
377
|
+
chunk = @io.read_nonblock(4096)
|
378
|
+
rescue IO::WaitReadable
|
379
|
+
return false
|
380
|
+
rescue SystemCallError, IOError
|
381
|
+
raise ConnectionError, "Connection error detected during read"
|
382
|
+
end
|
383
|
+
|
384
|
+
# No chunk means a closed socket
|
385
|
+
unless chunk
|
386
|
+
@body.close
|
387
|
+
@buffer = nil
|
388
|
+
set_ready
|
389
|
+
raise EOFError
|
390
|
+
end
|
391
|
+
|
392
|
+
return true if decode_chunk(chunk)
|
429
393
|
end
|
430
394
|
end
|
431
395
|
|
432
|
-
def
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
396
|
+
def setup_chunked_body(body)
|
397
|
+
@chunked_body = true
|
398
|
+
@partial_part_left = 0
|
399
|
+
@prev_chunk = ""
|
400
|
+
|
401
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
402
|
+
@body.binmode
|
403
|
+
@tempfile = @body
|
404
|
+
|
405
|
+
return decode_chunk(body)
|
437
406
|
end
|
438
407
|
|
439
|
-
def
|
440
|
-
|
441
|
-
@
|
442
|
-
|
408
|
+
def decode_chunk(chunk)
|
409
|
+
if @partial_part_left > 0
|
410
|
+
if @partial_part_left <= chunk.size
|
411
|
+
if @partial_part_left > 2
|
412
|
+
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
|
413
|
+
end
|
414
|
+
chunk = chunk[@partial_part_left..-1]
|
415
|
+
@partial_part_left = 0
|
416
|
+
else
|
417
|
+
@body << chunk if @partial_part_left > 2 # don't include the last \r\n
|
418
|
+
@partial_part_left -= chunk.size
|
419
|
+
return false
|
420
|
+
end
|
443
421
|
end
|
444
|
-
end
|
445
422
|
|
446
|
-
|
447
|
-
|
423
|
+
if @prev_chunk.empty?
|
424
|
+
io = StringIO.new(chunk)
|
425
|
+
else
|
426
|
+
io = StringIO.new(@prev_chunk+chunk)
|
427
|
+
@prev_chunk = ""
|
428
|
+
end
|
448
429
|
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
430
|
+
while !io.eof?
|
431
|
+
line = io.gets
|
432
|
+
if line.end_with?("\r\n")
|
433
|
+
len = line.strip.to_i(16)
|
434
|
+
if len == 0
|
435
|
+
@in_last_chunk = true
|
436
|
+
@body.rewind
|
437
|
+
rest = io.read
|
438
|
+
last_crlf_size = "\r\n".bytesize
|
439
|
+
if rest.bytesize < last_crlf_size
|
440
|
+
@buffer = nil
|
441
|
+
@partial_part_left = last_crlf_size - rest.bytesize
|
442
|
+
return false
|
443
|
+
else
|
444
|
+
@buffer = rest[last_crlf_size..-1]
|
445
|
+
@buffer = nil if @buffer.empty?
|
446
|
+
set_ready
|
447
|
+
return true
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
len += 2
|
452
|
+
|
453
|
+
part = io.read(len)
|
454
|
+
|
455
|
+
unless part
|
456
|
+
@partial_part_left = len
|
457
|
+
next
|
458
|
+
end
|
459
|
+
|
460
|
+
got = part.size
|
461
|
+
|
462
|
+
case
|
463
|
+
when got == len
|
464
|
+
@body << part[0..-3] # to skip the ending \r\n
|
465
|
+
when got <= len - 2
|
466
|
+
@body << part
|
467
|
+
@partial_part_left = len - part.size
|
468
|
+
when got == len - 1 # edge where we get just \r but not \n
|
469
|
+
@body << part[0..-2]
|
470
|
+
@partial_part_left = len - part.size
|
471
|
+
end
|
472
|
+
else
|
473
|
+
@prev_chunk = line
|
474
|
+
return false
|
475
|
+
end
|
453
476
|
end
|
454
477
|
|
455
|
-
|
478
|
+
if @in_last_chunk
|
479
|
+
set_ready
|
480
|
+
true
|
481
|
+
else
|
482
|
+
false
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
def set_ready
|
487
|
+
if @body_read_start
|
488
|
+
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @body_read_start
|
489
|
+
end
|
490
|
+
@requests_served += 1
|
491
|
+
@ready = true
|
456
492
|
end
|
457
493
|
end
|
458
494
|
end
|