puma 3.12.6 → 6.2.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 +1775 -451
- data/LICENSE +23 -20
- data/README.md +193 -65
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +59 -21
- data/docs/compile_options.md +55 -0
- data/docs/deployment.md +69 -58
- 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 +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 +2 -2
- data/docs/plugins.md +22 -12
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +47 -22
- data/docs/signals.md +13 -11
- data/docs/stats.md +142 -0
- data/docs/systemd.md +94 -120
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/PumaHttp11Service.java +2 -2
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +61 -3
- data/ext/puma_http11/http11_parser.c +103 -117
- data/ext/puma_http11/http11_parser.h +2 -2
- data/ext/puma_http11/http11_parser.java.rl +22 -38
- data/ext/puma_http11/http11_parser.rl +3 -3
- data/ext/puma_http11/http11_parser_common.rl +6 -6
- data/ext/puma_http11/mini_ssl.c +361 -99
- 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 +84 -99
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +248 -92
- data/ext/puma_http11/puma_http11.c +49 -57
- data/lib/puma/app/status.rb +71 -49
- data/lib/puma/binder.rb +242 -150
- data/lib/puma/cli.rb +38 -34
- data/lib/puma/client.rb +387 -244
- data/lib/puma/cluster/worker.rb +180 -0
- data/lib/puma/cluster/worker_handle.rb +97 -0
- data/lib/puma/cluster.rb +261 -243
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +116 -88
- data/lib/puma/const.rb +101 -100
- data/lib/puma/control_cli.rb +115 -70
- data/lib/puma/detect.rb +33 -2
- data/lib/puma/dsl.rb +731 -134
- data/lib/puma/error_logger.rb +113 -0
- data/lib/puma/events.rb +16 -112
- data/lib/puma/io_buffer.rb +42 -5
- data/lib/puma/jruby_restart.rb +2 -59
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +184 -133
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +92 -0
- data/lib/puma/minissl.rb +246 -70
- data/lib/puma/null_io.rb +18 -1
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +3 -1
- data/lib/puma/plugin.rb +7 -13
- data/lib/puma/rack/builder.rb +7 -9
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +21 -4
- data/lib/puma/reactor.rb +85 -316
- data/lib/puma/request.rb +665 -0
- data/lib/puma/runner.rb +94 -69
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +314 -771
- data/lib/puma/single.rb +20 -74
- data/lib/puma/state_file.rb +45 -8
- data/lib/puma/thread_pool.rb +142 -92
- data/lib/puma/util.rb +22 -10
- data/lib/puma.rb +60 -5
- data/lib/rack/handler/puma.rb +113 -91
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +54 -32
- 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/client.rb
CHANGED
@@ -8,9 +8,10 @@ class IO
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
require_relative 'detect'
|
12
|
+
require_relative 'io_buffer'
|
13
13
|
require 'tempfile'
|
14
|
+
require 'forwardable'
|
14
15
|
|
15
16
|
if Puma::IS_JRUBY
|
16
17
|
# We have to work around some OpenSSL buffer/io-readiness bugs
|
@@ -23,37 +24,60 @@ module Puma
|
|
23
24
|
|
24
25
|
class ConnectionError < RuntimeError; end
|
25
26
|
|
27
|
+
class HttpParserError501 < IOError; end
|
28
|
+
|
29
|
+
#———————————————————————— DO NOT USE — this class is for internal use only ———
|
30
|
+
|
31
|
+
|
26
32
|
# An instance of this class represents a unique request from a client.
|
27
|
-
# For example a web request from a browser or from CURL.
|
33
|
+
# For example, this could be a web request from a browser or from CURL.
|
28
34
|
#
|
29
35
|
# An instance of `Puma::Client` can be used as if it were an IO object
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
36
|
+
# by the reactor. The reactor is expected to call `#to_io`
|
37
|
+
# on any non-IO objects it polls. For example, nio4r internally calls
|
38
|
+
# `IO::try_convert` (which may call `#to_io`) when a new socket is
|
39
|
+
# registered.
|
33
40
|
#
|
34
41
|
# Instances of this class are responsible for knowing if
|
35
42
|
# the header and body are fully buffered via the `try_to_finish` method.
|
36
43
|
# They can be used to "time out" a response via the `timeout_at` reader.
|
37
|
-
|
44
|
+
#
|
45
|
+
class Client # :nodoc:
|
46
|
+
|
47
|
+
# this tests all values but the last, which must be chunked
|
48
|
+
ALLOWED_TRANSFER_ENCODING = %w[compress deflate gzip].freeze
|
49
|
+
|
50
|
+
# chunked body validation
|
51
|
+
CHUNK_SIZE_INVALID = /[^\h]/.freeze
|
52
|
+
CHUNK_VALID_ENDING = "\r\n".freeze
|
53
|
+
|
54
|
+
# Content-Length header value validation
|
55
|
+
CONTENT_LENGTH_VALUE_INVALID = /[^\d]/.freeze
|
56
|
+
|
57
|
+
TE_ERR_MSG = 'Invalid Transfer-Encoding'
|
58
|
+
|
59
|
+
# The object used for a request with no body. All requests with
|
60
|
+
# no body share this one object since it has no state.
|
61
|
+
EmptyBody = NullIO.new
|
62
|
+
|
38
63
|
include Puma::Const
|
39
|
-
extend
|
64
|
+
extend Forwardable
|
40
65
|
|
41
66
|
def initialize(io, env=nil)
|
42
67
|
@io = io
|
43
68
|
@to_io = io.to_io
|
69
|
+
@io_buffer = IOBuffer.new
|
44
70
|
@proto_env = env
|
45
|
-
|
46
|
-
@env = nil
|
47
|
-
else
|
48
|
-
@env = env.dup
|
49
|
-
end
|
71
|
+
@env = env ? env.dup : nil
|
50
72
|
|
51
73
|
@parser = HttpParser.new
|
52
74
|
@parsed_bytes = 0
|
53
75
|
@read_header = true
|
76
|
+
@read_proxy = false
|
54
77
|
@ready = false
|
55
78
|
|
56
79
|
@body = nil
|
80
|
+
@body_read_start = nil
|
57
81
|
@buffer = nil
|
58
82
|
@tempfile = nil
|
59
83
|
|
@@ -62,19 +86,38 @@ module Puma
|
|
62
86
|
@requests_served = 0
|
63
87
|
@hijacked = false
|
64
88
|
|
89
|
+
@http_content_length_limit = nil
|
90
|
+
@http_content_length_limit_exceeded = false
|
91
|
+
|
65
92
|
@peerip = nil
|
93
|
+
@peer_family = nil
|
94
|
+
@listener = nil
|
66
95
|
@remote_addr_header = nil
|
96
|
+
@expect_proxy_proto = false
|
97
|
+
|
98
|
+
@body_remain = 0
|
99
|
+
|
100
|
+
@in_last_chunk = false
|
101
|
+
|
102
|
+
@read_buffer = +""
|
67
103
|
end
|
68
104
|
|
69
105
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
70
|
-
:tempfile
|
106
|
+
:tempfile, :io_buffer, :http_content_length_limit_exceeded
|
71
107
|
|
72
|
-
attr_writer :peerip
|
108
|
+
attr_writer :peerip, :http_content_length_limit
|
73
109
|
|
74
|
-
attr_accessor :remote_addr_header
|
110
|
+
attr_accessor :remote_addr_header, :listener
|
75
111
|
|
76
|
-
|
112
|
+
def_delegators :@io, :closed?
|
113
|
+
|
114
|
+
# Test to see if io meets a bare minimum of functioning, @to_io needs to be
|
115
|
+
# used for MiniSSL::Socket
|
116
|
+
def io_ok?
|
117
|
+
@to_io.is_a?(::BasicSocket) && !closed?
|
118
|
+
end
|
77
119
|
|
120
|
+
# @!attribute [r] inspect
|
78
121
|
def inspect
|
79
122
|
"#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
|
80
123
|
end
|
@@ -86,24 +129,38 @@ module Puma
|
|
86
129
|
env[HIJACK_IO] ||= @io
|
87
130
|
end
|
88
131
|
|
132
|
+
# @!attribute [r] in_data_phase
|
89
133
|
def in_data_phase
|
90
|
-
|
134
|
+
!(@read_header || @read_proxy)
|
91
135
|
end
|
92
136
|
|
93
137
|
def set_timeout(val)
|
94
|
-
@timeout_at =
|
138
|
+
@timeout_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + val
|
139
|
+
end
|
140
|
+
|
141
|
+
# Number of seconds until the timeout elapses.
|
142
|
+
def timeout
|
143
|
+
[@timeout_at - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0].max
|
95
144
|
end
|
96
145
|
|
97
146
|
def reset(fast_check=true)
|
98
147
|
@parser.reset
|
148
|
+
@io_buffer.reset
|
99
149
|
@read_header = true
|
150
|
+
@read_proxy = !!@expect_proxy_proto
|
100
151
|
@env = @proto_env.dup
|
101
152
|
@body = nil
|
102
153
|
@tempfile = nil
|
103
154
|
@parsed_bytes = 0
|
104
155
|
@ready = false
|
156
|
+
@body_remain = 0
|
157
|
+
@peerip = nil if @remote_addr_header
|
158
|
+
@in_last_chunk = false
|
159
|
+
@http_content_length_limit_exceeded = false
|
105
160
|
|
106
161
|
if @buffer
|
162
|
+
return false unless try_to_parse_proxy_protocol
|
163
|
+
|
107
164
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
108
165
|
|
109
166
|
if @parser.finished?
|
@@ -114,123 +171,175 @@ module Puma
|
|
114
171
|
end
|
115
172
|
|
116
173
|
return false
|
117
|
-
|
118
|
-
|
119
|
-
|
174
|
+
else
|
175
|
+
begin
|
176
|
+
if fast_check && @to_io.wait_readable(FAST_TRACK_KA_TIMEOUT)
|
177
|
+
return try_to_finish
|
178
|
+
end
|
179
|
+
rescue IOError
|
180
|
+
# swallow it
|
181
|
+
end
|
182
|
+
|
120
183
|
end
|
121
184
|
end
|
122
185
|
|
123
186
|
def close
|
124
187
|
begin
|
125
188
|
@io.close
|
126
|
-
rescue IOError
|
127
|
-
|
189
|
+
rescue IOError, Errno::EBADF
|
190
|
+
Puma::Util.purge_interrupt_queue
|
128
191
|
end
|
129
192
|
end
|
130
193
|
|
131
|
-
#
|
132
|
-
#
|
133
|
-
|
194
|
+
# If necessary, read the PROXY protocol from the buffer. Returns
|
195
|
+
# false if more data is needed.
|
196
|
+
def try_to_parse_proxy_protocol
|
197
|
+
if @read_proxy
|
198
|
+
if @expect_proxy_proto == :v1
|
199
|
+
if @buffer.include? "\r\n"
|
200
|
+
if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer)
|
201
|
+
if md[1]
|
202
|
+
@peerip = md[1].split(" ")[0]
|
203
|
+
end
|
204
|
+
@buffer = md.post_match
|
205
|
+
end
|
206
|
+
# if the buffer has a \r\n but doesn't have a PROXY protocol
|
207
|
+
# request, this is just HTTP from a non-PROXY client; move on
|
208
|
+
@read_proxy = false
|
209
|
+
return @buffer.size > 0
|
210
|
+
else
|
211
|
+
return false
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
true
|
216
|
+
end
|
134
217
|
|
135
|
-
def
|
136
|
-
|
137
|
-
|
138
|
-
|
218
|
+
def try_to_finish
|
219
|
+
if env[CONTENT_LENGTH] && above_http_content_limit(env[CONTENT_LENGTH].to_i)
|
220
|
+
@http_content_length_limit_exceeded = true
|
221
|
+
end
|
139
222
|
|
140
|
-
@
|
141
|
-
|
142
|
-
|
223
|
+
if @http_content_length_limit_exceeded
|
224
|
+
@buffer = nil
|
225
|
+
@body = EmptyBody
|
226
|
+
set_ready
|
227
|
+
return true
|
228
|
+
end
|
143
229
|
|
144
|
-
return
|
145
|
-
end
|
230
|
+
return read_body if in_data_phase
|
146
231
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
return false
|
156
|
-
end
|
232
|
+
begin
|
233
|
+
data = @io.read_nonblock(CHUNK_SIZE)
|
234
|
+
rescue IO::WaitReadable
|
235
|
+
return false
|
236
|
+
rescue EOFError
|
237
|
+
# Swallow error, don't log
|
238
|
+
rescue SystemCallError, IOError
|
239
|
+
raise ConnectionError, "Connection error detected during read"
|
157
240
|
end
|
158
241
|
|
159
|
-
|
160
|
-
|
242
|
+
# No data means a closed socket
|
243
|
+
unless data
|
244
|
+
@buffer = nil
|
245
|
+
set_ready
|
246
|
+
raise EOFError
|
247
|
+
end
|
248
|
+
|
249
|
+
if @buffer
|
250
|
+
@buffer << data
|
161
251
|
else
|
162
|
-
|
163
|
-
@prev_chunk = ""
|
252
|
+
@buffer = data
|
164
253
|
end
|
165
254
|
|
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
|
255
|
+
return false unless try_to_parse_proxy_protocol
|
179
256
|
|
180
|
-
|
257
|
+
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
181
258
|
|
182
|
-
|
259
|
+
if @parser.finished? && above_http_content_limit(@parser.body.bytesize)
|
260
|
+
@http_content_length_limit_exceeded = true
|
261
|
+
end
|
183
262
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
263
|
+
if @parser.finished?
|
264
|
+
return setup_body
|
265
|
+
elsif @parsed_bytes >= MAX_HEADER
|
266
|
+
raise HttpParserError,
|
267
|
+
"HEADER is longer than allowed, aborting client early."
|
268
|
+
end
|
188
269
|
|
189
|
-
|
270
|
+
false
|
271
|
+
end
|
190
272
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
273
|
+
def eagerly_finish
|
274
|
+
return true if @ready
|
275
|
+
return false unless @to_io.wait_readable(0)
|
276
|
+
try_to_finish
|
277
|
+
end
|
278
|
+
|
279
|
+
def finish(timeout)
|
280
|
+
return if @ready
|
281
|
+
@to_io.wait_readable(timeout) || timeout! until try_to_finish
|
282
|
+
end
|
283
|
+
|
284
|
+
def timeout!
|
285
|
+
write_error(408) if in_data_phase
|
286
|
+
raise ConnectionError
|
287
|
+
end
|
288
|
+
|
289
|
+
def write_error(status_code)
|
290
|
+
begin
|
291
|
+
@io << ERROR_RESPONSE[status_code]
|
292
|
+
rescue StandardError
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
def peerip
|
297
|
+
return @peerip if @peerip
|
298
|
+
|
299
|
+
if @remote_addr_header
|
300
|
+
hdr = (@env[@remote_addr_header] || @io.peeraddr.last).split(/[\s,]/).first
|
301
|
+
@peerip = hdr
|
302
|
+
return hdr
|
205
303
|
end
|
206
304
|
|
207
|
-
|
305
|
+
@peerip ||= @io.peeraddr.last
|
208
306
|
end
|
209
307
|
|
210
|
-
def
|
211
|
-
|
212
|
-
begin
|
213
|
-
chunk = @io.read_nonblock(4096)
|
214
|
-
rescue Errno::EAGAIN
|
215
|
-
return false
|
216
|
-
rescue SystemCallError, IOError
|
217
|
-
raise ConnectionError, "Connection error detected during read"
|
218
|
-
end
|
308
|
+
def peer_family
|
309
|
+
return @peer_family if @peer_family
|
219
310
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
311
|
+
@peer_family ||= begin
|
312
|
+
@io.local_address.afamily
|
313
|
+
rescue
|
314
|
+
Socket::AF_INET
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
# Returns true if the persistent connection can be closed immediately
|
319
|
+
# without waiting for the configured idle/shutdown timeout.
|
320
|
+
# @version 5.0.0
|
321
|
+
#
|
322
|
+
def can_close?
|
323
|
+
# Allow connection to close if we're not in the middle of parsing a request.
|
324
|
+
@parsed_bytes == 0
|
325
|
+
end
|
228
326
|
|
229
|
-
|
327
|
+
def expect_proxy_proto=(val)
|
328
|
+
if val
|
329
|
+
if @read_header
|
330
|
+
@read_proxy = true
|
331
|
+
end
|
332
|
+
else
|
333
|
+
@read_proxy = false
|
230
334
|
end
|
335
|
+
@expect_proxy_proto = val
|
231
336
|
end
|
232
337
|
|
338
|
+
private
|
339
|
+
|
233
340
|
def setup_body
|
341
|
+
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond)
|
342
|
+
|
234
343
|
if @env[HTTP_EXPECT] == CONTINUE
|
235
344
|
# TODO allow a hook here to check the headers before
|
236
345
|
# going forward
|
@@ -243,16 +352,27 @@ module Puma
|
|
243
352
|
body = @parser.body
|
244
353
|
|
245
354
|
te = @env[TRANSFER_ENCODING2]
|
246
|
-
|
247
355
|
if te
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
356
|
+
te_lwr = te.downcase
|
357
|
+
if te.include? ','
|
358
|
+
te_ary = te_lwr.split ','
|
359
|
+
te_count = te_ary.count CHUNKED
|
360
|
+
te_valid = te_ary[0..-2].all? { |e| ALLOWED_TRANSFER_ENCODING.include? e }
|
361
|
+
if te_ary.last == CHUNKED && te_count == 1 && te_valid
|
362
|
+
@env.delete TRANSFER_ENCODING2
|
363
|
+
return setup_chunked_body body
|
364
|
+
elsif te_count >= 1
|
365
|
+
raise HttpParserError , "#{TE_ERR_MSG}, multiple chunked: '#{te}'"
|
366
|
+
elsif !te_valid
|
367
|
+
raise HttpParserError501, "#{TE_ERR_MSG}, unknown value: '#{te}'"
|
253
368
|
end
|
254
|
-
elsif
|
255
|
-
|
369
|
+
elsif te_lwr == CHUNKED
|
370
|
+
@env.delete TRANSFER_ENCODING2
|
371
|
+
return setup_chunked_body body
|
372
|
+
elsif ALLOWED_TRANSFER_ENCODING.include? te_lwr
|
373
|
+
raise HttpParserError , "#{TE_ERR_MSG}, single value must be chunked: '#{te}'"
|
374
|
+
else
|
375
|
+
raise HttpParserError501 , "#{TE_ERR_MSG}, unknown value: '#{te}'"
|
256
376
|
end
|
257
377
|
end
|
258
378
|
|
@@ -260,11 +380,15 @@ module Puma
|
|
260
380
|
|
261
381
|
cl = @env[CONTENT_LENGTH]
|
262
382
|
|
263
|
-
|
383
|
+
if cl
|
384
|
+
# cannot contain characters that are not \d
|
385
|
+
if CONTENT_LENGTH_VALUE_INVALID.match? cl
|
386
|
+
raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
|
387
|
+
end
|
388
|
+
else
|
264
389
|
@buffer = body.empty? ? nil : body
|
265
390
|
@body = EmptyBody
|
266
|
-
|
267
|
-
@ready = true
|
391
|
+
set_ready
|
268
392
|
return true
|
269
393
|
end
|
270
394
|
|
@@ -273,13 +397,13 @@ module Puma
|
|
273
397
|
if remain <= 0
|
274
398
|
@body = StringIO.new(body)
|
275
399
|
@buffer = nil
|
276
|
-
|
277
|
-
@ready = true
|
400
|
+
set_ready
|
278
401
|
return true
|
279
402
|
end
|
280
403
|
|
281
404
|
if remain > MAX_BODY
|
282
405
|
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
406
|
+
@body.unlink
|
283
407
|
@body.binmode
|
284
408
|
@tempfile = @body
|
285
409
|
else
|
@@ -292,111 +416,9 @@ module Puma
|
|
292
416
|
|
293
417
|
@body_remain = remain
|
294
418
|
|
295
|
-
return false
|
296
|
-
end
|
297
|
-
|
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
419
|
false
|
333
420
|
end
|
334
421
|
|
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
422
|
def read_body
|
401
423
|
if @chunked_body
|
402
424
|
return read_chunked_body
|
@@ -413,8 +435,8 @@ module Puma
|
|
413
435
|
end
|
414
436
|
|
415
437
|
begin
|
416
|
-
chunk = @io.read_nonblock(want)
|
417
|
-
rescue
|
438
|
+
chunk = @io.read_nonblock(want, @read_buffer)
|
439
|
+
rescue IO::WaitReadable
|
418
440
|
return false
|
419
441
|
rescue SystemCallError, IOError
|
420
442
|
raise ConnectionError, "Connection error detected during read"
|
@@ -424,8 +446,7 @@ module Puma
|
|
424
446
|
unless chunk
|
425
447
|
@body.close
|
426
448
|
@buffer = nil
|
427
|
-
|
428
|
-
@ready = true
|
449
|
+
set_ready
|
429
450
|
raise EOFError
|
430
451
|
end
|
431
452
|
|
@@ -434,8 +455,7 @@ module Puma
|
|
434
455
|
if remain <= 0
|
435
456
|
@body.rewind
|
436
457
|
@buffer = nil
|
437
|
-
|
438
|
-
@ready = true
|
458
|
+
set_ready
|
439
459
|
return true
|
440
460
|
end
|
441
461
|
|
@@ -444,37 +464,160 @@ module Puma
|
|
444
464
|
false
|
445
465
|
end
|
446
466
|
|
447
|
-
def
|
448
|
-
|
449
|
-
|
450
|
-
|
467
|
+
def read_chunked_body
|
468
|
+
while true
|
469
|
+
begin
|
470
|
+
chunk = @io.read_nonblock(4096, @read_buffer)
|
471
|
+
rescue IO::WaitReadable
|
472
|
+
return false
|
473
|
+
rescue SystemCallError, IOError
|
474
|
+
raise ConnectionError, "Connection error detected during read"
|
475
|
+
end
|
476
|
+
|
477
|
+
# No chunk means a closed socket
|
478
|
+
unless chunk
|
479
|
+
@body.close
|
480
|
+
@buffer = nil
|
481
|
+
set_ready
|
482
|
+
raise EOFError
|
483
|
+
end
|
484
|
+
|
485
|
+
if decode_chunk(chunk)
|
486
|
+
@env[CONTENT_LENGTH] = @chunked_content_length.to_s
|
487
|
+
return true
|
488
|
+
end
|
451
489
|
end
|
452
490
|
end
|
453
491
|
|
454
|
-
def
|
455
|
-
|
456
|
-
|
457
|
-
|
492
|
+
def setup_chunked_body(body)
|
493
|
+
@chunked_body = true
|
494
|
+
@partial_part_left = 0
|
495
|
+
@prev_chunk = ""
|
496
|
+
|
497
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
498
|
+
@body.unlink
|
499
|
+
@body.binmode
|
500
|
+
@tempfile = @body
|
501
|
+
@chunked_content_length = 0
|
502
|
+
|
503
|
+
if decode_chunk(body)
|
504
|
+
@env[CONTENT_LENGTH] = @chunked_content_length.to_s
|
505
|
+
return true
|
458
506
|
end
|
459
507
|
end
|
460
508
|
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
rescue StandardError
|
465
|
-
end
|
509
|
+
# @version 5.0.0
|
510
|
+
def write_chunk(str)
|
511
|
+
@chunked_content_length += @body.write(str)
|
466
512
|
end
|
467
513
|
|
468
|
-
def
|
469
|
-
|
514
|
+
def decode_chunk(chunk)
|
515
|
+
if @partial_part_left > 0
|
516
|
+
if @partial_part_left <= chunk.size
|
517
|
+
if @partial_part_left > 2
|
518
|
+
write_chunk(chunk[0..(@partial_part_left-3)]) # skip the \r\n
|
519
|
+
end
|
520
|
+
chunk = chunk[@partial_part_left..-1]
|
521
|
+
@partial_part_left = 0
|
522
|
+
else
|
523
|
+
if @partial_part_left > 2
|
524
|
+
if @partial_part_left == chunk.size + 1
|
525
|
+
# Don't include the last \r
|
526
|
+
write_chunk(chunk[0..(@partial_part_left-3)])
|
527
|
+
else
|
528
|
+
# don't include the last \r\n
|
529
|
+
write_chunk(chunk)
|
530
|
+
end
|
531
|
+
end
|
532
|
+
@partial_part_left -= chunk.size
|
533
|
+
return false
|
534
|
+
end
|
535
|
+
end
|
470
536
|
|
471
|
-
if @
|
472
|
-
|
473
|
-
|
474
|
-
|
537
|
+
if @prev_chunk.empty?
|
538
|
+
io = StringIO.new(chunk)
|
539
|
+
else
|
540
|
+
io = StringIO.new(@prev_chunk+chunk)
|
541
|
+
@prev_chunk = ""
|
475
542
|
end
|
476
543
|
|
477
|
-
|
544
|
+
while !io.eof?
|
545
|
+
line = io.gets
|
546
|
+
if line.end_with?("\r\n")
|
547
|
+
# Puma doesn't process chunk extensions, but should parse if they're
|
548
|
+
# present, which is the reason for the semicolon regex
|
549
|
+
chunk_hex = line.strip[/\A[^;]+/]
|
550
|
+
if CHUNK_SIZE_INVALID.match? chunk_hex
|
551
|
+
raise HttpParserError, "Invalid chunk size: '#{chunk_hex}'"
|
552
|
+
end
|
553
|
+
len = chunk_hex.to_i(16)
|
554
|
+
if len == 0
|
555
|
+
@in_last_chunk = true
|
556
|
+
@body.rewind
|
557
|
+
rest = io.read
|
558
|
+
last_crlf_size = "\r\n".bytesize
|
559
|
+
if rest.bytesize < last_crlf_size
|
560
|
+
@buffer = nil
|
561
|
+
@partial_part_left = last_crlf_size - rest.bytesize
|
562
|
+
return false
|
563
|
+
else
|
564
|
+
@buffer = rest[last_crlf_size..-1]
|
565
|
+
@buffer = nil if @buffer.empty?
|
566
|
+
set_ready
|
567
|
+
return true
|
568
|
+
end
|
569
|
+
end
|
570
|
+
|
571
|
+
len += 2
|
572
|
+
|
573
|
+
part = io.read(len)
|
574
|
+
|
575
|
+
unless part
|
576
|
+
@partial_part_left = len
|
577
|
+
next
|
578
|
+
end
|
579
|
+
|
580
|
+
got = part.size
|
581
|
+
|
582
|
+
case
|
583
|
+
when got == len
|
584
|
+
# proper chunked segment must end with "\r\n"
|
585
|
+
if part.end_with? CHUNK_VALID_ENDING
|
586
|
+
write_chunk(part[0..-3]) # to skip the ending \r\n
|
587
|
+
else
|
588
|
+
raise HttpParserError, "Chunk size mismatch"
|
589
|
+
end
|
590
|
+
when got <= len - 2
|
591
|
+
write_chunk(part)
|
592
|
+
@partial_part_left = len - part.size
|
593
|
+
when got == len - 1 # edge where we get just \r but not \n
|
594
|
+
write_chunk(part[0..-2])
|
595
|
+
@partial_part_left = len - part.size
|
596
|
+
end
|
597
|
+
else
|
598
|
+
@prev_chunk = line
|
599
|
+
return false
|
600
|
+
end
|
601
|
+
end
|
602
|
+
|
603
|
+
if @in_last_chunk
|
604
|
+
set_ready
|
605
|
+
true
|
606
|
+
else
|
607
|
+
false
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
def set_ready
|
612
|
+
if @body_read_start
|
613
|
+
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_millisecond) - @body_read_start
|
614
|
+
end
|
615
|
+
@requests_served += 1
|
616
|
+
@ready = true
|
617
|
+
end
|
618
|
+
|
619
|
+
def above_http_content_limit(value)
|
620
|
+
@http_content_length_limit&.< value
|
478
621
|
end
|
479
622
|
end
|
480
623
|
end
|