puma 3.12.6 → 5.3.2
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 +1400 -451
- 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/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 +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 +81 -108
- 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 +1 -1
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/ext/puma_http11/mini_ssl.c +254 -91
- 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.rb +54 -0
- data/lib/puma/app/status.rb +68 -49
- data/lib/puma/binder.rb +191 -139
- data/lib/puma/cli.rb +15 -15
- data/lib/puma/client.rb +247 -226
- data/lib/puma/cluster.rb +221 -212
- data/lib/puma/cluster/worker.rb +183 -0
- data/lib/puma/cluster/worker_handle.rb +90 -0
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +58 -51
- data/lib/puma/const.rb +32 -20
- data/lib/puma/control_cli.rb +109 -67
- data/lib/puma/detect.rb +24 -3
- data/lib/puma/dsl.rb +519 -121
- data/lib/puma/error_logger.rb +104 -0
- data/lib/puma/events.rb +55 -31
- data/lib/puma/io_buffer.rb +7 -5
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/json.rb +96 -0
- data/lib/puma/launcher.rb +178 -68
- data/lib/puma/minissl.rb +147 -48
- data/lib/puma/minissl/context_builder.rb +79 -0
- data/lib/puma/null_io.rb +13 -1
- data/lib/puma/plugin.rb +6 -12
- data/lib/puma/plugin/tmp_restart.rb +2 -0
- data/lib/puma/queue_close.rb +26 -0
- data/lib/puma/rack/builder.rb +2 -4
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +85 -316
- data/lib/puma/request.rb +467 -0
- data/lib/puma/runner.rb +31 -52
- data/lib/puma/server.rb +275 -726
- data/lib/puma/single.rb +11 -67
- data/lib/puma/state_file.rb +8 -3
- data/lib/puma/systemd.rb +46 -0
- data/lib/puma/thread_pool.rb +129 -81
- data/lib/puma/util.rb +13 -6
- data/lib/rack/handler/puma.rb +5 -6
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +45 -28
- 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 -25
- data/lib/puma/daemon_ext.rb +0 -33
- data/lib/puma/delegation.rb +0 -13
- data/lib/puma/java_io_buffer.rb +0 -47
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
- 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/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/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
|
@@ -104,6 +104,10 @@ module Puma
|
|
104
104
|
user_config.bind arg
|
105
105
|
end
|
106
106
|
|
107
|
+
o.on "--bind-to-activated-sockets [only]", "Bind to all activated sockets" do |arg|
|
108
|
+
user_config.bind_to_activated_sockets(arg || true)
|
109
|
+
end
|
110
|
+
|
107
111
|
o.on "-C", "--config PATH", "Load PATH as a config file" do |arg|
|
108
112
|
file_config.load arg
|
109
113
|
end
|
@@ -112,21 +116,11 @@ module Puma
|
|
112
116
|
configure_control_url(arg)
|
113
117
|
end
|
114
118
|
|
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
119
|
o.on "--control-token TOKEN",
|
121
120
|
"The token to use as authentication for the control server" do |arg|
|
122
121
|
@control_options[:auth_token] = arg
|
123
122
|
end
|
124
123
|
|
125
|
-
o.on "-d", "--daemon", "Daemonize the server into the background" do
|
126
|
-
user_config.daemonize
|
127
|
-
user_config.quiet
|
128
|
-
end
|
129
|
-
|
130
124
|
o.on "--debug", "Log lowlevel debugging information" do
|
131
125
|
user_config.debug
|
132
126
|
end
|
@@ -140,6 +134,12 @@ module Puma
|
|
140
134
|
user_config.environment arg
|
141
135
|
end
|
142
136
|
|
137
|
+
o.on "-f", "--fork-worker=[REQUESTS]", OptionParser::DecimalInteger,
|
138
|
+
"Fork new workers from existing worker. Cluster mode only",
|
139
|
+
"Auto-refork after REQUESTS (default 1000)" do |*args|
|
140
|
+
user_config.fork_worker(*args.compact)
|
141
|
+
end
|
142
|
+
|
143
143
|
o.on "-I", "--include PATH", "Specify $LOAD_PATH directories" do |arg|
|
144
144
|
$LOAD_PATH.unshift(*arg.split(':'))
|
145
145
|
end
|
@@ -161,6 +161,10 @@ module Puma
|
|
161
161
|
user_config.prune_bundler
|
162
162
|
end
|
163
163
|
|
164
|
+
o.on "--extra-runtime-dependencies GEM1,GEM2", "Defines any extra needed gems when using --prune-bundler" do |arg|
|
165
|
+
user_config.extra_runtime_dependencies arg.split(',')
|
166
|
+
end
|
167
|
+
|
164
168
|
o.on "-q", "--quiet", "Do not log requests internally (default true)" do
|
165
169
|
user_config.quiet
|
166
170
|
end
|
@@ -188,10 +192,6 @@ module Puma
|
|
188
192
|
end
|
189
193
|
end
|
190
194
|
|
191
|
-
o.on "--tcp-mode", "Run the app in raw TCP mode instead of HTTP mode" do
|
192
|
-
user_config.tcp_mode!
|
193
|
-
end
|
194
|
-
|
195
195
|
o.on "--early-hints", "Enable early hints support" do
|
196
196
|
user_config.early_hints
|
197
197
|
end
|
data/lib/puma/client.rb
CHANGED
@@ -9,8 +9,8 @@ class IO
|
|
9
9
|
end
|
10
10
|
|
11
11
|
require 'puma/detect'
|
12
|
-
require 'puma/delegation'
|
13
12
|
require 'tempfile'
|
13
|
+
require 'forwardable'
|
14
14
|
|
15
15
|
if Puma::IS_JRUBY
|
16
16
|
# We have to work around some OpenSSL buffer/io-readiness bugs
|
@@ -24,19 +24,24 @@ module Puma
|
|
24
24
|
class ConnectionError < RuntimeError; end
|
25
25
|
|
26
26
|
# An instance of this class represents a unique request from a client.
|
27
|
-
# 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.
|
28
28
|
#
|
29
29
|
# An instance of `Puma::Client` can be used as if it were an IO object
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
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.
|
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.
|
36
37
|
# They can be used to "time out" a response via the `timeout_at` reader.
|
37
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
|
+
|
38
43
|
include Puma::Const
|
39
|
-
extend
|
44
|
+
extend Forwardable
|
40
45
|
|
41
46
|
def initialize(io, env=nil)
|
42
47
|
@io = io
|
@@ -54,6 +59,7 @@ module Puma
|
|
54
59
|
@ready = false
|
55
60
|
|
56
61
|
@body = nil
|
62
|
+
@body_read_start = nil
|
57
63
|
@buffer = nil
|
58
64
|
@tempfile = nil
|
59
65
|
|
@@ -63,7 +69,12 @@ module Puma
|
|
63
69
|
@hijacked = false
|
64
70
|
|
65
71
|
@peerip = nil
|
72
|
+
@listener = nil
|
66
73
|
@remote_addr_header = nil
|
74
|
+
|
75
|
+
@body_remain = 0
|
76
|
+
|
77
|
+
@in_last_chunk = false
|
67
78
|
end
|
68
79
|
|
69
80
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
@@ -71,10 +82,17 @@ module Puma
|
|
71
82
|
|
72
83
|
attr_writer :peerip
|
73
84
|
|
74
|
-
attr_accessor :remote_addr_header
|
85
|
+
attr_accessor :remote_addr_header, :listener
|
86
|
+
|
87
|
+
def_delegators :@io, :closed?
|
75
88
|
|
76
|
-
|
89
|
+
# Test to see if io meets a bare minimum of functioning, @to_io needs to be
|
90
|
+
# used for MiniSSL::Socket
|
91
|
+
def io_ok?
|
92
|
+
@to_io.is_a?(::BasicSocket) && !closed?
|
93
|
+
end
|
77
94
|
|
95
|
+
# @!attribute [r] inspect
|
78
96
|
def inspect
|
79
97
|
"#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
|
80
98
|
end
|
@@ -86,12 +104,18 @@ module Puma
|
|
86
104
|
env[HIJACK_IO] ||= @io
|
87
105
|
end
|
88
106
|
|
107
|
+
# @!attribute [r] in_data_phase
|
89
108
|
def in_data_phase
|
90
109
|
!@read_header
|
91
110
|
end
|
92
111
|
|
93
112
|
def set_timeout(val)
|
94
|
-
@timeout_at =
|
113
|
+
@timeout_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + val
|
114
|
+
end
|
115
|
+
|
116
|
+
# Number of seconds until the timeout elapses.
|
117
|
+
def timeout
|
118
|
+
[@timeout_at - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0].max
|
95
119
|
end
|
96
120
|
|
97
121
|
def reset(fast_check=true)
|
@@ -102,6 +126,9 @@ module Puma
|
|
102
126
|
@tempfile = nil
|
103
127
|
@parsed_bytes = 0
|
104
128
|
@ready = false
|
129
|
+
@body_remain = 0
|
130
|
+
@peerip = nil if @remote_addr_header
|
131
|
+
@in_last_chunk = false
|
105
132
|
|
106
133
|
if @buffer
|
107
134
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
@@ -114,9 +141,16 @@ module Puma
|
|
114
141
|
end
|
115
142
|
|
116
143
|
return false
|
117
|
-
|
118
|
-
|
119
|
-
|
144
|
+
else
|
145
|
+
begin
|
146
|
+
if fast_check &&
|
147
|
+
IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
|
148
|
+
return try_to_finish
|
149
|
+
end
|
150
|
+
rescue IOError
|
151
|
+
# swallow it
|
152
|
+
end
|
153
|
+
|
120
154
|
end
|
121
155
|
end
|
122
156
|
|
@@ -128,109 +162,93 @@ module Puma
|
|
128
162
|
end
|
129
163
|
end
|
130
164
|
|
131
|
-
|
132
|
-
|
133
|
-
EmptyBody = NullIO.new
|
134
|
-
|
135
|
-
def setup_chunked_body(body)
|
136
|
-
@chunked_body = true
|
137
|
-
@partial_part_left = 0
|
138
|
-
@prev_chunk = ""
|
139
|
-
|
140
|
-
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
141
|
-
@body.binmode
|
142
|
-
@tempfile = @body
|
165
|
+
def try_to_finish
|
166
|
+
return read_body unless @read_header
|
143
167
|
|
144
|
-
|
145
|
-
|
168
|
+
begin
|
169
|
+
data = @io.read_nonblock(CHUNK_SIZE)
|
170
|
+
rescue IO::WaitReadable
|
171
|
+
return false
|
172
|
+
rescue EOFError
|
173
|
+
# Swallow error, don't log
|
174
|
+
rescue SystemCallError, IOError
|
175
|
+
raise ConnectionError, "Connection error detected during read"
|
176
|
+
end
|
146
177
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
else
|
153
|
-
@body << chunk
|
154
|
-
@partial_part_left -= chunk.size
|
155
|
-
return false
|
156
|
-
end
|
178
|
+
# No data means a closed socket
|
179
|
+
unless data
|
180
|
+
@buffer = nil
|
181
|
+
set_ready
|
182
|
+
raise EOFError
|
157
183
|
end
|
158
184
|
|
159
|
-
if @
|
160
|
-
|
185
|
+
if @buffer
|
186
|
+
@buffer << data
|
161
187
|
else
|
162
|
-
|
163
|
-
@prev_chunk = ""
|
188
|
+
@buffer = data
|
164
189
|
end
|
165
190
|
|
166
|
-
|
167
|
-
line = io.gets
|
168
|
-
if line.end_with?("\r\n")
|
169
|
-
len = line.strip.to_i(16)
|
170
|
-
if len == 0
|
171
|
-
@body.rewind
|
172
|
-
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
|
178
|
-
end
|
179
|
-
|
180
|
-
len += 2
|
191
|
+
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
181
192
|
|
182
|
-
|
193
|
+
if @parser.finished?
|
194
|
+
return setup_body
|
195
|
+
elsif @parsed_bytes >= MAX_HEADER
|
196
|
+
raise HttpParserError,
|
197
|
+
"HEADER is longer than allowed, aborting client early."
|
198
|
+
end
|
183
199
|
|
184
|
-
|
185
|
-
|
186
|
-
next
|
187
|
-
end
|
200
|
+
false
|
201
|
+
end
|
188
202
|
|
189
|
-
|
203
|
+
def eagerly_finish
|
204
|
+
return true if @ready
|
205
|
+
return false unless IO.select([@to_io], nil, nil, 0)
|
206
|
+
try_to_finish
|
207
|
+
end
|
190
208
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
@body << part
|
196
|
-
@partial_part_left = len - part.size
|
197
|
-
when got == len - 1 # edge where we get just \r but not \n
|
198
|
-
@body << part[0..-2]
|
199
|
-
@partial_part_left = len - part.size
|
200
|
-
end
|
201
|
-
else
|
202
|
-
@prev_chunk = line
|
203
|
-
return false
|
204
|
-
end
|
205
|
-
end
|
209
|
+
def finish(timeout)
|
210
|
+
return if @ready
|
211
|
+
IO.select([@to_io], nil, nil, timeout) || timeout! until try_to_finish
|
212
|
+
end
|
206
213
|
|
207
|
-
|
214
|
+
def timeout!
|
215
|
+
write_error(408) if in_data_phase
|
216
|
+
raise ConnectionError
|
208
217
|
end
|
209
218
|
|
210
|
-
def
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
rescue SystemCallError, IOError
|
217
|
-
raise ConnectionError, "Connection error detected during read"
|
218
|
-
end
|
219
|
+
def write_error(status_code)
|
220
|
+
begin
|
221
|
+
@io << ERROR_RESPONSE[status_code]
|
222
|
+
rescue StandardError
|
223
|
+
end
|
224
|
+
end
|
219
225
|
|
220
|
-
|
221
|
-
|
222
|
-
@body.close
|
223
|
-
@buffer = nil
|
224
|
-
@requests_served += 1
|
225
|
-
@ready = true
|
226
|
-
raise EOFError
|
227
|
-
end
|
226
|
+
def peerip
|
227
|
+
return @peerip if @peerip
|
228
228
|
|
229
|
-
|
229
|
+
if @remote_addr_header
|
230
|
+
hdr = (@env[@remote_addr_header] || LOCALHOST_IP).split(/[\s,]/).first
|
231
|
+
@peerip = hdr
|
232
|
+
return hdr
|
230
233
|
end
|
234
|
+
|
235
|
+
@peerip ||= @io.peeraddr.last
|
231
236
|
end
|
232
237
|
|
238
|
+
# Returns true if the persistent connection can be closed immediately
|
239
|
+
# without waiting for the configured idle/shutdown timeout.
|
240
|
+
# @version 5.0.0
|
241
|
+
#
|
242
|
+
def can_close?
|
243
|
+
# Allow connection to close if we're not in the middle of parsing a request.
|
244
|
+
@parsed_bytes == 0
|
245
|
+
end
|
246
|
+
|
247
|
+
private
|
248
|
+
|
233
249
|
def setup_body
|
250
|
+
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
251
|
+
|
234
252
|
if @env[HTTP_EXPECT] == CONTINUE
|
235
253
|
# TODO allow a hook here to check the headers before
|
236
254
|
# going forward
|
@@ -263,8 +281,7 @@ module Puma
|
|
263
281
|
unless cl
|
264
282
|
@buffer = body.empty? ? nil : body
|
265
283
|
@body = EmptyBody
|
266
|
-
|
267
|
-
@ready = true
|
284
|
+
set_ready
|
268
285
|
return true
|
269
286
|
end
|
270
287
|
|
@@ -273,13 +290,13 @@ module Puma
|
|
273
290
|
if remain <= 0
|
274
291
|
@body = StringIO.new(body)
|
275
292
|
@buffer = nil
|
276
|
-
|
277
|
-
@ready = true
|
293
|
+
set_ready
|
278
294
|
return true
|
279
295
|
end
|
280
296
|
|
281
297
|
if remain > MAX_BODY
|
282
298
|
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
299
|
+
@body.unlink
|
283
300
|
@body.binmode
|
284
301
|
@tempfile = @body
|
285
302
|
else
|
@@ -295,108 +312,6 @@ module Puma
|
|
295
312
|
return false
|
296
313
|
end
|
297
314
|
|
298
|
-
def try_to_finish
|
299
|
-
return read_body unless @read_header
|
300
|
-
|
301
|
-
begin
|
302
|
-
data = @io.read_nonblock(CHUNK_SIZE)
|
303
|
-
rescue Errno::EAGAIN
|
304
|
-
return false
|
305
|
-
rescue SystemCallError, IOError
|
306
|
-
raise ConnectionError, "Connection error detected during read"
|
307
|
-
end
|
308
|
-
|
309
|
-
# No data means a closed socket
|
310
|
-
unless data
|
311
|
-
@buffer = nil
|
312
|
-
@requests_served += 1
|
313
|
-
@ready = true
|
314
|
-
raise EOFError
|
315
|
-
end
|
316
|
-
|
317
|
-
if @buffer
|
318
|
-
@buffer << data
|
319
|
-
else
|
320
|
-
@buffer = data
|
321
|
-
end
|
322
|
-
|
323
|
-
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
324
|
-
|
325
|
-
if @parser.finished?
|
326
|
-
return setup_body
|
327
|
-
elsif @parsed_bytes >= MAX_HEADER
|
328
|
-
raise HttpParserError,
|
329
|
-
"HEADER is longer than allowed, aborting client early."
|
330
|
-
end
|
331
|
-
|
332
|
-
false
|
333
|
-
end
|
334
|
-
|
335
|
-
if IS_JRUBY
|
336
|
-
def jruby_start_try_to_finish
|
337
|
-
return read_body unless @read_header
|
338
|
-
|
339
|
-
begin
|
340
|
-
data = @io.sysread_nonblock(CHUNK_SIZE)
|
341
|
-
rescue OpenSSL::SSL::SSLError => e
|
342
|
-
return false if e.kind_of? IO::WaitReadable
|
343
|
-
raise e
|
344
|
-
end
|
345
|
-
|
346
|
-
# No data means a closed socket
|
347
|
-
unless data
|
348
|
-
@buffer = nil
|
349
|
-
@requests_served += 1
|
350
|
-
@ready = true
|
351
|
-
raise EOFError
|
352
|
-
end
|
353
|
-
|
354
|
-
if @buffer
|
355
|
-
@buffer << data
|
356
|
-
else
|
357
|
-
@buffer = data
|
358
|
-
end
|
359
|
-
|
360
|
-
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
361
|
-
|
362
|
-
if @parser.finished?
|
363
|
-
return setup_body
|
364
|
-
elsif @parsed_bytes >= MAX_HEADER
|
365
|
-
raise HttpParserError,
|
366
|
-
"HEADER is longer than allowed, aborting client early."
|
367
|
-
end
|
368
|
-
|
369
|
-
false
|
370
|
-
end
|
371
|
-
|
372
|
-
def eagerly_finish
|
373
|
-
return true if @ready
|
374
|
-
|
375
|
-
if @io.kind_of? OpenSSL::SSL::SSLSocket
|
376
|
-
return true if jruby_start_try_to_finish
|
377
|
-
end
|
378
|
-
|
379
|
-
return false unless IO.select([@to_io], nil, nil, 0)
|
380
|
-
try_to_finish
|
381
|
-
end
|
382
|
-
|
383
|
-
else
|
384
|
-
|
385
|
-
def eagerly_finish
|
386
|
-
return true if @ready
|
387
|
-
return false unless IO.select([@to_io], nil, nil, 0)
|
388
|
-
try_to_finish
|
389
|
-
end
|
390
|
-
end # IS_JRUBY
|
391
|
-
|
392
|
-
def finish
|
393
|
-
return true if @ready
|
394
|
-
until try_to_finish
|
395
|
-
IO.select([@to_io], nil, nil)
|
396
|
-
end
|
397
|
-
true
|
398
|
-
end
|
399
|
-
|
400
315
|
def read_body
|
401
316
|
if @chunked_body
|
402
317
|
return read_chunked_body
|
@@ -414,7 +329,7 @@ module Puma
|
|
414
329
|
|
415
330
|
begin
|
416
331
|
chunk = @io.read_nonblock(want)
|
417
|
-
rescue
|
332
|
+
rescue IO::WaitReadable
|
418
333
|
return false
|
419
334
|
rescue SystemCallError, IOError
|
420
335
|
raise ConnectionError, "Connection error detected during read"
|
@@ -424,8 +339,7 @@ module Puma
|
|
424
339
|
unless chunk
|
425
340
|
@body.close
|
426
341
|
@buffer = nil
|
427
|
-
|
428
|
-
@ready = true
|
342
|
+
set_ready
|
429
343
|
raise EOFError
|
430
344
|
end
|
431
345
|
|
@@ -434,8 +348,7 @@ module Puma
|
|
434
348
|
if remain <= 0
|
435
349
|
@body.rewind
|
436
350
|
@buffer = nil
|
437
|
-
|
438
|
-
@ready = true
|
351
|
+
set_ready
|
439
352
|
return true
|
440
353
|
end
|
441
354
|
|
@@ -444,37 +357,145 @@ module Puma
|
|
444
357
|
false
|
445
358
|
end
|
446
359
|
|
447
|
-
def
|
448
|
-
|
449
|
-
|
450
|
-
|
360
|
+
def read_chunked_body
|
361
|
+
while true
|
362
|
+
begin
|
363
|
+
chunk = @io.read_nonblock(4096)
|
364
|
+
rescue IO::WaitReadable
|
365
|
+
return false
|
366
|
+
rescue SystemCallError, IOError
|
367
|
+
raise ConnectionError, "Connection error detected during read"
|
368
|
+
end
|
369
|
+
|
370
|
+
# No chunk means a closed socket
|
371
|
+
unless chunk
|
372
|
+
@body.close
|
373
|
+
@buffer = nil
|
374
|
+
set_ready
|
375
|
+
raise EOFError
|
376
|
+
end
|
377
|
+
|
378
|
+
if decode_chunk(chunk)
|
379
|
+
@env[CONTENT_LENGTH] = @chunked_content_length.to_s
|
380
|
+
return true
|
381
|
+
end
|
451
382
|
end
|
452
383
|
end
|
453
384
|
|
454
|
-
def
|
455
|
-
|
456
|
-
|
457
|
-
|
385
|
+
def setup_chunked_body(body)
|
386
|
+
@chunked_body = true
|
387
|
+
@partial_part_left = 0
|
388
|
+
@prev_chunk = ""
|
389
|
+
|
390
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
391
|
+
@body.unlink
|
392
|
+
@body.binmode
|
393
|
+
@tempfile = @body
|
394
|
+
@chunked_content_length = 0
|
395
|
+
|
396
|
+
if decode_chunk(body)
|
397
|
+
@env[CONTENT_LENGTH] = @chunked_content_length.to_s
|
398
|
+
return true
|
458
399
|
end
|
459
400
|
end
|
460
401
|
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
rescue StandardError
|
465
|
-
end
|
402
|
+
# @version 5.0.0
|
403
|
+
def write_chunk(str)
|
404
|
+
@chunked_content_length += @body.write(str)
|
466
405
|
end
|
467
406
|
|
468
|
-
def
|
469
|
-
|
407
|
+
def decode_chunk(chunk)
|
408
|
+
if @partial_part_left > 0
|
409
|
+
if @partial_part_left <= chunk.size
|
410
|
+
if @partial_part_left > 2
|
411
|
+
write_chunk(chunk[0..(@partial_part_left-3)]) # skip the \r\n
|
412
|
+
end
|
413
|
+
chunk = chunk[@partial_part_left..-1]
|
414
|
+
@partial_part_left = 0
|
415
|
+
else
|
416
|
+
if @partial_part_left > 2
|
417
|
+
if @partial_part_left == chunk.size + 1
|
418
|
+
# Don't include the last \r
|
419
|
+
write_chunk(chunk[0..(@partial_part_left-3)])
|
420
|
+
else
|
421
|
+
# don't include the last \r\n
|
422
|
+
write_chunk(chunk)
|
423
|
+
end
|
424
|
+
end
|
425
|
+
@partial_part_left -= chunk.size
|
426
|
+
return false
|
427
|
+
end
|
428
|
+
end
|
470
429
|
|
471
|
-
if @
|
472
|
-
|
473
|
-
|
474
|
-
|
430
|
+
if @prev_chunk.empty?
|
431
|
+
io = StringIO.new(chunk)
|
432
|
+
else
|
433
|
+
io = StringIO.new(@prev_chunk+chunk)
|
434
|
+
@prev_chunk = ""
|
475
435
|
end
|
476
436
|
|
477
|
-
|
437
|
+
while !io.eof?
|
438
|
+
line = io.gets
|
439
|
+
if line.end_with?("\r\n")
|
440
|
+
len = line.strip.to_i(16)
|
441
|
+
if len == 0
|
442
|
+
@in_last_chunk = true
|
443
|
+
@body.rewind
|
444
|
+
rest = io.read
|
445
|
+
last_crlf_size = "\r\n".bytesize
|
446
|
+
if rest.bytesize < last_crlf_size
|
447
|
+
@buffer = nil
|
448
|
+
@partial_part_left = last_crlf_size - rest.bytesize
|
449
|
+
return false
|
450
|
+
else
|
451
|
+
@buffer = rest[last_crlf_size..-1]
|
452
|
+
@buffer = nil if @buffer.empty?
|
453
|
+
set_ready
|
454
|
+
return true
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
len += 2
|
459
|
+
|
460
|
+
part = io.read(len)
|
461
|
+
|
462
|
+
unless part
|
463
|
+
@partial_part_left = len
|
464
|
+
next
|
465
|
+
end
|
466
|
+
|
467
|
+
got = part.size
|
468
|
+
|
469
|
+
case
|
470
|
+
when got == len
|
471
|
+
write_chunk(part[0..-3]) # to skip the ending \r\n
|
472
|
+
when got <= len - 2
|
473
|
+
write_chunk(part)
|
474
|
+
@partial_part_left = len - part.size
|
475
|
+
when got == len - 1 # edge where we get just \r but not \n
|
476
|
+
write_chunk(part[0..-2])
|
477
|
+
@partial_part_left = len - part.size
|
478
|
+
end
|
479
|
+
else
|
480
|
+
@prev_chunk = line
|
481
|
+
return false
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
if @in_last_chunk
|
486
|
+
set_ready
|
487
|
+
true
|
488
|
+
else
|
489
|
+
false
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
def set_ready
|
494
|
+
if @body_read_start
|
495
|
+
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @body_read_start
|
496
|
+
end
|
497
|
+
@requests_served += 1
|
498
|
+
@ready = true
|
478
499
|
end
|
479
500
|
end
|
480
501
|
end
|