puma 3.7.1 → 4.1.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.md +229 -1
- data/README.md +179 -212
- data/docs/architecture.md +37 -0
- data/{DEPLOYMENT.md → docs/deployment.md} +24 -4
- 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 +41 -0
- data/docs/signals.md +56 -3
- data/docs/systemd.md +130 -37
- data/ext/puma_http11/PumaHttp11Service.java +2 -0
- data/ext/puma_http11/extconf.rb +8 -0
- data/ext/puma_http11/http11_parser.c +84 -84
- data/ext/puma_http11/http11_parser.rl +9 -9
- data/ext/puma_http11/mini_ssl.c +105 -9
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +13 -16
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +30 -6
- data/lib/puma.rb +10 -0
- data/lib/puma/accept_nonblock.rb +2 -0
- data/lib/puma/app/status.rb +13 -0
- data/lib/puma/binder.rb +33 -18
- data/lib/puma/cli.rb +48 -33
- data/lib/puma/client.rb +94 -22
- data/lib/puma/cluster.rb +69 -21
- data/lib/puma/commonlogger.rb +2 -0
- data/lib/puma/configuration.rb +134 -136
- data/lib/puma/const.rb +16 -2
- data/lib/puma/control_cli.rb +31 -18
- data/lib/puma/convenient.rb +5 -3
- data/lib/puma/daemon_ext.rb +2 -0
- data/lib/puma/delegation.rb +2 -0
- data/lib/puma/detect.rb +2 -0
- data/lib/puma/dsl.rb +349 -113
- data/lib/puma/events.rb +8 -4
- data/lib/puma/io_buffer.rb +3 -6
- data/lib/puma/jruby_restart.rb +2 -1
- data/lib/puma/launcher.rb +60 -36
- data/lib/puma/minissl.rb +85 -28
- data/lib/puma/null_io.rb +2 -0
- data/lib/puma/plugin.rb +2 -0
- data/lib/puma/plugin/tmp_restart.rb +3 -2
- data/lib/puma/rack/builder.rb +4 -1
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +218 -30
- data/lib/puma/runner.rb +18 -4
- data/lib/puma/server.rb +149 -56
- data/lib/puma/single.rb +16 -5
- data/lib/puma/state_file.rb +2 -0
- data/lib/puma/tcp_logger.rb +2 -0
- data/lib/puma/thread_pool.rb +59 -6
- data/lib/puma/util.rb +2 -6
- data/lib/rack/handler/puma.rb +58 -19
- data/tools/jungle/README.md +12 -2
- data/tools/jungle/init.d/README.md +2 -0
- data/tools/jungle/init.d/puma +8 -8
- data/tools/jungle/init.d/run-puma +1 -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/trickletest.rb +1 -1
- metadata +25 -85
- data/.github/issue_template.md +0 -20
- data/Gemfile +0 -12
- data/Manifest.txt +0 -77
- data/Rakefile +0 -158
- data/gemfiles/2.1-Gemfile +0 -12
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/java_io_buffer.rb +0 -45
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
- data/puma.gemspec +0 -52
data/lib/puma.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Standard libraries
|
2
4
|
require 'socket'
|
3
5
|
require 'tempfile'
|
@@ -12,4 +14,12 @@ module Puma
|
|
12
14
|
autoload :Const, 'puma/const'
|
13
15
|
autoload :Server, 'puma/server'
|
14
16
|
autoload :Launcher, 'puma/launcher'
|
17
|
+
|
18
|
+
def self.stats_object=(val)
|
19
|
+
@get_stats = val
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.stats
|
23
|
+
@get_stats.stats
|
24
|
+
end
|
15
25
|
end
|
data/lib/puma/accept_nonblock.rb
CHANGED
data/lib/puma/app/status.rb
CHANGED
@@ -1,5 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
1
5
|
module Puma
|
2
6
|
module App
|
7
|
+
# Check out {#call}'s source code to see what actions this web application
|
8
|
+
# can respond to.
|
3
9
|
class Status
|
4
10
|
def initialize(cli)
|
5
11
|
@cli = cli
|
@@ -55,6 +61,13 @@ module Puma
|
|
55
61
|
return rack_response(200, OK_STATUS)
|
56
62
|
end
|
57
63
|
|
64
|
+
when /\/gc$/
|
65
|
+
GC.start
|
66
|
+
return rack_response(200, OK_STATUS)
|
67
|
+
|
68
|
+
when /\/gc-stats$/
|
69
|
+
return rack_response(200, GC.stat.to_json)
|
70
|
+
|
58
71
|
when /\/stats$/
|
59
72
|
return rack_response(200, @cli.stats)
|
60
73
|
else
|
data/lib/puma/binder.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'uri'
|
2
4
|
require 'socket'
|
3
5
|
|
@@ -48,7 +50,14 @@ module Puma
|
|
48
50
|
|
49
51
|
def close
|
50
52
|
@ios.each { |i| i.close }
|
51
|
-
@unix_paths.each
|
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
|
52
61
|
end
|
53
62
|
|
54
63
|
def import_from_env
|
@@ -90,19 +99,19 @@ module Puma
|
|
90
99
|
case uri.scheme
|
91
100
|
when "tcp"
|
92
101
|
if fd = @inherited_fds.delete(str)
|
93
|
-
logger.log "* Inherited #{str}"
|
94
102
|
io = inherit_tcp_listener uri.host, uri.port, fd
|
103
|
+
logger.log "* Inherited #{str}"
|
95
104
|
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
96
|
-
logger.log "* Activated #{str}"
|
97
105
|
io = inherit_tcp_listener uri.host, uri.port, sock
|
106
|
+
logger.log "* Activated #{str}"
|
98
107
|
else
|
99
108
|
params = Util.parse_query uri.query
|
100
109
|
|
101
110
|
opt = params.key?('low_latency')
|
102
111
|
bak = params.fetch('backlog', 1024).to_i
|
103
112
|
|
104
|
-
logger.log "* Listening on #{str}"
|
105
113
|
io = add_tcp_listener uri.host, uri.port, opt, bak
|
114
|
+
logger.log "* Listening on #{str}"
|
106
115
|
end
|
107
116
|
|
108
117
|
@listeners << [str, io] if io
|
@@ -110,17 +119,15 @@ module Puma
|
|
110
119
|
path = "#{uri.host}#{uri.path}".gsub("%20", " ")
|
111
120
|
|
112
121
|
if fd = @inherited_fds.delete(str)
|
113
|
-
logger.log "* Inherited #{str}"
|
114
122
|
io = inherit_unix_listener path, fd
|
123
|
+
logger.log "* Inherited #{str}"
|
115
124
|
elsif sock = @activated_sockets.delete([ :unix, path ])
|
116
|
-
logger.log "* Activated #{str}"
|
117
125
|
io = inherit_unix_listener path, sock
|
126
|
+
logger.log "* Activated #{str}"
|
118
127
|
else
|
119
|
-
logger.log "* Listening on #{str}"
|
120
|
-
|
121
128
|
umask = nil
|
122
129
|
mode = nil
|
123
|
-
backlog =
|
130
|
+
backlog = 1024
|
124
131
|
|
125
132
|
if uri.query
|
126
133
|
params = Util.parse_query uri.query
|
@@ -139,6 +146,7 @@ module Puma
|
|
139
146
|
end
|
140
147
|
|
141
148
|
io = add_unix_listener path, umask, mode, backlog
|
149
|
+
logger.log "* Listening on #{str}"
|
142
150
|
end
|
143
151
|
|
144
152
|
@listeners << [str, io]
|
@@ -162,6 +170,7 @@ module Puma
|
|
162
170
|
end
|
163
171
|
|
164
172
|
ctx.keystore_pass = params['keystore-pass']
|
173
|
+
ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
|
165
174
|
else
|
166
175
|
unless params['key']
|
167
176
|
@events.error "Please specify the SSL key via 'key='"
|
@@ -182,8 +191,12 @@ module Puma
|
|
182
191
|
end
|
183
192
|
|
184
193
|
ctx.ca = params['ca'] if params['ca']
|
194
|
+
ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
|
185
195
|
end
|
186
196
|
|
197
|
+
ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
|
198
|
+
ctx.no_tlsv1_1 = true if params['no_tlsv1_1'] == 'true'
|
199
|
+
|
187
200
|
if params['verify_mode']
|
188
201
|
ctx.verify_mode = case params['verify_mode']
|
189
202
|
when "peer"
|
@@ -202,11 +215,11 @@ module Puma
|
|
202
215
|
logger.log "* Inherited #{str}"
|
203
216
|
io = inherit_ssl_listener fd, ctx
|
204
217
|
elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
|
205
|
-
logger.log "* Activated #{str}"
|
206
218
|
io = inherit_ssl_listener sock, ctx
|
219
|
+
logger.log "* Activated #{str}"
|
207
220
|
else
|
208
|
-
logger.log "* Listening on #{str}"
|
209
221
|
io = add_ssl_listener uri.host, uri.port, ctx
|
222
|
+
logger.log "* Listening on #{str}"
|
210
223
|
end
|
211
224
|
|
212
225
|
@listeners << [str, io] if io
|
@@ -245,9 +258,10 @@ module Puma
|
|
245
258
|
end
|
246
259
|
end
|
247
260
|
|
248
|
-
def
|
249
|
-
|
250
|
-
|
261
|
+
def loopback_addresses
|
262
|
+
Socket.ip_address_list.select do |addrinfo|
|
263
|
+
addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
|
264
|
+
end.map { |addrinfo| addrinfo.ip_address }.uniq
|
251
265
|
end
|
252
266
|
|
253
267
|
# Tell the server to listen on host +host+, port +port+.
|
@@ -259,7 +273,7 @@ module Puma
|
|
259
273
|
#
|
260
274
|
def add_tcp_listener(host, port, optimize_for_latency=true, backlog=1024)
|
261
275
|
if host == "localhost"
|
262
|
-
|
276
|
+
loopback_addresses.each do |addr|
|
263
277
|
add_tcp_listener addr, port, optimize_for_latency, backlog
|
264
278
|
end
|
265
279
|
return
|
@@ -298,7 +312,7 @@ module Puma
|
|
298
312
|
MiniSSL.check
|
299
313
|
|
300
314
|
if host == "localhost"
|
301
|
-
|
315
|
+
loopback_addresses.each do |addr|
|
302
316
|
add_ssl_listener addr, port, ctx, optimize_for_latency, backlog
|
303
317
|
end
|
304
318
|
return
|
@@ -312,6 +326,7 @@ module Puma
|
|
312
326
|
s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
|
313
327
|
s.listen backlog
|
314
328
|
|
329
|
+
|
315
330
|
ssl = MiniSSL::Server.new s, ctx
|
316
331
|
env = @proto_env.dup
|
317
332
|
env[HTTPS_KEY] = HTTPS
|
@@ -343,7 +358,7 @@ module Puma
|
|
343
358
|
|
344
359
|
# Tell the server to listen on +path+ as a UNIX domain socket.
|
345
360
|
#
|
346
|
-
def add_unix_listener(path, umask=nil, mode=nil, backlog=
|
361
|
+
def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
|
347
362
|
@unix_paths << path
|
348
363
|
|
349
364
|
# Let anyone connect by default
|
@@ -364,7 +379,7 @@ module Puma
|
|
364
379
|
end
|
365
380
|
|
366
381
|
s = UNIXServer.new(path)
|
367
|
-
s.listen backlog
|
382
|
+
s.listen backlog
|
368
383
|
@ios << s
|
369
384
|
ensure
|
370
385
|
File.umask old_mask
|
data/lib/puma/cli.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'optparse'
|
2
4
|
require 'uri'
|
3
5
|
|
6
|
+
require 'puma'
|
4
7
|
require 'puma/configuration'
|
5
8
|
require 'puma/launcher'
|
6
9
|
require 'puma/const'
|
@@ -47,21 +50,21 @@ module Puma
|
|
47
50
|
@parser.parse! @argv
|
48
51
|
|
49
52
|
if file = @argv.shift
|
50
|
-
@conf.configure do |
|
51
|
-
|
53
|
+
@conf.configure do |user_config, file_config|
|
54
|
+
file_config.rackup file
|
52
55
|
end
|
53
56
|
end
|
54
57
|
rescue UnsupportedOption
|
55
58
|
exit 1
|
56
59
|
end
|
57
60
|
|
58
|
-
@conf.configure do |
|
61
|
+
@conf.configure do |user_config, file_config|
|
59
62
|
if @stdout || @stderr
|
60
|
-
|
63
|
+
user_config.stdout_redirect @stdout, @stderr, @append
|
61
64
|
end
|
62
65
|
|
63
66
|
if @control_url
|
64
|
-
|
67
|
+
user_config.activate_control_app @control_url, @control_options
|
65
68
|
end
|
66
69
|
end
|
67
70
|
|
@@ -83,27 +86,35 @@ module Puma
|
|
83
86
|
raise UnsupportedOption
|
84
87
|
end
|
85
88
|
|
89
|
+
def configure_control_url(command_line_arg)
|
90
|
+
if command_line_arg
|
91
|
+
@control_url = command_line_arg
|
92
|
+
elsif Puma.jruby?
|
93
|
+
unsupported "No default url available on JRuby"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
86
97
|
# Build the OptionParser object to handle the available options.
|
87
98
|
#
|
88
99
|
|
89
100
|
def setup_options
|
90
|
-
@conf = Configuration.new do |
|
101
|
+
@conf = Configuration.new do |user_config, file_config|
|
91
102
|
@parser = OptionParser.new do |o|
|
92
103
|
o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
|
93
|
-
|
104
|
+
user_config.bind arg
|
94
105
|
end
|
95
106
|
|
96
107
|
o.on "-C", "--config PATH", "Load PATH as a config file" do |arg|
|
97
|
-
|
108
|
+
file_config.load arg
|
98
109
|
end
|
99
110
|
|
100
|
-
o.on "--control URL", "The bind url to use for the control server"
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
111
|
+
o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg|
|
112
|
+
configure_control_url(arg)
|
113
|
+
end
|
114
|
+
|
115
|
+
# alias --control-url for backwards-compatibility
|
116
|
+
o.on "--control URL", "DEPRECATED alias for --control-url" do |arg|
|
117
|
+
configure_control_url(arg)
|
107
118
|
end
|
108
119
|
|
109
120
|
o.on "--control-token TOKEN",
|
@@ -112,21 +123,21 @@ module Puma
|
|
112
123
|
end
|
113
124
|
|
114
125
|
o.on "-d", "--daemon", "Daemonize the server into the background" do
|
115
|
-
|
116
|
-
|
126
|
+
user_config.daemonize
|
127
|
+
user_config.quiet
|
117
128
|
end
|
118
129
|
|
119
130
|
o.on "--debug", "Log lowlevel debugging information" do
|
120
|
-
|
131
|
+
user_config.debug
|
121
132
|
end
|
122
133
|
|
123
134
|
o.on "--dir DIR", "Change to DIR before starting" do |d|
|
124
|
-
|
135
|
+
user_config.directory d
|
125
136
|
end
|
126
137
|
|
127
138
|
o.on "-e", "--environment ENVIRONMENT",
|
128
139
|
"The environment to run the Rack app on (default development)" do |arg|
|
129
|
-
|
140
|
+
user_config.environment arg
|
130
141
|
end
|
131
142
|
|
132
143
|
o.on "-I", "--include PATH", "Specify $LOAD_PATH directories" do |arg|
|
@@ -135,50 +146,54 @@ module Puma
|
|
135
146
|
|
136
147
|
o.on "-p", "--port PORT", "Define the TCP port to bind to",
|
137
148
|
"Use -b for more advanced options" do |arg|
|
138
|
-
|
149
|
+
user_config.bind "tcp://#{Configuration::DefaultTCPHost}:#{arg}"
|
139
150
|
end
|
140
151
|
|
141
152
|
o.on "--pidfile PATH", "Use PATH as a pidfile" do |arg|
|
142
|
-
|
153
|
+
user_config.pidfile arg
|
143
154
|
end
|
144
155
|
|
145
156
|
o.on "--preload", "Preload the app. Cluster mode only" do
|
146
|
-
|
157
|
+
user_config.preload_app!
|
147
158
|
end
|
148
159
|
|
149
160
|
o.on "--prune-bundler", "Prune out the bundler env if possible" do
|
150
|
-
|
161
|
+
user_config.prune_bundler
|
151
162
|
end
|
152
163
|
|
153
164
|
o.on "-q", "--quiet", "Do not log requests internally (default true)" do
|
154
|
-
|
165
|
+
user_config.quiet
|
155
166
|
end
|
156
167
|
|
157
168
|
o.on "-v", "--log-requests", "Log requests as they occur" do
|
158
|
-
|
169
|
+
user_config.log_requests
|
159
170
|
end
|
160
171
|
|
161
172
|
o.on "-R", "--restart-cmd CMD",
|
162
173
|
"The puma command to run during a hot restart",
|
163
174
|
"Default: inferred" do |cmd|
|
164
|
-
|
175
|
+
user_config.restart_command cmd
|
165
176
|
end
|
166
177
|
|
167
178
|
o.on "-S", "--state PATH", "Where to store the state details" do |arg|
|
168
|
-
|
179
|
+
user_config.state_path arg
|
169
180
|
end
|
170
181
|
|
171
182
|
o.on '-t', '--threads INT', "min:max threads to use (default 0:16)" do |arg|
|
172
183
|
min, max = arg.split(":")
|
173
184
|
if max
|
174
|
-
|
185
|
+
user_config.threads min, max
|
175
186
|
else
|
176
|
-
|
187
|
+
user_config.threads min, min
|
177
188
|
end
|
178
189
|
end
|
179
190
|
|
180
191
|
o.on "--tcp-mode", "Run the app in raw TCP mode instead of HTTP mode" do
|
181
|
-
|
192
|
+
user_config.tcp_mode!
|
193
|
+
end
|
194
|
+
|
195
|
+
o.on "--early-hints", "Enable early hints support" do
|
196
|
+
user_config.early_hints
|
182
197
|
end
|
183
198
|
|
184
199
|
o.on "-V", "--version", "Print the version information" do
|
@@ -188,11 +203,11 @@ module Puma
|
|
188
203
|
|
189
204
|
o.on "-w", "--workers COUNT",
|
190
205
|
"Activate cluster mode: How many worker processes to create" do |arg|
|
191
|
-
|
206
|
+
user_config.workers arg
|
192
207
|
end
|
193
208
|
|
194
209
|
o.on "--tag NAME", "Additional text to display in process listing" do |arg|
|
195
|
-
|
210
|
+
user_config.tag arg
|
196
211
|
end
|
197
212
|
|
198
213
|
o.on "--redirect-stdout FILE", "Redirect STDOUT to a specific file" do |arg|
|
data/lib/puma/client.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class IO
|
2
4
|
# We need to use this for a jruby work around on both 1.8 and 1.9.
|
3
5
|
# So this either creates the constant (on 1.8), or harmlessly
|
@@ -21,6 +23,18 @@ module Puma
|
|
21
23
|
|
22
24
|
class ConnectionError < RuntimeError; end
|
23
25
|
|
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. This
|
28
|
+
#
|
29
|
+
# An instance of `Puma::Client` can be used as if it were an IO object
|
30
|
+
# by the reactor, that's because the latter is expected to call `#to_io`
|
31
|
+
# on any non-IO objects it polls. For example nio4r internally calls
|
32
|
+
# `IO::try_convert` (which may call `#to_io`) when a new socket is
|
33
|
+
# registered.
|
34
|
+
#
|
35
|
+
# Instances of this class are responsible for knowing if
|
36
|
+
# the header and body are fully buffered via the `try_to_finish` method.
|
37
|
+
# They can be used to "time out" a response via the `timeout_at` reader.
|
24
38
|
class Client
|
25
39
|
include Puma::Const
|
26
40
|
extend Puma::Delegation
|
@@ -41,6 +55,7 @@ module Puma
|
|
41
55
|
@ready = false
|
42
56
|
|
43
57
|
@body = nil
|
58
|
+
@body_read_start = nil
|
44
59
|
@buffer = nil
|
45
60
|
@tempfile = nil
|
46
61
|
|
@@ -51,6 +66,10 @@ module Puma
|
|
51
66
|
|
52
67
|
@peerip = nil
|
53
68
|
@remote_addr_header = nil
|
69
|
+
|
70
|
+
@body_remain = 0
|
71
|
+
|
72
|
+
@in_last_chunk = false
|
54
73
|
end
|
55
74
|
|
56
75
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
@@ -89,6 +108,9 @@ module Puma
|
|
89
108
|
@tempfile = nil
|
90
109
|
@parsed_bytes = 0
|
91
110
|
@ready = false
|
111
|
+
@body_remain = 0
|
112
|
+
@peerip = nil
|
113
|
+
@in_last_chunk = false
|
92
114
|
|
93
115
|
if @buffer
|
94
116
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
@@ -101,9 +123,16 @@ module Puma
|
|
101
123
|
end
|
102
124
|
|
103
125
|
return false
|
104
|
-
|
105
|
-
|
106
|
-
|
126
|
+
else
|
127
|
+
begin
|
128
|
+
if fast_check &&
|
129
|
+
IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
|
130
|
+
return try_to_finish
|
131
|
+
end
|
132
|
+
rescue IOError
|
133
|
+
# swallow it
|
134
|
+
end
|
135
|
+
|
107
136
|
end
|
108
137
|
end
|
109
138
|
|
@@ -111,6 +140,7 @@ module Puma
|
|
111
140
|
begin
|
112
141
|
@io.close
|
113
142
|
rescue IOError
|
143
|
+
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
114
144
|
end
|
115
145
|
end
|
116
146
|
|
@@ -133,10 +163,13 @@ module Puma
|
|
133
163
|
def decode_chunk(chunk)
|
134
164
|
if @partial_part_left > 0
|
135
165
|
if @partial_part_left <= chunk.size
|
136
|
-
|
166
|
+
if @partial_part_left > 2
|
167
|
+
@body << chunk[0..(@partial_part_left-3)] # skip the \r\n
|
168
|
+
end
|
137
169
|
chunk = chunk[@partial_part_left..-1]
|
170
|
+
@partial_part_left = 0
|
138
171
|
else
|
139
|
-
@body << chunk
|
172
|
+
@body << chunk if @partial_part_left > 2 # don't include the last \r\n
|
140
173
|
@partial_part_left -= chunk.size
|
141
174
|
return false
|
142
175
|
end
|
@@ -154,12 +187,20 @@ module Puma
|
|
154
187
|
if line.end_with?("\r\n")
|
155
188
|
len = line.strip.to_i(16)
|
156
189
|
if len == 0
|
190
|
+
@in_last_chunk = true
|
157
191
|
@body.rewind
|
158
192
|
rest = io.read
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
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
|
163
204
|
end
|
164
205
|
|
165
206
|
len += 2
|
@@ -189,14 +230,19 @@ module Puma
|
|
189
230
|
end
|
190
231
|
end
|
191
232
|
|
192
|
-
|
233
|
+
if @in_last_chunk
|
234
|
+
set_ready
|
235
|
+
true
|
236
|
+
else
|
237
|
+
false
|
238
|
+
end
|
193
239
|
end
|
194
240
|
|
195
241
|
def read_chunked_body
|
196
242
|
while true
|
197
243
|
begin
|
198
244
|
chunk = @io.read_nonblock(4096)
|
199
|
-
rescue
|
245
|
+
rescue IO::WaitReadable
|
200
246
|
return false
|
201
247
|
rescue SystemCallError, IOError
|
202
248
|
raise ConnectionError, "Connection error detected during read"
|
@@ -206,8 +252,7 @@ module Puma
|
|
206
252
|
unless chunk
|
207
253
|
@body.close
|
208
254
|
@buffer = nil
|
209
|
-
|
210
|
-
@ready = true
|
255
|
+
set_ready
|
211
256
|
raise EOFError
|
212
257
|
end
|
213
258
|
|
@@ -216,6 +261,15 @@ module Puma
|
|
216
261
|
end
|
217
262
|
|
218
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
|
+
|
219
273
|
@read_header = false
|
220
274
|
|
221
275
|
body = @parser.body
|
@@ -233,8 +287,7 @@ module Puma
|
|
233
287
|
unless cl
|
234
288
|
@buffer = body.empty? ? nil : body
|
235
289
|
@body = EmptyBody
|
236
|
-
|
237
|
-
@ready = true
|
290
|
+
set_ready
|
238
291
|
return true
|
239
292
|
end
|
240
293
|
|
@@ -243,8 +296,7 @@ module Puma
|
|
243
296
|
if remain <= 0
|
244
297
|
@body = StringIO.new(body)
|
245
298
|
@buffer = nil
|
246
|
-
|
247
|
-
@ready = true
|
299
|
+
set_ready
|
248
300
|
return true
|
249
301
|
end
|
250
302
|
|
@@ -272,10 +324,17 @@ module Puma
|
|
272
324
|
data = @io.read_nonblock(CHUNK_SIZE)
|
273
325
|
rescue Errno::EAGAIN
|
274
326
|
return false
|
275
|
-
rescue SystemCallError, IOError
|
327
|
+
rescue SystemCallError, IOError, EOFError
|
276
328
|
raise ConnectionError, "Connection error detected during read"
|
277
329
|
end
|
278
330
|
|
331
|
+
# No data means a closed socket
|
332
|
+
unless data
|
333
|
+
@buffer = nil
|
334
|
+
set_ready
|
335
|
+
raise EOFError
|
336
|
+
end
|
337
|
+
|
279
338
|
if @buffer
|
280
339
|
@buffer << data
|
281
340
|
else
|
@@ -305,6 +364,13 @@ module Puma
|
|
305
364
|
raise e
|
306
365
|
end
|
307
366
|
|
367
|
+
# No data means a closed socket
|
368
|
+
unless data
|
369
|
+
@buffer = nil
|
370
|
+
set_ready
|
371
|
+
raise EOFError
|
372
|
+
end
|
373
|
+
|
308
374
|
if @buffer
|
309
375
|
@buffer << data
|
310
376
|
else
|
@@ -378,8 +444,7 @@ module Puma
|
|
378
444
|
unless chunk
|
379
445
|
@body.close
|
380
446
|
@buffer = nil
|
381
|
-
|
382
|
-
@ready = true
|
447
|
+
set_ready
|
383
448
|
raise EOFError
|
384
449
|
end
|
385
450
|
|
@@ -388,8 +453,7 @@ module Puma
|
|
388
453
|
if remain <= 0
|
389
454
|
@body.rewind
|
390
455
|
@buffer = nil
|
391
|
-
|
392
|
-
@ready = true
|
456
|
+
set_ready
|
393
457
|
return true
|
394
458
|
end
|
395
459
|
|
@@ -398,6 +462,14 @@ module Puma
|
|
398
462
|
false
|
399
463
|
end
|
400
464
|
|
465
|
+
def set_ready
|
466
|
+
if @body_read_start
|
467
|
+
@env['puma.request_body_wait'] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - @body_read_start
|
468
|
+
end
|
469
|
+
@requests_served += 1
|
470
|
+
@ready = true
|
471
|
+
end
|
472
|
+
|
401
473
|
def write_400
|
402
474
|
begin
|
403
475
|
@io << ERROR_400_RESPONSE
|