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