puma 2.7.0 → 3.1.1
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 -13
- data/DEPLOYMENT.md +91 -0
- data/Gemfile +3 -2
- data/History.txt +624 -1
- data/Manifest.txt +15 -3
- data/README.md +129 -14
- data/Rakefile +3 -3
- data/bin/puma-wild +31 -0
- data/bin/pumactl +1 -1
- data/docs/nginx.md +1 -1
- data/docs/signals.md +43 -0
- data/ext/puma_http11/extconf.rb +7 -2
- data/ext/puma_http11/http11_parser.java.rl +5 -5
- data/ext/puma_http11/io_buffer.c +1 -1
- data/ext/puma_http11/mini_ssl.c +233 -18
- data/ext/puma_http11/org/jruby/puma/Http11.java +12 -3
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +39 -39
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +245 -195
- data/ext/puma_http11/puma_http11.c +12 -4
- data/lib/puma.rb +1 -0
- data/lib/puma/app/status.rb +7 -0
- data/lib/puma/binder.rb +108 -39
- data/lib/puma/capistrano.rb +23 -6
- data/lib/puma/cli.rb +141 -446
- data/lib/puma/client.rb +48 -1
- data/lib/puma/cluster.rb +207 -58
- data/lib/puma/commonlogger.rb +107 -0
- data/lib/puma/configuration.rb +262 -235
- data/lib/puma/const.rb +97 -14
- data/lib/puma/control_cli.rb +85 -77
- data/lib/puma/convenient.rb +23 -0
- data/lib/puma/daemon_ext.rb +11 -4
- data/lib/puma/detect.rb +8 -1
- data/lib/puma/dsl.rb +456 -0
- data/lib/puma/events.rb +35 -18
- data/lib/puma/jruby_restart.rb +1 -1
- data/lib/puma/launcher.rb +399 -0
- data/lib/puma/minissl.rb +49 -20
- data/lib/puma/null_io.rb +15 -0
- data/lib/puma/plugin.rb +104 -0
- data/lib/puma/plugin/tmp_restart.rb +35 -0
- data/lib/puma/rack/backports/uri/common_18.rb +56 -0
- data/lib/puma/rack/backports/uri/common_192.rb +52 -0
- data/lib/puma/rack/backports/uri/common_193.rb +29 -0
- data/lib/puma/rack/builder.rb +295 -0
- data/lib/puma/rack/urlmap.rb +90 -0
- data/lib/puma/reactor.rb +14 -1
- data/lib/puma/runner.rb +35 -17
- data/lib/puma/server.rb +161 -58
- data/lib/puma/single.rb +15 -10
- data/lib/puma/state_file.rb +29 -0
- data/lib/puma/thread_pool.rb +88 -13
- data/lib/puma/util.rb +123 -0
- data/lib/rack/handler/puma.rb +35 -29
- data/puma.gemspec +2 -4
- data/tools/jungle/init.d/README.md +2 -2
- data/tools/jungle/init.d/puma +69 -7
- data/tools/jungle/upstart/puma.conf +8 -2
- metadata +51 -71
- data/COPYING +0 -55
- data/TODO +0 -5
- data/lib/puma/rack_patch.rb +0 -45
- data/test/test_app_status.rb +0 -92
- data/test/test_cli.rb +0 -173
- data/test/test_config.rb +0 -16
- data/test/test_http10.rb +0 -27
- data/test/test_http11.rb +0 -145
- data/test/test_integration.rb +0 -165
- data/test/test_iobuffer.rb +0 -38
- data/test/test_minissl.rb +0 -25
- data/test/test_null_io.rb +0 -31
- data/test/test_persistent.rb +0 -238
- data/test/test_puma_server.rb +0 -292
- data/test/test_rack_handler.rb +0 -10
- data/test/test_rack_server.rb +0 -141
- data/test/test_tcp_rack.rb +0 -42
- data/test/test_thread_pool.rb +0 -156
- data/test/test_unix_socket.rb +0 -39
- data/test/test_ws.rb +0 -89
@@ -0,0 +1,90 @@
|
|
1
|
+
module Puma::Rack
|
2
|
+
# Rack::URLMap takes a hash mapping urls or paths to apps, and
|
3
|
+
# dispatches accordingly. Support for HTTP/1.1 host names exists if
|
4
|
+
# the URLs start with <tt>http://</tt> or <tt>https://</tt>.
|
5
|
+
#
|
6
|
+
# URLMap modifies the SCRIPT_NAME and PATH_INFO such that the part
|
7
|
+
# relevant for dispatch is in the SCRIPT_NAME, and the rest in the
|
8
|
+
# PATH_INFO. This should be taken care of when you need to
|
9
|
+
# reconstruct the URL in order to create links.
|
10
|
+
#
|
11
|
+
# URLMap dispatches in such a way that the longest paths are tried
|
12
|
+
# first, since they are most specific.
|
13
|
+
|
14
|
+
class URLMap
|
15
|
+
NEGATIVE_INFINITY = -1.0 / 0.0
|
16
|
+
INFINITY = 1.0 / 0.0
|
17
|
+
|
18
|
+
def initialize(map = {})
|
19
|
+
remap(map)
|
20
|
+
end
|
21
|
+
|
22
|
+
def remap(map)
|
23
|
+
@mapping = map.map { |location, app|
|
24
|
+
if location =~ %r{\Ahttps?://(.*?)(/.*)}
|
25
|
+
host, location = $1, $2
|
26
|
+
else
|
27
|
+
host = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
unless location[0] == ?/
|
31
|
+
raise ArgumentError, "paths need to start with /"
|
32
|
+
end
|
33
|
+
|
34
|
+
location = location.chomp('/')
|
35
|
+
match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
|
36
|
+
|
37
|
+
[host, location, match, app]
|
38
|
+
}.sort_by do |(host, location, _, _)|
|
39
|
+
[host ? -host.size : INFINITY, -location.size]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def call(env)
|
44
|
+
path = env['PATH_INFO']
|
45
|
+
script_name = env['SCRIPT_NAME']
|
46
|
+
hHost = env['HTTP_HOST']
|
47
|
+
sName = env['SERVER_NAME']
|
48
|
+
sPort = env['SERVER_PORT']
|
49
|
+
|
50
|
+
@mapping.each do |host, location, match, app|
|
51
|
+
unless casecmp?(hHost, host) \
|
52
|
+
|| casecmp?(sName, host) \
|
53
|
+
|| (!host && (casecmp?(hHost, sName) ||
|
54
|
+
casecmp?(hHost, sName+':'+sPort)))
|
55
|
+
next
|
56
|
+
end
|
57
|
+
|
58
|
+
next unless m = match.match(path.to_s)
|
59
|
+
|
60
|
+
rest = m[1]
|
61
|
+
next unless !rest || rest.empty? || rest[0] == ?/
|
62
|
+
|
63
|
+
env['SCRIPT_NAME'] = (script_name + location)
|
64
|
+
env['PATH_INFO'] = rest
|
65
|
+
|
66
|
+
return app.call(env)
|
67
|
+
end
|
68
|
+
|
69
|
+
[404, {'Content-Type' => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
|
70
|
+
|
71
|
+
ensure
|
72
|
+
env['PATH_INFO'] = path
|
73
|
+
env['SCRIPT_NAME'] = script_name
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
def casecmp?(v1, v2)
|
78
|
+
# if both nil, or they're the same string
|
79
|
+
return true if v1 == v2
|
80
|
+
|
81
|
+
# if either are nil... (but they're not the same)
|
82
|
+
return false if v1.nil?
|
83
|
+
return false if v2.nil?
|
84
|
+
|
85
|
+
# otherwise check they're not case-insensitive the same
|
86
|
+
v1.casecmp(v2).zero?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
data/lib/puma/reactor.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'puma/util'
|
2
|
+
require 'puma/minissl'
|
2
3
|
|
3
4
|
module Puma
|
4
5
|
class Reactor
|
@@ -74,6 +75,17 @@ module Puma
|
|
74
75
|
sockets.delete c
|
75
76
|
end
|
76
77
|
|
78
|
+
# SSL handshake failure
|
79
|
+
rescue MiniSSL::SSLError => e
|
80
|
+
ssl_socket = c.io
|
81
|
+
addr = ssl_socket.peeraddr.last
|
82
|
+
cert = ssl_socket.peercert
|
83
|
+
|
84
|
+
c.close
|
85
|
+
sockets.delete c
|
86
|
+
|
87
|
+
@events.ssl_error @server, addr, cert, e
|
88
|
+
|
77
89
|
# The client doesn't know HTTP well
|
78
90
|
rescue HttpParserError => e
|
79
91
|
c.write_400
|
@@ -98,8 +110,9 @@ module Puma
|
|
98
110
|
|
99
111
|
while @timeouts.first.timeout_at < now
|
100
112
|
c = @timeouts.shift
|
101
|
-
|
113
|
+
c.write_408 if c.in_data_phase
|
102
114
|
c.close
|
115
|
+
sockets.delete c
|
103
116
|
|
104
117
|
break if @timeouts.empty?
|
105
118
|
end
|
data/lib/puma/runner.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
module Puma
|
2
2
|
class Runner
|
3
|
-
def initialize(cli)
|
4
|
-
@
|
3
|
+
def initialize(cli, events)
|
4
|
+
@launcher = cli
|
5
|
+
@events = events
|
5
6
|
@options = cli.options
|
6
7
|
@app = nil
|
7
8
|
@control = nil
|
@@ -16,15 +17,19 @@ module Puma
|
|
16
17
|
end
|
17
18
|
|
18
19
|
def log(str)
|
19
|
-
@
|
20
|
+
@events.log str
|
21
|
+
end
|
22
|
+
|
23
|
+
def before_restart
|
24
|
+
@control.stop(true) if @control
|
20
25
|
end
|
21
26
|
|
22
27
|
def error(str)
|
23
|
-
@
|
28
|
+
@events.error str
|
24
29
|
end
|
25
30
|
|
26
|
-
def
|
27
|
-
@
|
31
|
+
def debug(str)
|
32
|
+
@events.log "- #{str}" if @options[:debug]
|
28
33
|
end
|
29
34
|
|
30
35
|
def start_control
|
@@ -35,13 +40,13 @@ module Puma
|
|
35
40
|
|
36
41
|
uri = URI.parse str
|
37
42
|
|
38
|
-
app = Puma::App::Status.new @
|
43
|
+
app = Puma::App::Status.new @launcher
|
39
44
|
|
40
45
|
if token = @options[:control_auth_token]
|
41
46
|
app.auth_token = token unless token.empty? or token == :none
|
42
47
|
end
|
43
48
|
|
44
|
-
control = Puma::Server.new app, @
|
49
|
+
control = Puma::Server.new app, @launcher.events
|
45
50
|
control.min_threads = 0
|
46
51
|
control.max_threads = 1
|
47
52
|
|
@@ -52,8 +57,9 @@ module Puma
|
|
52
57
|
when "unix"
|
53
58
|
log "* Starting control server on #{str}"
|
54
59
|
path = "#{uri.host}#{uri.path}"
|
60
|
+
mask = @options[:control_url_umask]
|
55
61
|
|
56
|
-
control.add_unix_listener path
|
62
|
+
control.add_unix_listener path, mask
|
57
63
|
else
|
58
64
|
error "Invalid control URI: #{str}"
|
59
65
|
end
|
@@ -62,12 +68,24 @@ module Puma
|
|
62
68
|
@control = control
|
63
69
|
end
|
64
70
|
|
71
|
+
def ruby_engine
|
72
|
+
if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
|
73
|
+
"ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
|
74
|
+
else
|
75
|
+
if defined?(RUBY_ENGINE_VERSION)
|
76
|
+
"#{RUBY_ENGINE} #{RUBY_ENGINE_VERSION} - ruby #{RUBY_VERSION}"
|
77
|
+
else
|
78
|
+
"#{RUBY_ENGINE} #{RUBY_VERSION}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
65
83
|
def output_header(mode)
|
66
84
|
min_t = @options[:min_threads]
|
67
85
|
max_t = @options[:max_threads]
|
68
86
|
|
69
87
|
log "Puma starting in #{mode} mode..."
|
70
|
-
log "* Version #{Puma::Const::PUMA_VERSION}, codename: #{Puma::Const::CODE_NAME}"
|
88
|
+
log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
|
71
89
|
log "* Min threads: #{min_t}, max threads: #{max_t}"
|
72
90
|
log "* Environment: #{ENV['RACK_ENV']}"
|
73
91
|
|
@@ -95,34 +113,34 @@ module Puma
|
|
95
113
|
end
|
96
114
|
|
97
115
|
def load_and_bind
|
98
|
-
unless @
|
116
|
+
unless @launcher.config.app_configured?
|
99
117
|
error "No application configured, nothing to run"
|
100
118
|
exit 1
|
101
119
|
end
|
102
120
|
|
103
121
|
# Load the app before we daemonize.
|
104
122
|
begin
|
105
|
-
@app = @
|
123
|
+
@app = @launcher.config.app
|
106
124
|
rescue Exception => e
|
107
|
-
log "! Unable to load application"
|
125
|
+
log "! Unable to load application: #{e.class}: #{e.message}"
|
108
126
|
raise e
|
109
127
|
end
|
110
128
|
|
111
|
-
@
|
129
|
+
@launcher.binder.parse @options[:binds], self
|
112
130
|
end
|
113
131
|
|
114
132
|
def app
|
115
|
-
@app ||= @
|
133
|
+
@app ||= @launcher.config.app
|
116
134
|
end
|
117
135
|
|
118
136
|
def start_server
|
119
137
|
min_t = @options[:min_threads]
|
120
138
|
max_t = @options[:max_threads]
|
121
139
|
|
122
|
-
server = Puma::Server.new app, @
|
140
|
+
server = Puma::Server.new app, @launcher.events, @options
|
123
141
|
server.min_threads = min_t
|
124
142
|
server.max_threads = max_t
|
125
|
-
server.inherit_binder @
|
143
|
+
server.inherit_binder @launcher.binder
|
126
144
|
|
127
145
|
if @options[:mode] == :tcp
|
128
146
|
server.tcp_mode!
|
data/lib/puma/server.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'rack'
|
2
1
|
require 'stringio'
|
3
2
|
|
4
3
|
require 'puma/thread_pool'
|
@@ -13,11 +12,9 @@ require 'puma/delegation'
|
|
13
12
|
require 'puma/accept_nonblock'
|
14
13
|
require 'puma/util'
|
15
14
|
|
16
|
-
require 'puma/rack_patch'
|
17
|
-
|
18
15
|
require 'puma/puma_http11'
|
19
16
|
|
20
|
-
unless Puma.const_defined?
|
17
|
+
unless Puma.const_defined? "IOBuffer"
|
21
18
|
require 'puma/io_buffer'
|
22
19
|
end
|
23
20
|
|
@@ -39,6 +36,8 @@ module Puma
|
|
39
36
|
attr_accessor :max_threads
|
40
37
|
attr_accessor :persistent_timeout
|
41
38
|
attr_accessor :auto_trim_time
|
39
|
+
attr_accessor :reaping_time
|
40
|
+
attr_accessor :first_data_timeout
|
42
41
|
|
43
42
|
# Create a server for the rack app +app+.
|
44
43
|
#
|
@@ -46,7 +45,7 @@ module Puma
|
|
46
45
|
# to be handled. See Puma::Events for the list of current methods to implement.
|
47
46
|
#
|
48
47
|
# Server#run returns a thread that you can join on to wait for the server
|
49
|
-
# to do
|
48
|
+
# to do its work.
|
50
49
|
#
|
51
50
|
def initialize(app, events=Events.stdio, options={})
|
52
51
|
@app = app
|
@@ -59,6 +58,7 @@ module Puma
|
|
59
58
|
@min_threads = 0
|
60
59
|
@max_threads = 16
|
61
60
|
@auto_trim_time = 1
|
61
|
+
@reaping_time = 1
|
62
62
|
|
63
63
|
@thread = nil
|
64
64
|
@thread_pool = nil
|
@@ -73,6 +73,7 @@ module Puma
|
|
73
73
|
@leak_stack_on_error = true
|
74
74
|
|
75
75
|
@options = options
|
76
|
+
@queue_requests = options[:queue_requests].nil? ? true : options[:queue_requests]
|
76
77
|
|
77
78
|
ENV['RACK_ENV'] ||= "development"
|
78
79
|
|
@@ -84,6 +85,7 @@ module Puma
|
|
84
85
|
forward :add_tcp_listener, :@binder
|
85
86
|
forward :add_ssl_listener, :@binder
|
86
87
|
forward :add_unix_listener, :@binder
|
88
|
+
forward :connected_port, :@binder
|
87
89
|
|
88
90
|
def inherit_binder(bind)
|
89
91
|
@binder = bind
|
@@ -102,13 +104,16 @@ module Puma
|
|
102
104
|
# 3 == TCP_CORK
|
103
105
|
# 1/0 == turn on/off
|
104
106
|
def cork_socket(socket)
|
105
|
-
|
107
|
+
begin
|
108
|
+
socket.setsockopt(6, 3, 1) if socket.kind_of? TCPSocket
|
109
|
+
rescue IOError, SystemCallError
|
110
|
+
end
|
106
111
|
end
|
107
112
|
|
108
113
|
def uncork_socket(socket)
|
109
114
|
begin
|
110
115
|
socket.setsockopt(6, 3, 0) if socket.kind_of? TCPSocket
|
111
|
-
rescue IOError
|
116
|
+
rescue IOError, SystemCallError
|
112
117
|
end
|
113
118
|
end
|
114
119
|
else
|
@@ -181,16 +186,17 @@ module Puma
|
|
181
186
|
else
|
182
187
|
begin
|
183
188
|
if io = sock.accept_nonblock
|
184
|
-
|
185
|
-
pool <<
|
189
|
+
client = Client.new io, nil
|
190
|
+
pool << client
|
186
191
|
end
|
187
192
|
rescue SystemCallError
|
193
|
+
# nothing
|
194
|
+
rescue Errno::ECONNABORTED
|
195
|
+
# client closed the socket even before accept
|
196
|
+
io.close rescue nil
|
188
197
|
end
|
189
198
|
end
|
190
199
|
end
|
191
|
-
rescue Errno::ECONNABORTED
|
192
|
-
# client closed the socket even before accept
|
193
|
-
client.close rescue nil
|
194
200
|
rescue Object => e
|
195
201
|
@events.unknown_error self, e, "Listen loop"
|
196
202
|
end
|
@@ -231,13 +237,28 @@ module Puma
|
|
231
237
|
return run_lopez_mode(background)
|
232
238
|
end
|
233
239
|
|
240
|
+
queue_requests = @queue_requests
|
241
|
+
|
234
242
|
@thread_pool = ThreadPool.new(@min_threads,
|
235
243
|
@max_threads,
|
236
244
|
IOBuffer) do |client, buffer|
|
237
245
|
process_now = false
|
238
246
|
|
239
247
|
begin
|
240
|
-
|
248
|
+
if queue_requests
|
249
|
+
process_now = client.eagerly_finish
|
250
|
+
else
|
251
|
+
client.finish
|
252
|
+
process_now = true
|
253
|
+
end
|
254
|
+
rescue MiniSSL::SSLError => e
|
255
|
+
ssl_socket = client.io
|
256
|
+
addr = ssl_socket.peeraddr.last
|
257
|
+
cert = ssl_socket.peercert
|
258
|
+
|
259
|
+
client.close
|
260
|
+
|
261
|
+
@events.ssl_error self, addr, cert, e
|
241
262
|
rescue HttpParserError => e
|
242
263
|
client.write_400
|
243
264
|
client.close
|
@@ -255,9 +276,16 @@ module Puma
|
|
255
276
|
end
|
256
277
|
end
|
257
278
|
|
258
|
-
@
|
279
|
+
@thread_pool.clean_thread_locals = @options[:clean_thread_locals]
|
259
280
|
|
260
|
-
|
281
|
+
if queue_requests
|
282
|
+
@reactor = Reactor.new self, @thread_pool
|
283
|
+
@reactor.run_in_thread
|
284
|
+
end
|
285
|
+
|
286
|
+
if @reaping_time
|
287
|
+
@thread_pool.auto_reap!(@reaping_time)
|
288
|
+
end
|
261
289
|
|
262
290
|
if @auto_trim_time
|
263
291
|
@thread_pool.auto_trim!(@auto_trim_time)
|
@@ -278,6 +306,17 @@ module Puma
|
|
278
306
|
check = @check
|
279
307
|
sockets = [check] + @binder.ios
|
280
308
|
pool = @thread_pool
|
309
|
+
queue_requests = @queue_requests
|
310
|
+
|
311
|
+
remote_addr_value = nil
|
312
|
+
remote_addr_header = nil
|
313
|
+
|
314
|
+
case @options[:remote_address]
|
315
|
+
when :value
|
316
|
+
remote_addr_value = @options[:remote_address_value]
|
317
|
+
when :header
|
318
|
+
remote_addr_header = @options[:remote_address_header]
|
319
|
+
end
|
281
320
|
|
282
321
|
while @status == :run
|
283
322
|
begin
|
@@ -288,16 +327,24 @@ module Puma
|
|
288
327
|
else
|
289
328
|
begin
|
290
329
|
if io = sock.accept_nonblock
|
291
|
-
|
292
|
-
|
330
|
+
client = Client.new io, @binder.env(sock)
|
331
|
+
if remote_addr_value
|
332
|
+
client.peerip = remote_addr_value
|
333
|
+
elsif remote_addr_header
|
334
|
+
client.remote_addr_header = remote_addr_header
|
335
|
+
end
|
336
|
+
|
337
|
+
pool << client
|
338
|
+
pool.wait_until_not_full unless queue_requests
|
293
339
|
end
|
294
340
|
rescue SystemCallError
|
341
|
+
# nothing
|
342
|
+
rescue Errno::ECONNABORTED
|
343
|
+
# client closed the socket even before accept
|
344
|
+
io.close rescue nil
|
295
345
|
end
|
296
346
|
end
|
297
347
|
end
|
298
|
-
rescue Errno::ECONNABORTED
|
299
|
-
# client closed the socket even before accept
|
300
|
-
client.close rescue nil
|
301
348
|
rescue Object => e
|
302
349
|
@events.unknown_error self, e, "Listen loop"
|
303
350
|
end
|
@@ -306,9 +353,10 @@ module Puma
|
|
306
353
|
@events.fire :state, @status
|
307
354
|
|
308
355
|
graceful_shutdown if @status == :stop || @status == :restart
|
309
|
-
|
310
|
-
|
311
|
-
|
356
|
+
if queue_requests
|
357
|
+
@reactor.clear! if @status == :restart
|
358
|
+
@reactor.shutdown
|
359
|
+
end
|
312
360
|
rescue Exception => e
|
313
361
|
STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
|
314
362
|
STDERR.puts e.backtrace
|
@@ -326,7 +374,7 @@ module Puma
|
|
326
374
|
|
327
375
|
# :nodoc:
|
328
376
|
def handle_check
|
329
|
-
cmd = @check.read(1)
|
377
|
+
cmd = @check.read(1)
|
330
378
|
|
331
379
|
case cmd
|
332
380
|
when STOP_COMMAND
|
@@ -351,6 +399,7 @@ module Puma
|
|
351
399
|
#
|
352
400
|
def process_client(client, buffer)
|
353
401
|
begin
|
402
|
+
clean_thread_locals = @options[:clean_thread_locals]
|
354
403
|
close_socket = true
|
355
404
|
|
356
405
|
while true
|
@@ -361,8 +410,11 @@ module Puma
|
|
361
410
|
close_socket = false
|
362
411
|
return
|
363
412
|
when true
|
413
|
+
return unless @queue_requests
|
364
414
|
buffer.reset
|
365
415
|
|
416
|
+
ThreadPool.clean_thread_locals if clean_thread_locals
|
417
|
+
|
366
418
|
unless client.reset(@status == :run)
|
367
419
|
close_socket = false
|
368
420
|
client.set_timeout @persistent_timeout
|
@@ -376,6 +428,16 @@ module Puma
|
|
376
428
|
rescue ConnectionError
|
377
429
|
# Swallow them. The ensure tries to close +client+ down
|
378
430
|
|
431
|
+
# SSL handshake error
|
432
|
+
rescue MiniSSL::SSLError => e
|
433
|
+
ssl_socket = client.io
|
434
|
+
addr = ssl_socket.peeraddr.last
|
435
|
+
cert = ssl_socket.peercert
|
436
|
+
|
437
|
+
close_socket = true
|
438
|
+
|
439
|
+
@events.ssl_error self, addr, cert, e
|
440
|
+
|
379
441
|
# The client doesn't know HTTP well
|
380
442
|
rescue HttpParserError => e
|
381
443
|
client.write_400
|
@@ -437,15 +499,24 @@ module Puma
|
|
437
499
|
# intermediary acting on behalf of the actual source client."
|
438
500
|
#
|
439
501
|
|
440
|
-
|
502
|
+
unless env.key?(REMOTE_ADDR)
|
503
|
+
begin
|
504
|
+
addr = client.peerip
|
505
|
+
rescue Errno::ENOTCONN
|
506
|
+
# Client disconnects can result in an inability to get the
|
507
|
+
# peeraddr from the socket; default to localhost.
|
508
|
+
addr = LOCALHOST_IP
|
509
|
+
end
|
441
510
|
|
442
|
-
|
443
|
-
|
511
|
+
# Set unix socket addrs to localhost
|
512
|
+
addr = LOCALHOST_IP if addr.empty?
|
444
513
|
|
445
|
-
|
514
|
+
env[REMOTE_ADDR] = addr
|
515
|
+
end
|
446
516
|
end
|
447
517
|
|
448
518
|
def default_server_port(env)
|
519
|
+
return PORT_443 if env[HTTPS_KEY] == 'on' || env[HTTPS_KEY] == 'https'
|
449
520
|
env['HTTP_X_FORWARDED_PROTO'] == 'https' ? PORT_443 : PORT_80
|
450
521
|
end
|
451
522
|
|
@@ -462,10 +533,14 @@ module Puma
|
|
462
533
|
env = req.env
|
463
534
|
client = req.io
|
464
535
|
|
465
|
-
normalize_env env,
|
536
|
+
normalize_env env, req
|
466
537
|
|
467
538
|
env[PUMA_SOCKET] = client
|
468
539
|
|
540
|
+
if env[HTTPS_KEY] && client.peercert
|
541
|
+
env[PUMA_PEERCERT] = client.peercert
|
542
|
+
end
|
543
|
+
|
469
544
|
env[HIJACK_P] = true
|
470
545
|
env[HIJACK] = req
|
471
546
|
|
@@ -499,7 +574,7 @@ module Puma
|
|
499
574
|
rescue StandardError => e
|
500
575
|
@events.unknown_error self, e, "Rack app"
|
501
576
|
|
502
|
-
status, headers, res_body = lowlevel_error(e)
|
577
|
+
status, headers, res_body = lowlevel_error(e, env)
|
503
578
|
end
|
504
579
|
|
505
580
|
content_length = nil
|
@@ -514,9 +589,9 @@ module Puma
|
|
514
589
|
line_ending = LINE_END
|
515
590
|
colon = COLON
|
516
591
|
|
517
|
-
if env[HTTP_VERSION] == HTTP_11
|
592
|
+
http_11 = if env[HTTP_VERSION] == HTTP_11
|
518
593
|
allow_chunked = true
|
519
|
-
keep_alive = env
|
594
|
+
keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
|
520
595
|
include_keepalive_header = false
|
521
596
|
|
522
597
|
# An optimization. The most common response is 200, so we can
|
@@ -531,9 +606,10 @@ module Puma
|
|
531
606
|
|
532
607
|
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
533
608
|
end
|
609
|
+
true
|
534
610
|
else
|
535
611
|
allow_chunked = false
|
536
|
-
keep_alive = env
|
612
|
+
keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
|
537
613
|
include_keepalive_header = keep_alive
|
538
614
|
|
539
615
|
# Same optimization as above for HTTP/1.1
|
@@ -546,12 +622,13 @@ module Puma
|
|
546
622
|
|
547
623
|
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
548
624
|
end
|
625
|
+
false
|
549
626
|
end
|
550
627
|
|
551
628
|
response_hijack = nil
|
552
629
|
|
553
630
|
headers.each do |k, vs|
|
554
|
-
case k
|
631
|
+
case k.downcase
|
555
632
|
when CONTENT_LENGTH2
|
556
633
|
content_length = vs
|
557
634
|
next
|
@@ -572,6 +649,12 @@ module Puma
|
|
572
649
|
end
|
573
650
|
end
|
574
651
|
|
652
|
+
if include_keepalive_header
|
653
|
+
lines << CONNECTION_KEEP_ALIVE
|
654
|
+
elsif http_11 && !keep_alive
|
655
|
+
lines << CONNECTION_CLOSE
|
656
|
+
end
|
657
|
+
|
575
658
|
if no_body
|
576
659
|
if content_length and status != 204
|
577
660
|
lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
@@ -582,20 +665,12 @@ module Puma
|
|
582
665
|
return keep_alive
|
583
666
|
end
|
584
667
|
|
585
|
-
if
|
586
|
-
lines
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
unless response_hijack
|
592
|
-
if content_length
|
593
|
-
lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
594
|
-
chunked = false
|
595
|
-
elsif allow_chunked
|
596
|
-
lines << TRANSFER_ENCODING_CHUNKED
|
597
|
-
chunked = true
|
598
|
-
end
|
668
|
+
if content_length
|
669
|
+
lines.append CONTENT_LENGTH_S, content_length.to_s, line_ending
|
670
|
+
chunked = false
|
671
|
+
elsif !response_hijack and allow_chunked
|
672
|
+
lines << TRANSFER_ENCODING_CHUNKED
|
673
|
+
chunked = true
|
599
674
|
end
|
600
675
|
|
601
676
|
lines << line_ending
|
@@ -610,10 +685,11 @@ module Puma
|
|
610
685
|
begin
|
611
686
|
res_body.each do |part|
|
612
687
|
if chunked
|
613
|
-
|
614
|
-
client.
|
688
|
+
next if part.bytesize.zero?
|
689
|
+
fast_write client, part.bytesize.to_s(16)
|
690
|
+
fast_write client, line_ending
|
615
691
|
fast_write client, part
|
616
|
-
client
|
692
|
+
fast_write client, line_ending
|
617
693
|
else
|
618
694
|
fast_write client, part
|
619
695
|
end
|
@@ -622,7 +698,7 @@ module Puma
|
|
622
698
|
end
|
623
699
|
|
624
700
|
if chunked
|
625
|
-
client
|
701
|
+
fast_write client, CLOSE_CHUNKED
|
626
702
|
client.flush
|
627
703
|
end
|
628
704
|
rescue SystemCallError, IOError
|
@@ -633,6 +709,7 @@ module Puma
|
|
633
709
|
uncork_socket client
|
634
710
|
|
635
711
|
body.close
|
712
|
+
req.tempfile.unlink if req.tempfile
|
636
713
|
res_body.close if res_body.respond_to? :close
|
637
714
|
|
638
715
|
after_reply.each { |o| o.call }
|
@@ -702,17 +779,40 @@ module Puma
|
|
702
779
|
|
703
780
|
# A fallback rack response if +@app+ raises as exception.
|
704
781
|
#
|
705
|
-
def lowlevel_error(e)
|
782
|
+
def lowlevel_error(e, env)
|
783
|
+
if handler = @options[:lowlevel_error_handler]
|
784
|
+
if handler.arity == 1
|
785
|
+
return handler.call(e)
|
786
|
+
else
|
787
|
+
return handler.call(e, env)
|
788
|
+
end
|
789
|
+
end
|
790
|
+
|
706
791
|
if @leak_stack_on_error
|
707
792
|
[500, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
|
708
793
|
else
|
709
|
-
[500, {}, ["
|
794
|
+
[500, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
|
710
795
|
end
|
711
796
|
end
|
712
797
|
|
713
798
|
# Wait for all outstanding requests to finish.
|
714
799
|
#
|
715
800
|
def graceful_shutdown
|
801
|
+
if @options[:shutdown_debug]
|
802
|
+
threads = Thread.list
|
803
|
+
total = threads.size
|
804
|
+
|
805
|
+
pid = Process.pid
|
806
|
+
|
807
|
+
$stdout.syswrite "#{pid}: === Begin thread backtrace dump ===\n"
|
808
|
+
|
809
|
+
threads.each_with_index do |t,i|
|
810
|
+
$stdout.syswrite "#{pid}: Thread #{i+1}/#{total}: #{t.inspect}\n"
|
811
|
+
$stdout.syswrite "#{pid}: #{t.backtrace.join("\n#{pid}: ")}\n\n"
|
812
|
+
end
|
813
|
+
$stdout.syswrite "#{pid}: === End thread backtrace dump ===\n"
|
814
|
+
end
|
815
|
+
|
716
816
|
if @options[:drain_on_shutdown]
|
717
817
|
count = 0
|
718
818
|
|
@@ -724,8 +824,8 @@ module Puma
|
|
724
824
|
begin
|
725
825
|
if io = sock.accept_nonblock
|
726
826
|
count += 1
|
727
|
-
|
728
|
-
@thread_pool <<
|
827
|
+
client = Client.new io, @binder.env(sock)
|
828
|
+
@thread_pool << client
|
729
829
|
end
|
730
830
|
rescue SystemCallError
|
731
831
|
end
|
@@ -775,10 +875,13 @@ module Puma
|
|
775
875
|
begin
|
776
876
|
n = io.syswrite str
|
777
877
|
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
778
|
-
IO.select(nil, [io], nil,
|
878
|
+
if !IO.select(nil, [io], nil, WRITE_TIMEOUT)
|
879
|
+
raise ConnectionError, "Socket timeout writing data"
|
880
|
+
end
|
881
|
+
|
779
882
|
retry
|
780
883
|
rescue Errno::EPIPE, SystemCallError, IOError
|
781
|
-
|
884
|
+
raise ConnectionError, "Socket timeout writing data"
|
782
885
|
end
|
783
886
|
|
784
887
|
return if n == str.bytesize
|