puma 4.1.1 → 4.3.6
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 +71 -10
- data/README.md +8 -37
- data/docs/plugins.md +20 -10
- data/docs/tcp_mode.md +96 -0
- data/ext/puma_http11/extconf.rb +5 -0
- data/ext/puma_http11/http11_parser.c +40 -63
- data/ext/puma_http11/http11_parser.java.rl +21 -37
- data/ext/puma_http11/http11_parser.rl +3 -1
- data/ext/puma_http11/http11_parser_common.rl +3 -3
- data/ext/puma_http11/org/jruby/puma/Http11.java +106 -114
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +91 -106
- data/ext/puma_http11/puma_http11.c +3 -0
- data/lib/puma.rb +6 -0
- data/lib/puma/app/status.rb +33 -30
- data/lib/puma/binder.rb +38 -70
- data/lib/puma/cli.rb +4 -0
- data/lib/puma/client.rb +202 -206
- data/lib/puma/cluster.rb +13 -12
- data/lib/puma/const.rb +23 -18
- data/lib/puma/control_cli.rb +20 -3
- data/lib/puma/dsl.rb +19 -1
- data/lib/puma/launcher.rb +87 -46
- data/lib/puma/minissl/context_builder.rb +76 -0
- data/lib/puma/plugin.rb +5 -2
- data/lib/puma/reactor.rb +7 -5
- data/lib/puma/runner.rb +10 -3
- data/lib/puma/server.rb +68 -12
- data/lib/puma/thread_pool.rb +10 -32
- data/lib/rack/handler/puma.rb +0 -2
- data/tools/docker/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +10 -9
- data/lib/puma/convenient.rb +0 -25
- data/lib/puma/daemon_ext.rb +0 -33
- data/lib/puma/delegation.rb +0 -13
@@ -10,6 +10,7 @@
|
|
10
10
|
#include "ext_help.h"
|
11
11
|
#include <assert.h>
|
12
12
|
#include <string.h>
|
13
|
+
#include <ctype.h>
|
13
14
|
#include "http11_parser.h"
|
14
15
|
|
15
16
|
#ifndef MANAGED_STRINGS
|
@@ -200,6 +201,8 @@ void http_field(puma_parser* hp, const char *field, size_t flen,
|
|
200
201
|
f = rb_str_new(hp->buf, new_size);
|
201
202
|
}
|
202
203
|
|
204
|
+
while (vlen > 0 && isspace(value[vlen - 1])) vlen--;
|
205
|
+
|
203
206
|
/* check for duplicate header */
|
204
207
|
v = rb_hash_aref(hp->request, f);
|
205
208
|
|
data/lib/puma.rb
CHANGED
data/lib/puma/app/status.rb
CHANGED
@@ -1,32 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'json'
|
4
|
-
|
5
3
|
module Puma
|
6
4
|
module App
|
7
5
|
# Check out {#call}'s source code to see what actions this web application
|
8
6
|
# can respond to.
|
9
7
|
class Status
|
10
|
-
def initialize(cli)
|
11
|
-
@cli = cli
|
12
|
-
@auth_token = nil
|
13
|
-
end
|
14
8
|
OK_STATUS = '{ "status": "ok" }'.freeze
|
15
9
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
return true unless @auth_token
|
20
|
-
env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
|
21
|
-
end
|
22
|
-
|
23
|
-
def rack_response(status, body, content_type='application/json')
|
24
|
-
headers = {
|
25
|
-
'Content-Type' => content_type,
|
26
|
-
'Content-Length' => body.bytesize.to_s
|
27
|
-
}
|
28
|
-
|
29
|
-
[status, headers, [body]]
|
10
|
+
def initialize(cli, token = nil)
|
11
|
+
@cli = cli
|
12
|
+
@auth_token = token
|
30
13
|
end
|
31
14
|
|
32
15
|
def call(env)
|
@@ -34,46 +17,66 @@ module Puma
|
|
34
17
|
return rack_response(403, 'Invalid auth token', 'text/plain')
|
35
18
|
end
|
36
19
|
|
20
|
+
if env['PATH_INFO'] =~ /\/(gc-stats|stats|thread-backtraces)$/
|
21
|
+
require 'json'
|
22
|
+
end
|
23
|
+
|
37
24
|
case env['PATH_INFO']
|
38
25
|
when /\/stop$/
|
39
26
|
@cli.stop
|
40
|
-
|
27
|
+
rack_response(200, OK_STATUS)
|
41
28
|
|
42
29
|
when /\/halt$/
|
43
30
|
@cli.halt
|
44
|
-
|
31
|
+
rack_response(200, OK_STATUS)
|
45
32
|
|
46
33
|
when /\/restart$/
|
47
34
|
@cli.restart
|
48
|
-
|
35
|
+
rack_response(200, OK_STATUS)
|
49
36
|
|
50
37
|
when /\/phased-restart$/
|
51
38
|
if !@cli.phased_restart
|
52
|
-
|
39
|
+
rack_response(404, '{ "error": "phased restart not available" }')
|
53
40
|
else
|
54
|
-
|
41
|
+
rack_response(200, OK_STATUS)
|
55
42
|
end
|
56
43
|
|
57
44
|
when /\/reload-worker-directory$/
|
58
45
|
if !@cli.send(:reload_worker_directory)
|
59
|
-
|
46
|
+
rack_response(404, '{ "error": "reload_worker_directory not available" }')
|
60
47
|
else
|
61
|
-
|
48
|
+
rack_response(200, OK_STATUS)
|
62
49
|
end
|
63
50
|
|
64
51
|
when /\/gc$/
|
65
52
|
GC.start
|
66
|
-
|
53
|
+
rack_response(200, OK_STATUS)
|
67
54
|
|
68
55
|
when /\/gc-stats$/
|
69
|
-
|
56
|
+
rack_response(200, GC.stat.to_json)
|
70
57
|
|
71
58
|
when /\/stats$/
|
72
|
-
|
59
|
+
rack_response(200, @cli.stats)
|
73
60
|
else
|
74
61
|
rack_response 404, "Unsupported action", 'text/plain'
|
75
62
|
end
|
76
63
|
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def authenticate(env)
|
68
|
+
return true unless @auth_token
|
69
|
+
env['QUERY_STRING'].to_s.split(/&;/).include?("token=#{@auth_token}")
|
70
|
+
end
|
71
|
+
|
72
|
+
def rack_response(status, body, content_type='application/json')
|
73
|
+
headers = {
|
74
|
+
'Content-Type' => content_type,
|
75
|
+
'Content-Length' => body.bytesize.to_s
|
76
|
+
}
|
77
|
+
|
78
|
+
[status, headers, [body]]
|
79
|
+
end
|
77
80
|
end
|
78
81
|
end
|
79
82
|
end
|
data/lib/puma/binder.rb
CHANGED
@@ -5,6 +5,7 @@ require 'socket'
|
|
5
5
|
|
6
6
|
require 'puma/const'
|
7
7
|
require 'puma/util'
|
8
|
+
require 'puma/minissl/context_builder'
|
8
9
|
|
9
10
|
module Puma
|
10
11
|
class Binder
|
@@ -42,7 +43,7 @@ module Puma
|
|
42
43
|
@ios = []
|
43
44
|
end
|
44
45
|
|
45
|
-
attr_reader :
|
46
|
+
attr_reader :ios
|
46
47
|
|
47
48
|
def env(sock)
|
48
49
|
@envs.fetch(sock, @proto_env)
|
@@ -50,14 +51,6 @@ module Puma
|
|
50
51
|
|
51
52
|
def close
|
52
53
|
@ios.each { |i| i.close }
|
53
|
-
@unix_paths.each do |i|
|
54
|
-
# Errno::ENOENT is intermittently raised
|
55
|
-
begin
|
56
|
-
unix_socket = UNIXSocket.new i
|
57
|
-
unix_socket.close
|
58
|
-
rescue Errno::ENOENT
|
59
|
-
end
|
60
|
-
end
|
61
54
|
end
|
62
55
|
|
63
56
|
def import_from_env
|
@@ -111,7 +104,17 @@ module Puma
|
|
111
104
|
bak = params.fetch('backlog', 1024).to_i
|
112
105
|
|
113
106
|
io = add_tcp_listener uri.host, uri.port, opt, bak
|
114
|
-
|
107
|
+
|
108
|
+
@ios.each do |i|
|
109
|
+
next unless TCPServer === i
|
110
|
+
addr = if i.local_address.ipv6?
|
111
|
+
"[#{i.local_address.ip_unpack[0]}]:#{i.local_address.ip_unpack[1]}"
|
112
|
+
else
|
113
|
+
i.local_address.ip_unpack.join(':')
|
114
|
+
end
|
115
|
+
|
116
|
+
logger.log "* Listening on tcp://#{addr}"
|
117
|
+
end
|
115
118
|
end
|
116
119
|
|
117
120
|
@listeners << [str, io] if io
|
@@ -152,64 +155,7 @@ module Puma
|
|
152
155
|
@listeners << [str, io]
|
153
156
|
when "ssl"
|
154
157
|
params = Util.parse_query uri.query
|
155
|
-
|
156
|
-
|
157
|
-
MiniSSL.check
|
158
|
-
|
159
|
-
ctx = MiniSSL::Context.new
|
160
|
-
|
161
|
-
if defined?(JRUBY_VERSION)
|
162
|
-
unless params['keystore']
|
163
|
-
@events.error "Please specify the Java keystore via 'keystore='"
|
164
|
-
end
|
165
|
-
|
166
|
-
ctx.keystore = params['keystore']
|
167
|
-
|
168
|
-
unless params['keystore-pass']
|
169
|
-
@events.error "Please specify the Java keystore password via 'keystore-pass='"
|
170
|
-
end
|
171
|
-
|
172
|
-
ctx.keystore_pass = params['keystore-pass']
|
173
|
-
ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
|
174
|
-
else
|
175
|
-
unless params['key']
|
176
|
-
@events.error "Please specify the SSL key via 'key='"
|
177
|
-
end
|
178
|
-
|
179
|
-
ctx.key = params['key']
|
180
|
-
|
181
|
-
unless params['cert']
|
182
|
-
@events.error "Please specify the SSL cert via 'cert='"
|
183
|
-
end
|
184
|
-
|
185
|
-
ctx.cert = params['cert']
|
186
|
-
|
187
|
-
if ['peer', 'force_peer'].include?(params['verify_mode'])
|
188
|
-
unless params['ca']
|
189
|
-
@events.error "Please specify the SSL ca via 'ca='"
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
ctx.ca = params['ca'] if params['ca']
|
194
|
-
ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
|
195
|
-
end
|
196
|
-
|
197
|
-
ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
|
198
|
-
ctx.no_tlsv1_1 = true if params['no_tlsv1_1'] == 'true'
|
199
|
-
|
200
|
-
if params['verify_mode']
|
201
|
-
ctx.verify_mode = case params['verify_mode']
|
202
|
-
when "peer"
|
203
|
-
MiniSSL::VERIFY_PEER
|
204
|
-
when "force_peer"
|
205
|
-
MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
206
|
-
when "none"
|
207
|
-
MiniSSL::VERIFY_NONE
|
208
|
-
else
|
209
|
-
@events.error "Please specify a valid verify_mode="
|
210
|
-
MiniSSL::VERIFY_NONE
|
211
|
-
end
|
212
|
-
end
|
158
|
+
ctx = MiniSSL::ContextBuilder.new(params, @events).context
|
213
159
|
|
214
160
|
if fd = @inherited_fds.delete(str)
|
215
161
|
logger.log "* Inherited #{str}"
|
@@ -359,7 +305,7 @@ module Puma
|
|
359
305
|
# Tell the server to listen on +path+ as a UNIX domain socket.
|
360
306
|
#
|
361
307
|
def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
|
362
|
-
@unix_paths << path
|
308
|
+
@unix_paths << path unless File.exist? path
|
363
309
|
|
364
310
|
# Let anyone connect by default
|
365
311
|
umask ||= 0
|
@@ -397,7 +343,7 @@ module Puma
|
|
397
343
|
end
|
398
344
|
|
399
345
|
def inherit_unix_listener(path, fd)
|
400
|
-
@unix_paths << path
|
346
|
+
@unix_paths << path unless File.exist? path
|
401
347
|
|
402
348
|
if fd.kind_of? TCPServer
|
403
349
|
s = fd
|
@@ -413,5 +359,27 @@ module Puma
|
|
413
359
|
s
|
414
360
|
end
|
415
361
|
|
362
|
+
def close_listeners
|
363
|
+
@listeners.each do |l, io|
|
364
|
+
io.close
|
365
|
+
uri = URI.parse(l)
|
366
|
+
next unless uri.scheme == 'unix'
|
367
|
+
unix_path = "#{uri.host}#{uri.path}"
|
368
|
+
File.unlink unix_path if @unix_paths.include? unix_path
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def close_unix_paths
|
373
|
+
@unix_paths.each { |up| File.unlink(up) if File.exist? up }
|
374
|
+
end
|
375
|
+
|
376
|
+
def redirects_for_restart
|
377
|
+
redirects = {:close_others => true}
|
378
|
+
@listeners.each_with_index do |(l, io), i|
|
379
|
+
ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
|
380
|
+
redirects[io.to_i] = io.to_i
|
381
|
+
end
|
382
|
+
redirects
|
383
|
+
end
|
416
384
|
end
|
417
385
|
end
|
data/lib/puma/cli.rb
CHANGED
@@ -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
|
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,11 +24,11 @@ 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
|
-
# by the reactor
|
31
|
-
# on any non-IO objects it polls. For example nio4r internally calls
|
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
32
|
# `IO::try_convert` (which may call `#to_io`) when a new socket is
|
33
33
|
# registered.
|
34
34
|
#
|
@@ -36,8 +36,12 @@ module Puma
|
|
36
36
|
# the header and body are fully buffered via the `try_to_finish` method.
|
37
37
|
# They can be used to "time out" a response via the `timeout_at` reader.
|
38
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
|
+
|
39
43
|
include Puma::Const
|
40
|
-
extend
|
44
|
+
extend Forwardable
|
41
45
|
|
42
46
|
def initialize(io, env=nil)
|
43
47
|
@io = io
|
@@ -79,7 +83,7 @@ module Puma
|
|
79
83
|
|
80
84
|
attr_accessor :remote_addr_header
|
81
85
|
|
82
|
-
|
86
|
+
def_delegators :@io, :closed?
|
83
87
|
|
84
88
|
def inspect
|
85
89
|
"#<Puma::Client:0x#{object_id.to_s(16)} @ready=#{@ready.inspect}>"
|
@@ -144,179 +148,6 @@ module Puma
|
|
144
148
|
end
|
145
149
|
end
|
146
150
|
|
147
|
-
# The object used for a request with no body. All requests with
|
148
|
-
# no body share this one object since it has no state.
|
149
|
-
EmptyBody = NullIO.new
|
150
|
-
|
151
|
-
def setup_chunked_body(body)
|
152
|
-
@chunked_body = true
|
153
|
-
@partial_part_left = 0
|
154
|
-
@prev_chunk = ""
|
155
|
-
|
156
|
-
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
157
|
-
@body.binmode
|
158
|
-
@tempfile = @body
|
159
|
-
|
160
|
-
return decode_chunk(body)
|
161
|
-
end
|
162
|
-
|
163
|
-
def decode_chunk(chunk)
|
164
|
-
if @partial_part_left > 0
|
165
|
-
if @partial_part_left <= chunk.size
|
166
|
-
if @partial_part_left > 2
|
167
|
-
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
|
168
|
-
end
|
169
|
-
chunk = chunk[@partial_part_left..-1]
|
170
|
-
@partial_part_left = 0
|
171
|
-
else
|
172
|
-
@body << chunk if @partial_part_left > 2 # don't include the last \r\n
|
173
|
-
@partial_part_left -= chunk.size
|
174
|
-
return false
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
if @prev_chunk.empty?
|
179
|
-
io = StringIO.new(chunk)
|
180
|
-
else
|
181
|
-
io = StringIO.new(@prev_chunk+chunk)
|
182
|
-
@prev_chunk = ""
|
183
|
-
end
|
184
|
-
|
185
|
-
while !io.eof?
|
186
|
-
line = io.gets
|
187
|
-
if line.end_with?("\r\n")
|
188
|
-
len = line.strip.to_i(16)
|
189
|
-
if len == 0
|
190
|
-
@in_last_chunk = true
|
191
|
-
@body.rewind
|
192
|
-
rest = io.read
|
193
|
-
last_crlf_size = "\r\n".bytesize
|
194
|
-
if rest.bytesize < last_crlf_size
|
195
|
-
@buffer = nil
|
196
|
-
@partial_part_left = last_crlf_size - rest.bytesize
|
197
|
-
return false
|
198
|
-
else
|
199
|
-
@buffer = rest[last_crlf_size..-1]
|
200
|
-
@buffer = nil if @buffer.empty?
|
201
|
-
set_ready
|
202
|
-
return true
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
len += 2
|
207
|
-
|
208
|
-
part = io.read(len)
|
209
|
-
|
210
|
-
unless part
|
211
|
-
@partial_part_left = len
|
212
|
-
next
|
213
|
-
end
|
214
|
-
|
215
|
-
got = part.size
|
216
|
-
|
217
|
-
case
|
218
|
-
when got == len
|
219
|
-
@body << part[0..-3] # to skip the ending \r\n
|
220
|
-
when got <= len - 2
|
221
|
-
@body << part
|
222
|
-
@partial_part_left = len - part.size
|
223
|
-
when got == len - 1 # edge where we get just \r but not \n
|
224
|
-
@body << part[0..-2]
|
225
|
-
@partial_part_left = len - part.size
|
226
|
-
end
|
227
|
-
else
|
228
|
-
@prev_chunk = line
|
229
|
-
return false
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
|
-
if @in_last_chunk
|
234
|
-
set_ready
|
235
|
-
true
|
236
|
-
else
|
237
|
-
false
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
def read_chunked_body
|
242
|
-
while true
|
243
|
-
begin
|
244
|
-
chunk = @io.read_nonblock(4096)
|
245
|
-
rescue IO::WaitReadable
|
246
|
-
return false
|
247
|
-
rescue SystemCallError, IOError
|
248
|
-
raise ConnectionError, "Connection error detected during read"
|
249
|
-
end
|
250
|
-
|
251
|
-
# No chunk means a closed socket
|
252
|
-
unless chunk
|
253
|
-
@body.close
|
254
|
-
@buffer = nil
|
255
|
-
set_ready
|
256
|
-
raise EOFError
|
257
|
-
end
|
258
|
-
|
259
|
-
return true if decode_chunk(chunk)
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
def setup_body
|
264
|
-
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
265
|
-
|
266
|
-
if @env[HTTP_EXPECT] == CONTINUE
|
267
|
-
# TODO allow a hook here to check the headers before
|
268
|
-
# going forward
|
269
|
-
@io << HTTP_11_100
|
270
|
-
@io.flush
|
271
|
-
end
|
272
|
-
|
273
|
-
@read_header = false
|
274
|
-
|
275
|
-
body = @parser.body
|
276
|
-
|
277
|
-
te = @env[TRANSFER_ENCODING2]
|
278
|
-
|
279
|
-
if te && CHUNKED.casecmp(te) == 0
|
280
|
-
return setup_chunked_body(body)
|
281
|
-
end
|
282
|
-
|
283
|
-
@chunked_body = false
|
284
|
-
|
285
|
-
cl = @env[CONTENT_LENGTH]
|
286
|
-
|
287
|
-
unless cl
|
288
|
-
@buffer = body.empty? ? nil : body
|
289
|
-
@body = EmptyBody
|
290
|
-
set_ready
|
291
|
-
return true
|
292
|
-
end
|
293
|
-
|
294
|
-
remain = cl.to_i - body.bytesize
|
295
|
-
|
296
|
-
if remain <= 0
|
297
|
-
@body = StringIO.new(body)
|
298
|
-
@buffer = nil
|
299
|
-
set_ready
|
300
|
-
return true
|
301
|
-
end
|
302
|
-
|
303
|
-
if remain > MAX_BODY
|
304
|
-
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
305
|
-
@body.binmode
|
306
|
-
@tempfile = @body
|
307
|
-
else
|
308
|
-
# The body[0,0] trick is to get an empty string in the same
|
309
|
-
# encoding as body.
|
310
|
-
@body = StringIO.new body[0,0]
|
311
|
-
end
|
312
|
-
|
313
|
-
@body.write body
|
314
|
-
|
315
|
-
@body_remain = remain
|
316
|
-
|
317
|
-
return false
|
318
|
-
end
|
319
|
-
|
320
151
|
def try_to_finish
|
321
152
|
return read_body unless @read_header
|
322
153
|
|
@@ -417,6 +248,92 @@ module Puma
|
|
417
248
|
true
|
418
249
|
end
|
419
250
|
|
251
|
+
def write_error(status_code)
|
252
|
+
begin
|
253
|
+
@io << ERROR_RESPONSE[status_code]
|
254
|
+
rescue StandardError
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def peerip
|
259
|
+
return @peerip if @peerip
|
260
|
+
|
261
|
+
if @remote_addr_header
|
262
|
+
hdr = (@env[@remote_addr_header] || LOCALHOST_ADDR).split(/[\s,]/).first
|
263
|
+
@peerip = hdr
|
264
|
+
return hdr
|
265
|
+
end
|
266
|
+
|
267
|
+
@peerip ||= @io.peeraddr.last
|
268
|
+
end
|
269
|
+
|
270
|
+
private
|
271
|
+
|
272
|
+
def setup_body
|
273
|
+
@body_read_start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
274
|
+
|
275
|
+
if @env[HTTP_EXPECT] == CONTINUE
|
276
|
+
# TODO allow a hook here to check the headers before
|
277
|
+
# going forward
|
278
|
+
@io << HTTP_11_100
|
279
|
+
@io.flush
|
280
|
+
end
|
281
|
+
|
282
|
+
@read_header = false
|
283
|
+
|
284
|
+
body = @parser.body
|
285
|
+
|
286
|
+
te = @env[TRANSFER_ENCODING2]
|
287
|
+
|
288
|
+
if te
|
289
|
+
if te.include?(",")
|
290
|
+
te.split(",").each do |part|
|
291
|
+
if CHUNKED.casecmp(part.strip) == 0
|
292
|
+
return setup_chunked_body(body)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
elsif CHUNKED.casecmp(te) == 0
|
296
|
+
return setup_chunked_body(body)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
@chunked_body = false
|
301
|
+
|
302
|
+
cl = @env[CONTENT_LENGTH]
|
303
|
+
|
304
|
+
unless cl
|
305
|
+
@buffer = body.empty? ? nil : body
|
306
|
+
@body = EmptyBody
|
307
|
+
set_ready
|
308
|
+
return true
|
309
|
+
end
|
310
|
+
|
311
|
+
remain = cl.to_i - body.bytesize
|
312
|
+
|
313
|
+
if remain <= 0
|
314
|
+
@body = StringIO.new(body)
|
315
|
+
@buffer = nil
|
316
|
+
set_ready
|
317
|
+
return true
|
318
|
+
end
|
319
|
+
|
320
|
+
if remain > MAX_BODY
|
321
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
322
|
+
@body.binmode
|
323
|
+
@tempfile = @body
|
324
|
+
else
|
325
|
+
# The body[0,0] trick is to get an empty string in the same
|
326
|
+
# encoding as body.
|
327
|
+
@body = StringIO.new body[0,0]
|
328
|
+
end
|
329
|
+
|
330
|
+
@body.write body
|
331
|
+
|
332
|
+
@body_remain = remain
|
333
|
+
|
334
|
+
return false
|
335
|
+
end
|
336
|
+
|
420
337
|
def read_body
|
421
338
|
if @chunked_body
|
422
339
|
return read_chunked_body
|
@@ -462,45 +379,124 @@ module Puma
|
|
462
379
|
false
|
463
380
|
end
|
464
381
|
|
465
|
-
def
|
466
|
-
|
467
|
-
|
382
|
+
def read_chunked_body
|
383
|
+
while true
|
384
|
+
begin
|
385
|
+
chunk = @io.read_nonblock(4096)
|
386
|
+
rescue IO::WaitReadable
|
387
|
+
return false
|
388
|
+
rescue SystemCallError, IOError
|
389
|
+
raise ConnectionError, "Connection error detected during read"
|
390
|
+
end
|
391
|
+
|
392
|
+
# No chunk means a closed socket
|
393
|
+
unless chunk
|
394
|
+
@body.close
|
395
|
+
@buffer = nil
|
396
|
+
set_ready
|
397
|
+
raise EOFError
|
398
|
+
end
|
399
|
+
|
400
|
+
return true if decode_chunk(chunk)
|
468
401
|
end
|
469
|
-
@requests_served += 1
|
470
|
-
@ready = true
|
471
402
|
end
|
472
403
|
|
473
|
-
def
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
404
|
+
def setup_chunked_body(body)
|
405
|
+
@chunked_body = true
|
406
|
+
@partial_part_left = 0
|
407
|
+
@prev_chunk = ""
|
408
|
+
|
409
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
410
|
+
@body.binmode
|
411
|
+
@tempfile = @body
|
412
|
+
|
413
|
+
return decode_chunk(body)
|
478
414
|
end
|
479
415
|
|
480
|
-
def
|
481
|
-
|
482
|
-
@
|
483
|
-
|
416
|
+
def decode_chunk(chunk)
|
417
|
+
if @partial_part_left > 0
|
418
|
+
if @partial_part_left <= chunk.size
|
419
|
+
if @partial_part_left > 2
|
420
|
+
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
|
421
|
+
end
|
422
|
+
chunk = chunk[@partial_part_left..-1]
|
423
|
+
@partial_part_left = 0
|
424
|
+
else
|
425
|
+
@body << chunk if @partial_part_left > 2 # don't include the last \r\n
|
426
|
+
@partial_part_left -= chunk.size
|
427
|
+
return false
|
428
|
+
end
|
484
429
|
end
|
485
|
-
end
|
486
430
|
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
431
|
+
if @prev_chunk.empty?
|
432
|
+
io = StringIO.new(chunk)
|
433
|
+
else
|
434
|
+
io = StringIO.new(@prev_chunk+chunk)
|
435
|
+
@prev_chunk = ""
|
491
436
|
end
|
492
|
-
end
|
493
437
|
|
494
|
-
|
495
|
-
|
438
|
+
while !io.eof?
|
439
|
+
line = io.gets
|
440
|
+
if line.end_with?("\r\n")
|
441
|
+
len = line.strip.to_i(16)
|
442
|
+
if len == 0
|
443
|
+
@in_last_chunk = true
|
444
|
+
@body.rewind
|
445
|
+
rest = io.read
|
446
|
+
last_crlf_size = "\r\n".bytesize
|
447
|
+
if rest.bytesize < last_crlf_size
|
448
|
+
@buffer = nil
|
449
|
+
@partial_part_left = last_crlf_size - rest.bytesize
|
450
|
+
return false
|
451
|
+
else
|
452
|
+
@buffer = rest[last_crlf_size..-1]
|
453
|
+
@buffer = nil if @buffer.empty?
|
454
|
+
set_ready
|
455
|
+
return true
|
456
|
+
end
|
457
|
+
end
|
496
458
|
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
459
|
+
len += 2
|
460
|
+
|
461
|
+
part = io.read(len)
|
462
|
+
|
463
|
+
unless part
|
464
|
+
@partial_part_left = len
|
465
|
+
next
|
466
|
+
end
|
467
|
+
|
468
|
+
got = part.size
|
469
|
+
|
470
|
+
case
|
471
|
+
when got == len
|
472
|
+
@body << part[0..-3] # to skip the ending \r\n
|
473
|
+
when got <= len - 2
|
474
|
+
@body << part
|
475
|
+
@partial_part_left = len - part.size
|
476
|
+
when got == len - 1 # edge where we get just \r but not \n
|
477
|
+
@body << part[0..-2]
|
478
|
+
@partial_part_left = len - part.size
|
479
|
+
end
|
480
|
+
else
|
481
|
+
@prev_chunk = line
|
482
|
+
return false
|
483
|
+
end
|
501
484
|
end
|
502
485
|
|
503
|
-
|
486
|
+
if @in_last_chunk
|
487
|
+
set_ready
|
488
|
+
true
|
489
|
+
else
|
490
|
+
false
|
491
|
+
end
|
492
|
+
end
|
493
|
+
|
494
|
+
def set_ready
|
495
|
+
if @body_read_start
|
496
|
+
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @body_read_start
|
497
|
+
end
|
498
|
+
@requests_served += 1
|
499
|
+
@ready = true
|
504
500
|
end
|
505
501
|
end
|
506
502
|
end
|