puma 3.12.1 → 5.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +1414 -448
- 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 +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 +257 -228
- 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 +39 -19
- 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 +282 -680
- 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 +42 -26
- 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
|
@@ -244,8 +262,16 @@ module Puma
|
|
244
262
|
|
245
263
|
te = @env[TRANSFER_ENCODING2]
|
246
264
|
|
247
|
-
if te
|
248
|
-
|
265
|
+
if te
|
266
|
+
if te.include?(",")
|
267
|
+
te.split(",").each do |part|
|
268
|
+
if CHUNKED.casecmp(part.strip) == 0
|
269
|
+
return setup_chunked_body(body)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
elsif CHUNKED.casecmp(te) == 0
|
273
|
+
return setup_chunked_body(body)
|
274
|
+
end
|
249
275
|
end
|
250
276
|
|
251
277
|
@chunked_body = false
|
@@ -255,8 +281,7 @@ module Puma
|
|
255
281
|
unless cl
|
256
282
|
@buffer = body.empty? ? nil : body
|
257
283
|
@body = EmptyBody
|
258
|
-
|
259
|
-
@ready = true
|
284
|
+
set_ready
|
260
285
|
return true
|
261
286
|
end
|
262
287
|
|
@@ -265,13 +290,13 @@ module Puma
|
|
265
290
|
if remain <= 0
|
266
291
|
@body = StringIO.new(body)
|
267
292
|
@buffer = nil
|
268
|
-
|
269
|
-
@ready = true
|
293
|
+
set_ready
|
270
294
|
return true
|
271
295
|
end
|
272
296
|
|
273
297
|
if remain > MAX_BODY
|
274
298
|
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
299
|
+
@body.unlink
|
275
300
|
@body.binmode
|
276
301
|
@tempfile = @body
|
277
302
|
else
|
@@ -287,108 +312,6 @@ module Puma
|
|
287
312
|
return false
|
288
313
|
end
|
289
314
|
|
290
|
-
def try_to_finish
|
291
|
-
return read_body unless @read_header
|
292
|
-
|
293
|
-
begin
|
294
|
-
data = @io.read_nonblock(CHUNK_SIZE)
|
295
|
-
rescue Errno::EAGAIN
|
296
|
-
return false
|
297
|
-
rescue SystemCallError, IOError
|
298
|
-
raise ConnectionError, "Connection error detected during read"
|
299
|
-
end
|
300
|
-
|
301
|
-
# No data means a closed socket
|
302
|
-
unless data
|
303
|
-
@buffer = nil
|
304
|
-
@requests_served += 1
|
305
|
-
@ready = true
|
306
|
-
raise EOFError
|
307
|
-
end
|
308
|
-
|
309
|
-
if @buffer
|
310
|
-
@buffer << data
|
311
|
-
else
|
312
|
-
@buffer = data
|
313
|
-
end
|
314
|
-
|
315
|
-
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
316
|
-
|
317
|
-
if @parser.finished?
|
318
|
-
return setup_body
|
319
|
-
elsif @parsed_bytes >= MAX_HEADER
|
320
|
-
raise HttpParserError,
|
321
|
-
"HEADER is longer than allowed, aborting client early."
|
322
|
-
end
|
323
|
-
|
324
|
-
false
|
325
|
-
end
|
326
|
-
|
327
|
-
if IS_JRUBY
|
328
|
-
def jruby_start_try_to_finish
|
329
|
-
return read_body unless @read_header
|
330
|
-
|
331
|
-
begin
|
332
|
-
data = @io.sysread_nonblock(CHUNK_SIZE)
|
333
|
-
rescue OpenSSL::SSL::SSLError => e
|
334
|
-
return false if e.kind_of? IO::WaitReadable
|
335
|
-
raise e
|
336
|
-
end
|
337
|
-
|
338
|
-
# No data means a closed socket
|
339
|
-
unless data
|
340
|
-
@buffer = nil
|
341
|
-
@requests_served += 1
|
342
|
-
@ready = true
|
343
|
-
raise EOFError
|
344
|
-
end
|
345
|
-
|
346
|
-
if @buffer
|
347
|
-
@buffer << data
|
348
|
-
else
|
349
|
-
@buffer = data
|
350
|
-
end
|
351
|
-
|
352
|
-
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
353
|
-
|
354
|
-
if @parser.finished?
|
355
|
-
return setup_body
|
356
|
-
elsif @parsed_bytes >= MAX_HEADER
|
357
|
-
raise HttpParserError,
|
358
|
-
"HEADER is longer than allowed, aborting client early."
|
359
|
-
end
|
360
|
-
|
361
|
-
false
|
362
|
-
end
|
363
|
-
|
364
|
-
def eagerly_finish
|
365
|
-
return true if @ready
|
366
|
-
|
367
|
-
if @io.kind_of? OpenSSL::SSL::SSLSocket
|
368
|
-
return true if jruby_start_try_to_finish
|
369
|
-
end
|
370
|
-
|
371
|
-
return false unless IO.select([@to_io], nil, nil, 0)
|
372
|
-
try_to_finish
|
373
|
-
end
|
374
|
-
|
375
|
-
else
|
376
|
-
|
377
|
-
def eagerly_finish
|
378
|
-
return true if @ready
|
379
|
-
return false unless IO.select([@to_io], nil, nil, 0)
|
380
|
-
try_to_finish
|
381
|
-
end
|
382
|
-
end # IS_JRUBY
|
383
|
-
|
384
|
-
def finish
|
385
|
-
return true if @ready
|
386
|
-
until try_to_finish
|
387
|
-
IO.select([@to_io], nil, nil)
|
388
|
-
end
|
389
|
-
true
|
390
|
-
end
|
391
|
-
|
392
315
|
def read_body
|
393
316
|
if @chunked_body
|
394
317
|
return read_chunked_body
|
@@ -406,7 +329,7 @@ module Puma
|
|
406
329
|
|
407
330
|
begin
|
408
331
|
chunk = @io.read_nonblock(want)
|
409
|
-
rescue
|
332
|
+
rescue IO::WaitReadable
|
410
333
|
return false
|
411
334
|
rescue SystemCallError, IOError
|
412
335
|
raise ConnectionError, "Connection error detected during read"
|
@@ -416,8 +339,7 @@ module Puma
|
|
416
339
|
unless chunk
|
417
340
|
@body.close
|
418
341
|
@buffer = nil
|
419
|
-
|
420
|
-
@ready = true
|
342
|
+
set_ready
|
421
343
|
raise EOFError
|
422
344
|
end
|
423
345
|
|
@@ -426,8 +348,7 @@ module Puma
|
|
426
348
|
if remain <= 0
|
427
349
|
@body.rewind
|
428
350
|
@buffer = nil
|
429
|
-
|
430
|
-
@ready = true
|
351
|
+
set_ready
|
431
352
|
return true
|
432
353
|
end
|
433
354
|
|
@@ -436,37 +357,145 @@ module Puma
|
|
436
357
|
false
|
437
358
|
end
|
438
359
|
|
439
|
-
def
|
440
|
-
|
441
|
-
|
442
|
-
|
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
|
443
382
|
end
|
444
383
|
end
|
445
384
|
|
446
|
-
def
|
447
|
-
|
448
|
-
|
449
|
-
|
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
|
450
399
|
end
|
451
400
|
end
|
452
401
|
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
rescue StandardError
|
457
|
-
end
|
402
|
+
# @version 5.0.0
|
403
|
+
def write_chunk(str)
|
404
|
+
@chunked_content_length += @body.write(str)
|
458
405
|
end
|
459
406
|
|
460
|
-
def
|
461
|
-
|
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
|
462
429
|
|
463
|
-
if @
|
464
|
-
|
465
|
-
|
466
|
-
|
430
|
+
if @prev_chunk.empty?
|
431
|
+
io = StringIO.new(chunk)
|
432
|
+
else
|
433
|
+
io = StringIO.new(@prev_chunk+chunk)
|
434
|
+
@prev_chunk = ""
|
467
435
|
end
|
468
436
|
|
469
|
-
|
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
|
470
499
|
end
|
471
500
|
end
|
472
501
|
end
|