puma 3.4.0 → 3.12.0
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 +5 -5
- data/{History.txt → History.md} +356 -74
- data/README.md +143 -227
- data/docs/architecture.md +36 -0
- data/{DEPLOYMENT.md → docs/deployment.md} +1 -1
- 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/plugins.md +28 -0
- data/docs/restart.md +39 -0
- data/docs/signals.md +56 -3
- data/docs/systemd.md +124 -22
- data/ext/puma_http11/extconf.rb +2 -0
- data/ext/puma_http11/http11_parser.c +291 -447
- data/ext/puma_http11/http11_parser.h +1 -0
- data/ext/puma_http11/http11_parser.rl +10 -9
- data/ext/puma_http11/http11_parser_common.rl +1 -1
- data/ext/puma_http11/io_buffer.c +7 -7
- data/ext/puma_http11/mini_ssl.c +67 -6
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +76 -94
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +15 -2
- data/ext/puma_http11/puma_http11.c +1 -0
- data/lib/puma.rb +13 -5
- data/lib/puma/app/status.rb +8 -0
- data/lib/puma/binder.rb +46 -21
- data/lib/puma/cli.rb +49 -33
- data/lib/puma/client.rb +149 -4
- data/lib/puma/cluster.rb +55 -13
- data/lib/puma/commonlogger.rb +19 -20
- data/lib/puma/compat.rb +3 -7
- data/lib/puma/configuration.rb +136 -131
- data/lib/puma/const.rb +19 -37
- data/lib/puma/control_cli.rb +38 -35
- data/lib/puma/convenient.rb +3 -3
- data/lib/puma/detect.rb +3 -1
- data/lib/puma/dsl.rb +86 -57
- data/lib/puma/events.rb +17 -13
- data/lib/puma/io_buffer.rb +1 -1
- data/lib/puma/jruby_restart.rb +0 -1
- data/lib/puma/launcher.rb +61 -30
- data/lib/puma/minissl.rb +85 -4
- data/lib/puma/null_io.rb +6 -13
- data/lib/puma/plugin.rb +12 -1
- data/lib/puma/plugin/tmp_restart.rb +1 -2
- data/lib/puma/rack/builder.rb +3 -0
- data/lib/puma/rack/urlmap.rb +9 -8
- data/lib/puma/reactor.rb +144 -0
- data/lib/puma/runner.rb +27 -1
- data/lib/puma/server.rb +135 -33
- data/lib/puma/single.rb +17 -3
- data/lib/puma/tcp_logger.rb +8 -1
- data/lib/puma/thread_pool.rb +70 -20
- data/lib/puma/util.rb +1 -5
- data/lib/rack/handler/puma.rb +58 -17
- data/tools/jungle/README.md +12 -2
- data/tools/jungle/init.d/README.md +9 -2
- data/tools/jungle/init.d/puma +85 -58
- data/tools/jungle/init.d/run-puma +16 -1
- data/tools/jungle/rc.d/README.md +74 -0
- data/tools/jungle/rc.d/puma +61 -0
- data/tools/jungle/rc.d/puma.conf +10 -0
- data/tools/jungle/upstart/puma.conf +1 -1
- data/tools/trickletest.rb +1 -1
- metadata +22 -94
- data/Gemfile +0 -13
- data/Manifest.txt +0 -78
- data/Rakefile +0 -158
- data/docs/config.md +0 -0
- data/lib/puma/rack/backports/uri/common_18.rb +0 -59
- data/lib/puma/rack/backports/uri/common_192.rb +0 -55
- data/puma.gemspec +0 -52
@@ -6,6 +6,7 @@ import org.jruby.RubyModule;
|
|
6
6
|
import org.jruby.RubyObject;
|
7
7
|
import org.jruby.RubyString;
|
8
8
|
import org.jruby.anno.JRubyMethod;
|
9
|
+
import org.jruby.javasupport.JavaEmbedUtils;
|
9
10
|
import org.jruby.runtime.Block;
|
10
11
|
import org.jruby.runtime.ObjectAllocator;
|
11
12
|
import org.jruby.runtime.ThreadContext;
|
@@ -18,6 +19,7 @@ import javax.net.ssl.SSLContext;
|
|
18
19
|
import javax.net.ssl.SSLEngine;
|
19
20
|
import javax.net.ssl.SSLEngineResult;
|
20
21
|
import javax.net.ssl.SSLException;
|
22
|
+
import javax.net.ssl.SSLPeerUnverifiedException;
|
21
23
|
import javax.net.ssl.SSLSession;
|
22
24
|
import java.io.FileInputStream;
|
23
25
|
import java.io.IOException;
|
@@ -27,6 +29,7 @@ import java.security.KeyStore;
|
|
27
29
|
import java.security.KeyStoreException;
|
28
30
|
import java.security.NoSuchAlgorithmException;
|
29
31
|
import java.security.UnrecoverableKeyException;
|
32
|
+
import java.security.cert.CertificateEncodingException;
|
30
33
|
import java.security.cert.CertificateException;
|
31
34
|
|
32
35
|
import static javax.net.ssl.SSLEngineResult.Status;
|
@@ -167,6 +170,12 @@ public class MiniSSL extends RubyObject {
|
|
167
170
|
engine.setNeedClientAuth(true);
|
168
171
|
}
|
169
172
|
|
173
|
+
IRubyObject sslCipherListObject = miniSSLContext.callMethod(threadContext, "ssl_cipher_list");
|
174
|
+
if (!sslCipherListObject.isNil()) {
|
175
|
+
String[] sslCipherList = sslCipherListObject.convertToString().asJavaString().split(",");
|
176
|
+
engine.setEnabledCipherSuites(sslCipherList);
|
177
|
+
}
|
178
|
+
|
170
179
|
SSLSession session = engine.getSession();
|
171
180
|
inboundNetData = new MiniSSLBuffer(session.getPacketBufferSize());
|
172
181
|
outboundAppData = new MiniSSLBuffer(session.getApplicationBufferSize());
|
@@ -333,7 +342,11 @@ public class MiniSSL extends RubyObject {
|
|
333
342
|
}
|
334
343
|
|
335
344
|
@JRubyMethod
|
336
|
-
public IRubyObject peercert() {
|
337
|
-
|
345
|
+
public IRubyObject peercert() throws CertificateEncodingException {
|
346
|
+
try {
|
347
|
+
return JavaEmbedUtils.javaToRuby(getRuntime(), engine.getSession().getPeerCertificates()[0].getEncoded());
|
348
|
+
} catch (SSLPeerUnverifiedException ex) {
|
349
|
+
return getRuntime().getNil();
|
350
|
+
}
|
338
351
|
}
|
339
352
|
}
|
data/lib/puma.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# Standard libraries
|
2
2
|
require 'socket'
|
3
3
|
require 'tempfile'
|
4
|
-
require 'yaml'
|
5
4
|
require 'time'
|
6
5
|
require 'etc'
|
7
6
|
require 'uri'
|
@@ -9,7 +8,16 @@ require 'stringio'
|
|
9
8
|
|
10
9
|
require 'thread'
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
module Puma
|
12
|
+
autoload :Const, 'puma/const'
|
13
|
+
autoload :Server, 'puma/server'
|
14
|
+
autoload :Launcher, 'puma/launcher'
|
15
|
+
|
16
|
+
def self.stats_object=(val)
|
17
|
+
@get_stats = val
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.stats
|
21
|
+
@get_stats.stats
|
22
|
+
end
|
23
|
+
end
|
data/lib/puma/app/status.rb
CHANGED
@@ -55,6 +55,14 @@ module Puma
|
|
55
55
|
return rack_response(200, OK_STATUS)
|
56
56
|
end
|
57
57
|
|
58
|
+
when /\/gc$/
|
59
|
+
GC.start
|
60
|
+
return rack_response(200, OK_STATUS)
|
61
|
+
|
62
|
+
when /\/gc-stats$/
|
63
|
+
json = "{" + GC.stat.map { |k, v| "\"#{k}\": #{v}" }.join(",") + "}"
|
64
|
+
return rack_response(200, json)
|
65
|
+
|
58
66
|
when /\/stats$/
|
59
67
|
return rack_response(200, @cli.stats)
|
60
68
|
else
|
data/lib/puma/binder.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
-
require 'puma/const'
|
2
1
|
require 'uri'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
require 'puma/const'
|
5
|
+
require 'puma/util'
|
3
6
|
|
4
7
|
module Puma
|
5
8
|
class Binder
|
@@ -102,7 +105,7 @@ module Puma
|
|
102
105
|
io = add_tcp_listener uri.host, uri.port, opt, bak
|
103
106
|
end
|
104
107
|
|
105
|
-
@listeners << [str, io]
|
108
|
+
@listeners << [str, io] if io
|
106
109
|
when "unix"
|
107
110
|
path = "#{uri.host}#{uri.path}".gsub("%20", " ")
|
108
111
|
|
@@ -117,7 +120,7 @@ module Puma
|
|
117
120
|
|
118
121
|
umask = nil
|
119
122
|
mode = nil
|
120
|
-
backlog =
|
123
|
+
backlog = 1024
|
121
124
|
|
122
125
|
if uri.query
|
123
126
|
params = Util.parse_query uri.query
|
@@ -140,11 +143,11 @@ module Puma
|
|
140
143
|
|
141
144
|
@listeners << [str, io]
|
142
145
|
when "ssl"
|
143
|
-
MiniSSL.check
|
144
|
-
|
145
146
|
params = Util.parse_query uri.query
|
146
147
|
require 'puma/minissl'
|
147
148
|
|
149
|
+
MiniSSL.check
|
150
|
+
|
148
151
|
ctx = MiniSSL::Context.new
|
149
152
|
|
150
153
|
if defined?(JRUBY_VERSION)
|
@@ -159,6 +162,7 @@ module Puma
|
|
159
162
|
end
|
160
163
|
|
161
164
|
ctx.keystore_pass = params['keystore-pass']
|
165
|
+
ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
|
162
166
|
else
|
163
167
|
unless params['key']
|
164
168
|
@events.error "Please specify the SSL key via 'key='"
|
@@ -179,20 +183,21 @@ module Puma
|
|
179
183
|
end
|
180
184
|
|
181
185
|
ctx.ca = params['ca'] if params['ca']
|
186
|
+
ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
|
187
|
+
end
|
182
188
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
end
|
189
|
+
if params['verify_mode']
|
190
|
+
ctx.verify_mode = case params['verify_mode']
|
191
|
+
when "peer"
|
192
|
+
MiniSSL::VERIFY_PEER
|
193
|
+
when "force_peer"
|
194
|
+
MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
195
|
+
when "none"
|
196
|
+
MiniSSL::VERIFY_NONE
|
197
|
+
else
|
198
|
+
@events.error "Please specify a valid verify_mode="
|
199
|
+
MiniSSL::VERIFY_NONE
|
200
|
+
end
|
196
201
|
end
|
197
202
|
|
198
203
|
if fd = @inherited_fds.delete(str)
|
@@ -206,7 +211,7 @@ module Puma
|
|
206
211
|
io = add_ssl_listener uri.host, uri.port, ctx
|
207
212
|
end
|
208
213
|
|
209
|
-
@listeners << [str, io]
|
214
|
+
@listeners << [str, io] if io
|
210
215
|
else
|
211
216
|
logger.error "Invalid URI: #{str}"
|
212
217
|
end
|
@@ -240,7 +245,12 @@ module Puma
|
|
240
245
|
# We have to unlink a unix socket path that's not being used
|
241
246
|
File.unlink key[1] if key[0] == :unix
|
242
247
|
end
|
248
|
+
end
|
243
249
|
|
250
|
+
def loopback_addresses
|
251
|
+
Socket.ip_address_list.select do |addrinfo|
|
252
|
+
addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
|
253
|
+
end.map { |addrinfo| addrinfo.ip_address }.uniq
|
244
254
|
end
|
245
255
|
|
246
256
|
# Tell the server to listen on host +host+, port +port+.
|
@@ -251,6 +261,13 @@ module Puma
|
|
251
261
|
# allow to accumulate before returning connection refused.
|
252
262
|
#
|
253
263
|
def add_tcp_listener(host, port, optimize_for_latency=true, backlog=1024)
|
264
|
+
if host == "localhost"
|
265
|
+
loopback_addresses.each do |addr|
|
266
|
+
add_tcp_listener addr, port, optimize_for_latency, backlog
|
267
|
+
end
|
268
|
+
return
|
269
|
+
end
|
270
|
+
|
254
271
|
host = host[1..-2] if host and host[0..0] == '['
|
255
272
|
s = TCPServer.new(host, port)
|
256
273
|
if optimize_for_latency
|
@@ -283,6 +300,13 @@ module Puma
|
|
283
300
|
|
284
301
|
MiniSSL.check
|
285
302
|
|
303
|
+
if host == "localhost"
|
304
|
+
loopback_addresses.each do |addr|
|
305
|
+
add_ssl_listener addr, port, ctx, optimize_for_latency, backlog
|
306
|
+
end
|
307
|
+
return
|
308
|
+
end
|
309
|
+
|
286
310
|
host = host[1..-2] if host[0..0] == '['
|
287
311
|
s = TCPServer.new(host, port)
|
288
312
|
if optimize_for_latency
|
@@ -291,6 +315,7 @@ module Puma
|
|
291
315
|
s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
|
292
316
|
s.listen backlog
|
293
317
|
|
318
|
+
|
294
319
|
ssl = MiniSSL::Server.new s, ctx
|
295
320
|
env = @proto_env.dup
|
296
321
|
env[HTTPS_KEY] = HTTPS
|
@@ -322,7 +347,7 @@ module Puma
|
|
322
347
|
|
323
348
|
# Tell the server to listen on +path+ as a UNIX domain socket.
|
324
349
|
#
|
325
|
-
def add_unix_listener(path, umask=nil, mode=nil, backlog=
|
350
|
+
def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
|
326
351
|
@unix_paths << path
|
327
352
|
|
328
353
|
# Let anyone connect by default
|
@@ -343,7 +368,7 @@ module Puma
|
|
343
368
|
end
|
344
369
|
|
345
370
|
s = UNIXServer.new(path)
|
346
|
-
s.listen backlog
|
371
|
+
s.listen backlog
|
347
372
|
@ios << s
|
348
373
|
ensure
|
349
374
|
File.umask old_mask
|
data/lib/puma/cli.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
require 'uri'
|
3
3
|
|
4
|
+
require 'puma'
|
5
|
+
require 'puma/configuration'
|
4
6
|
require 'puma/launcher'
|
7
|
+
require 'puma/const'
|
8
|
+
require 'puma/events'
|
5
9
|
|
6
10
|
module Puma
|
7
11
|
class << self
|
@@ -44,21 +48,21 @@ module Puma
|
|
44
48
|
@parser.parse! @argv
|
45
49
|
|
46
50
|
if file = @argv.shift
|
47
|
-
@conf.configure do |
|
48
|
-
|
51
|
+
@conf.configure do |user_config, file_config|
|
52
|
+
file_config.rackup file
|
49
53
|
end
|
50
54
|
end
|
51
55
|
rescue UnsupportedOption
|
52
56
|
exit 1
|
53
57
|
end
|
54
58
|
|
55
|
-
@conf.configure do |
|
59
|
+
@conf.configure do |user_config, file_config|
|
56
60
|
if @stdout || @stderr
|
57
|
-
|
61
|
+
user_config.stdout_redirect @stdout, @stderr, @append
|
58
62
|
end
|
59
63
|
|
60
64
|
if @control_url
|
61
|
-
|
65
|
+
user_config.activate_control_app @control_url, @control_options
|
62
66
|
end
|
63
67
|
end
|
64
68
|
|
@@ -80,27 +84,35 @@ module Puma
|
|
80
84
|
raise UnsupportedOption
|
81
85
|
end
|
82
86
|
|
87
|
+
def configure_control_url(command_line_arg)
|
88
|
+
if command_line_arg
|
89
|
+
@control_url = command_line_arg
|
90
|
+
elsif Puma.jruby?
|
91
|
+
unsupported "No default url available on JRuby"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
83
95
|
# Build the OptionParser object to handle the available options.
|
84
96
|
#
|
85
97
|
|
86
98
|
def setup_options
|
87
|
-
@conf = Configuration.new do |
|
99
|
+
@conf = Configuration.new do |user_config, file_config|
|
88
100
|
@parser = OptionParser.new do |o|
|
89
101
|
o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
|
90
|
-
|
102
|
+
user_config.bind arg
|
91
103
|
end
|
92
104
|
|
93
105
|
o.on "-C", "--config PATH", "Load PATH as a config file" do |arg|
|
94
|
-
|
106
|
+
file_config.load arg
|
95
107
|
end
|
96
108
|
|
97
|
-
o.on "--control URL", "The bind url to use for the control server"
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
109
|
+
o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg|
|
110
|
+
configure_control_url(arg)
|
111
|
+
end
|
112
|
+
|
113
|
+
# alias --control-url for backwards-compatibility
|
114
|
+
o.on "--control URL", "DEPRECATED alias for --control-url" do |arg|
|
115
|
+
configure_control_url(arg)
|
104
116
|
end
|
105
117
|
|
106
118
|
o.on "--control-token TOKEN",
|
@@ -109,21 +121,21 @@ module Puma
|
|
109
121
|
end
|
110
122
|
|
111
123
|
o.on "-d", "--daemon", "Daemonize the server into the background" do
|
112
|
-
|
113
|
-
|
124
|
+
user_config.daemonize
|
125
|
+
user_config.quiet
|
114
126
|
end
|
115
127
|
|
116
128
|
o.on "--debug", "Log lowlevel debugging information" do
|
117
|
-
|
129
|
+
user_config.debug
|
118
130
|
end
|
119
131
|
|
120
132
|
o.on "--dir DIR", "Change to DIR before starting" do |d|
|
121
|
-
|
133
|
+
user_config.directory d
|
122
134
|
end
|
123
135
|
|
124
136
|
o.on "-e", "--environment ENVIRONMENT",
|
125
137
|
"The environment to run the Rack app on (default development)" do |arg|
|
126
|
-
|
138
|
+
user_config.environment arg
|
127
139
|
end
|
128
140
|
|
129
141
|
o.on "-I", "--include PATH", "Specify $LOAD_PATH directories" do |arg|
|
@@ -132,50 +144,54 @@ module Puma
|
|
132
144
|
|
133
145
|
o.on "-p", "--port PORT", "Define the TCP port to bind to",
|
134
146
|
"Use -b for more advanced options" do |arg|
|
135
|
-
|
147
|
+
user_config.bind "tcp://#{Configuration::DefaultTCPHost}:#{arg}"
|
136
148
|
end
|
137
149
|
|
138
150
|
o.on "--pidfile PATH", "Use PATH as a pidfile" do |arg|
|
139
|
-
|
151
|
+
user_config.pidfile arg
|
140
152
|
end
|
141
153
|
|
142
154
|
o.on "--preload", "Preload the app. Cluster mode only" do
|
143
|
-
|
155
|
+
user_config.preload_app!
|
144
156
|
end
|
145
157
|
|
146
158
|
o.on "--prune-bundler", "Prune out the bundler env if possible" do
|
147
|
-
|
159
|
+
user_config.prune_bundler
|
148
160
|
end
|
149
161
|
|
150
162
|
o.on "-q", "--quiet", "Do not log requests internally (default true)" do
|
151
|
-
|
163
|
+
user_config.quiet
|
152
164
|
end
|
153
165
|
|
154
166
|
o.on "-v", "--log-requests", "Log requests as they occur" do
|
155
|
-
|
167
|
+
user_config.log_requests
|
156
168
|
end
|
157
169
|
|
158
170
|
o.on "-R", "--restart-cmd CMD",
|
159
171
|
"The puma command to run during a hot restart",
|
160
172
|
"Default: inferred" do |cmd|
|
161
|
-
|
173
|
+
user_config.restart_command cmd
|
162
174
|
end
|
163
175
|
|
164
176
|
o.on "-S", "--state PATH", "Where to store the state details" do |arg|
|
165
|
-
|
177
|
+
user_config.state_path arg
|
166
178
|
end
|
167
179
|
|
168
180
|
o.on '-t', '--threads INT', "min:max threads to use (default 0:16)" do |arg|
|
169
181
|
min, max = arg.split(":")
|
170
182
|
if max
|
171
|
-
|
183
|
+
user_config.threads min, max
|
172
184
|
else
|
173
|
-
|
185
|
+
user_config.threads min, min
|
174
186
|
end
|
175
187
|
end
|
176
188
|
|
177
189
|
o.on "--tcp-mode", "Run the app in raw TCP mode instead of HTTP mode" do
|
178
|
-
|
190
|
+
user_config.tcp_mode!
|
191
|
+
end
|
192
|
+
|
193
|
+
o.on "--early-hints", "Enable early hints support" do
|
194
|
+
user_config.early_hints
|
179
195
|
end
|
180
196
|
|
181
197
|
o.on "-V", "--version", "Print the version information" do
|
@@ -185,11 +201,11 @@ module Puma
|
|
185
201
|
|
186
202
|
o.on "-w", "--workers COUNT",
|
187
203
|
"Activate cluster mode: How many worker processes to create" do |arg|
|
188
|
-
|
204
|
+
user_config.workers arg
|
189
205
|
end
|
190
206
|
|
191
207
|
o.on "--tag NAME", "Additional text to display in process listing" do |arg|
|
192
|
-
|
208
|
+
user_config.tag arg
|
193
209
|
end
|
194
210
|
|
195
211
|
o.on "--redirect-stdout FILE", "Redirect STDOUT to a specific file" do |arg|
|
data/lib/puma/client.rb
CHANGED
@@ -8,6 +8,7 @@ end
|
|
8
8
|
|
9
9
|
require 'puma/detect'
|
10
10
|
require 'puma/delegation'
|
11
|
+
require 'tempfile'
|
11
12
|
|
12
13
|
if Puma::IS_JRUBY
|
13
14
|
# We have to work around some OpenSSL buffer/io-readiness bugs
|
@@ -20,6 +21,17 @@ module Puma
|
|
20
21
|
|
21
22
|
class ConnectionError < RuntimeError; end
|
22
23
|
|
24
|
+
# An instance of this class represents a unique request from a client.
|
25
|
+
# For example a web request from a browser or from CURL. This
|
26
|
+
#
|
27
|
+
# An instance of `Puma::Client` can be used as if it were an IO object
|
28
|
+
# for example it is passed into `IO.select` inside of the `Puma::Reactor`.
|
29
|
+
# This is accomplished by the `to_io` method which gets called on any
|
30
|
+
# non-IO objects being used with the IO api such as `IO.select.
|
31
|
+
#
|
32
|
+
# Instances of this class are responsible for knowing if
|
33
|
+
# the header and body are fully buffered via the `try_to_finish` method.
|
34
|
+
# They can be used to "time out" a response via the `timeout_at` reader.
|
23
35
|
class Client
|
24
36
|
include Puma::Const
|
25
37
|
extend Puma::Delegation
|
@@ -110,6 +122,7 @@ module Puma
|
|
110
122
|
begin
|
111
123
|
@io.close
|
112
124
|
rescue IOError
|
125
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
113
126
|
end
|
114
127
|
end
|
115
128
|
|
@@ -117,9 +130,123 @@ module Puma
|
|
117
130
|
# no body share this one object since it has no state.
|
118
131
|
EmptyBody = NullIO.new
|
119
132
|
|
133
|
+
def setup_chunked_body(body)
|
134
|
+
@chunked_body = true
|
135
|
+
@partial_part_left = 0
|
136
|
+
@prev_chunk = ""
|
137
|
+
|
138
|
+
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
139
|
+
@body.binmode
|
140
|
+
@tempfile = @body
|
141
|
+
|
142
|
+
return decode_chunk(body)
|
143
|
+
end
|
144
|
+
|
145
|
+
def decode_chunk(chunk)
|
146
|
+
if @partial_part_left > 0
|
147
|
+
if @partial_part_left <= chunk.size
|
148
|
+
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
|
149
|
+
chunk = chunk[@partial_part_left..-1]
|
150
|
+
else
|
151
|
+
@body << chunk
|
152
|
+
@partial_part_left -= chunk.size
|
153
|
+
return false
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
if @prev_chunk.empty?
|
158
|
+
io = StringIO.new(chunk)
|
159
|
+
else
|
160
|
+
io = StringIO.new(@prev_chunk+chunk)
|
161
|
+
@prev_chunk = ""
|
162
|
+
end
|
163
|
+
|
164
|
+
while !io.eof?
|
165
|
+
line = io.gets
|
166
|
+
if line.end_with?("\r\n")
|
167
|
+
len = line.strip.to_i(16)
|
168
|
+
if len == 0
|
169
|
+
@body.rewind
|
170
|
+
rest = io.read
|
171
|
+
@buffer = rest.empty? ? nil : rest
|
172
|
+
@requests_served += 1
|
173
|
+
@ready = true
|
174
|
+
return true
|
175
|
+
end
|
176
|
+
|
177
|
+
len += 2
|
178
|
+
|
179
|
+
part = io.read(len)
|
180
|
+
|
181
|
+
unless part
|
182
|
+
@partial_part_left = len
|
183
|
+
next
|
184
|
+
end
|
185
|
+
|
186
|
+
got = part.size
|
187
|
+
|
188
|
+
case
|
189
|
+
when got == len
|
190
|
+
@body << part[0..-3] # to skip the ending \r\n
|
191
|
+
when got <= len - 2
|
192
|
+
@body << part
|
193
|
+
@partial_part_left = len - part.size
|
194
|
+
when got == len - 1 # edge where we get just \r but not \n
|
195
|
+
@body << part[0..-2]
|
196
|
+
@partial_part_left = len - part.size
|
197
|
+
end
|
198
|
+
else
|
199
|
+
@prev_chunk = line
|
200
|
+
return false
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
return false
|
205
|
+
end
|
206
|
+
|
207
|
+
def read_chunked_body
|
208
|
+
while true
|
209
|
+
begin
|
210
|
+
chunk = @io.read_nonblock(4096)
|
211
|
+
rescue Errno::EAGAIN
|
212
|
+
return false
|
213
|
+
rescue SystemCallError, IOError
|
214
|
+
raise ConnectionError, "Connection error detected during read"
|
215
|
+
end
|
216
|
+
|
217
|
+
# No chunk means a closed socket
|
218
|
+
unless chunk
|
219
|
+
@body.close
|
220
|
+
@buffer = nil
|
221
|
+
@requests_served += 1
|
222
|
+
@ready = true
|
223
|
+
raise EOFError
|
224
|
+
end
|
225
|
+
|
226
|
+
return true if decode_chunk(chunk)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
120
230
|
def setup_body
|
121
|
-
@
|
231
|
+
if @env[HTTP_EXPECT] == CONTINUE
|
232
|
+
# TODO allow a hook here to check the headers before
|
233
|
+
# going forward
|
234
|
+
@io << HTTP_11_100
|
235
|
+
@io.flush
|
236
|
+
end
|
237
|
+
|
238
|
+
@read_header = false
|
239
|
+
|
122
240
|
body = @parser.body
|
241
|
+
|
242
|
+
te = @env[TRANSFER_ENCODING2]
|
243
|
+
|
244
|
+
if te && CHUNKED.casecmp(te) == 0
|
245
|
+
return setup_chunked_body(body)
|
246
|
+
end
|
247
|
+
|
248
|
+
@chunked_body = false
|
249
|
+
|
123
250
|
cl = @env[CONTENT_LENGTH]
|
124
251
|
|
125
252
|
unless cl
|
@@ -154,8 +281,6 @@ module Puma
|
|
154
281
|
|
155
282
|
@body_remain = remain
|
156
283
|
|
157
|
-
@read_header = false
|
158
|
-
|
159
284
|
return false
|
160
285
|
end
|
161
286
|
|
@@ -170,6 +295,14 @@ module Puma
|
|
170
295
|
raise ConnectionError, "Connection error detected during read"
|
171
296
|
end
|
172
297
|
|
298
|
+
# No data means a closed socket
|
299
|
+
unless data
|
300
|
+
@buffer = nil
|
301
|
+
@requests_served += 1
|
302
|
+
@ready = true
|
303
|
+
raise EOFError
|
304
|
+
end
|
305
|
+
|
173
306
|
if @buffer
|
174
307
|
@buffer << data
|
175
308
|
else
|
@@ -184,7 +317,7 @@ module Puma
|
|
184
317
|
raise HttpParserError,
|
185
318
|
"HEADER is longer than allowed, aborting client early."
|
186
319
|
end
|
187
|
-
|
320
|
+
|
188
321
|
false
|
189
322
|
end
|
190
323
|
|
@@ -199,6 +332,14 @@ module Puma
|
|
199
332
|
raise e
|
200
333
|
end
|
201
334
|
|
335
|
+
# No data means a closed socket
|
336
|
+
unless data
|
337
|
+
@buffer = nil
|
338
|
+
@requests_served += 1
|
339
|
+
@ready = true
|
340
|
+
raise EOFError
|
341
|
+
end
|
342
|
+
|
202
343
|
if @buffer
|
203
344
|
@buffer << data
|
204
345
|
else
|
@@ -246,6 +387,10 @@ module Puma
|
|
246
387
|
end
|
247
388
|
|
248
389
|
def read_body
|
390
|
+
if @chunked_body
|
391
|
+
return read_chunked_body
|
392
|
+
end
|
393
|
+
|
249
394
|
# Read an odd sized chunk so we can read even sized ones
|
250
395
|
# after this
|
251
396
|
remain = @body_remain
|