puma 4.3.1 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +94 -3
- data/LICENSE +23 -20
- data/README.md +26 -13
- data/docs/architecture.md +3 -3
- data/docs/deployment.md +9 -3
- data/docs/fork_worker.md +31 -0
- data/docs/jungle/README.md +13 -0
- data/{tools → docs}/jungle/rc.d/README.md +0 -0
- data/{tools → docs}/jungle/rc.d/puma +0 -0
- data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
- data/{tools → docs}/jungle/upstart/README.md +0 -0
- data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
- data/{tools → docs}/jungle/upstart/puma.conf +0 -0
- data/docs/signals.md +7 -6
- data/docs/systemd.md +1 -63
- data/ext/puma_http11/PumaHttp11Service.java +2 -4
- data/ext/puma_http11/extconf.rb +4 -3
- data/ext/puma_http11/http11_parser.c +3 -1
- data/ext/puma_http11/http11_parser.rl +3 -1
- data/ext/puma_http11/mini_ssl.c +15 -2
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
- data/ext/puma_http11/puma_http11.c +7 -38
- data/lib/puma.rb +17 -0
- data/lib/puma/app/status.rb +18 -3
- data/lib/puma/binder.rb +88 -68
- data/lib/puma/cli.rb +7 -15
- data/lib/puma/client.rb +67 -14
- data/lib/puma/cluster.rb +191 -74
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +31 -42
- data/lib/puma/const.rb +4 -3
- data/lib/puma/control_cli.rb +29 -17
- data/lib/puma/detect.rb +17 -0
- data/lib/puma/dsl.rb +144 -70
- data/lib/puma/error_logger.rb +97 -0
- data/lib/puma/events.rb +35 -31
- data/lib/puma/io_buffer.rb +9 -2
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/launcher.rb +49 -31
- data/lib/puma/minissl.rb +60 -18
- data/lib/puma/minissl/context_builder.rb +0 -3
- data/lib/puma/null_io.rb +1 -1
- data/lib/puma/plugin.rb +1 -10
- data/lib/puma/rack/builder.rb +0 -4
- data/lib/puma/reactor.rb +9 -4
- data/lib/puma/runner.rb +8 -36
- data/lib/puma/server.rb +149 -186
- data/lib/puma/single.rb +7 -64
- data/lib/puma/state_file.rb +6 -3
- data/lib/puma/thread_pool.rb +94 -49
- data/lib/rack/handler/puma.rb +1 -3
- data/tools/{docker/Dockerfile → Dockerfile} +0 -0
- metadata +21 -23
- data/docs/tcp_mode.md +0 -96
- data/ext/puma_http11/io_buffer.c +0 -155
- data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
- data/lib/puma/tcp_logger.rb +0 -41
- data/tools/jungle/README.md +0 -19
- data/tools/jungle/init.d/README.md +0 -61
- data/tools/jungle/init.d/puma +0 -421
- data/tools/jungle/init.d/run-puma +0 -18
data/lib/puma/minissl.rb
CHANGED
@@ -5,8 +5,18 @@ begin
|
|
5
5
|
rescue LoadError
|
6
6
|
end
|
7
7
|
|
8
|
+
# need for Puma::MiniSSL::OPENSSL constants used in `HAS_TLS1_3`
|
9
|
+
require 'puma/puma_http11'
|
10
|
+
|
8
11
|
module Puma
|
9
12
|
module MiniSSL
|
13
|
+
# Define constant at runtime, as it's easy to determine at built time,
|
14
|
+
# but Puma could (it shouldn't) be loaded with an older OpenSSL version
|
15
|
+
# @version 5.0.0
|
16
|
+
HAS_TLS1_3 = !IS_JRUBY &&
|
17
|
+
(OPENSSL_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) != -1 &&
|
18
|
+
(OPENSSL_LIBRARY_VERSION[/ \d+\.\d+\.\d+/].split('.').map(&:to_i) <=> [1,1,1]) !=-1
|
19
|
+
|
10
20
|
class Socket
|
11
21
|
def initialize(socket, engine)
|
12
22
|
@socket = socket
|
@@ -22,6 +32,26 @@ module Puma
|
|
22
32
|
@socket.closed?
|
23
33
|
end
|
24
34
|
|
35
|
+
# Returns a two element array,
|
36
|
+
# first is protocol version (SSL_get_version),
|
37
|
+
# second is 'handshake' state (SSL_state_string)
|
38
|
+
#
|
39
|
+
# Used for dropping tcp connections to ssl.
|
40
|
+
# See OpenSSL ssl/ssl_stat.c SSL_state_string for info
|
41
|
+
# @version 5.0.0
|
42
|
+
#
|
43
|
+
def ssl_version_state
|
44
|
+
IS_JRUBY ? [nil, nil] : @engine.ssl_vers_st
|
45
|
+
end
|
46
|
+
|
47
|
+
# Used to check the handshake status, in particular when a TCP connection
|
48
|
+
# is made with TLSv1.3 as an available protocol
|
49
|
+
# @version 5.0.0
|
50
|
+
def bad_tlsv1_3?
|
51
|
+
HAS_TLS1_3 && @engine.ssl_vers_st == ['TLSv1.3', 'SSLERR']
|
52
|
+
end
|
53
|
+
private :bad_tlsv1_3?
|
54
|
+
|
25
55
|
def readpartial(size)
|
26
56
|
while true
|
27
57
|
output = @engine.read
|
@@ -41,6 +71,7 @@ module Puma
|
|
41
71
|
|
42
72
|
def engine_read_all
|
43
73
|
output = @engine.read
|
74
|
+
raise SSLError.exception "HTTP connection?" if bad_tlsv1_3?
|
44
75
|
while output and additional_output = @engine.read
|
45
76
|
output << additional_output
|
46
77
|
end
|
@@ -107,14 +138,18 @@ module Puma
|
|
107
138
|
alias_method :<<, :write
|
108
139
|
|
109
140
|
# This is a temporary fix to deal with websockets code using
|
110
|
-
# write_nonblock.
|
141
|
+
# write_nonblock.
|
142
|
+
|
143
|
+
# The problem with implementing it properly
|
111
144
|
# is that it means we'd have to have the ability to rewind
|
112
145
|
# an engine because after we write+extract, the socket
|
113
146
|
# write_nonblock call might raise an exception and later
|
114
147
|
# code would pass the same data in, but the engine would think
|
115
|
-
# it had already written the data in.
|
116
|
-
#
|
117
|
-
#
|
148
|
+
# it had already written the data in.
|
149
|
+
#
|
150
|
+
# So for the time being (and since write blocking is quite rare),
|
151
|
+
# go ahead and actually block in write_nonblock.
|
152
|
+
#
|
118
153
|
def write_nonblock(data, *_)
|
119
154
|
write data
|
120
155
|
end
|
@@ -125,11 +160,14 @@ module Puma
|
|
125
160
|
|
126
161
|
def read_and_drop(timeout = 1)
|
127
162
|
return :timeout unless IO.select([@socket], nil, nil, timeout)
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
163
|
+
case @socket.read_nonblock(1024, exception: false)
|
164
|
+
when nil
|
165
|
+
:eof
|
166
|
+
when :wait_readable
|
167
|
+
:eagain
|
168
|
+
else
|
169
|
+
:drop
|
170
|
+
end
|
133
171
|
end
|
134
172
|
|
135
173
|
def should_drop_bytes?
|
@@ -141,9 +179,7 @@ module Puma
|
|
141
179
|
# Read any drop any partially initialized sockets and any received bytes during shutdown.
|
142
180
|
# Don't let this socket hold this loop forever.
|
143
181
|
# If it can't send more packets within 1s, then give up.
|
144
|
-
while should_drop_bytes?
|
145
|
-
return if [:timeout, :eof].include?(read_and_drop(1))
|
146
|
-
end
|
182
|
+
return if [:timeout, :eof].include?(read_and_drop(1)) while should_drop_bytes?
|
147
183
|
rescue IOError, SystemCallError
|
148
184
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
149
185
|
# nothing
|
@@ -166,12 +202,13 @@ module Puma
|
|
166
202
|
end
|
167
203
|
end
|
168
204
|
|
169
|
-
if
|
205
|
+
if IS_JRUBY
|
206
|
+
OPENSSL_NO_SSL3 = false
|
207
|
+
OPENSSL_NO_TLS1 = false
|
208
|
+
|
170
209
|
class SSLError < StandardError
|
171
210
|
# Define this for jruby even though it isn't used.
|
172
211
|
end
|
173
|
-
|
174
|
-
def self.check; end
|
175
212
|
end
|
176
213
|
|
177
214
|
class Context
|
@@ -183,7 +220,7 @@ module Puma
|
|
183
220
|
@no_tlsv1_1 = false
|
184
221
|
end
|
185
222
|
|
186
|
-
if
|
223
|
+
if IS_JRUBY
|
187
224
|
# jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
|
188
225
|
attr_reader :keystore
|
189
226
|
attr_accessor :keystore_pass
|
@@ -228,13 +265,13 @@ module Puma
|
|
228
265
|
|
229
266
|
# disables TLSv1
|
230
267
|
def no_tlsv1=(tlsv1)
|
231
|
-
raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
|
268
|
+
raise ArgumentError, "Invalid value of no_tlsv1=" unless ['true', 'false', true, false].include?(tlsv1)
|
232
269
|
@no_tlsv1 = tlsv1
|
233
270
|
end
|
234
271
|
|
235
272
|
# disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
|
236
273
|
def no_tlsv1_1=(tlsv1_1)
|
237
|
-
raise ArgumentError, "Invalid value of
|
274
|
+
raise ArgumentError, "Invalid value of no_tlsv1_1=" unless ['true', 'false', true, false].include?(tlsv1_1)
|
238
275
|
@no_tlsv1_1 = tlsv1_1
|
239
276
|
end
|
240
277
|
|
@@ -270,6 +307,11 @@ module Puma
|
|
270
307
|
Socket.new io, engine
|
271
308
|
end
|
272
309
|
|
310
|
+
# @version 5.0.0
|
311
|
+
def addr
|
312
|
+
@socket.addr
|
313
|
+
end
|
314
|
+
|
273
315
|
def close
|
274
316
|
@socket.close unless @socket.closed? # closed? call is for Windows
|
275
317
|
end
|
data/lib/puma/null_io.rb
CHANGED
data/lib/puma/plugin.rb
CHANGED
@@ -10,7 +10,7 @@ module Puma
|
|
10
10
|
|
11
11
|
def create(name)
|
12
12
|
if cls = Plugins.find(name)
|
13
|
-
plugin = cls.new
|
13
|
+
plugin = cls.new
|
14
14
|
@instances << plugin
|
15
15
|
return plugin
|
16
16
|
end
|
@@ -104,17 +104,8 @@ module Puma
|
|
104
104
|
Plugins.register name, cls
|
105
105
|
end
|
106
106
|
|
107
|
-
def initialize(loader)
|
108
|
-
@loader = loader
|
109
|
-
end
|
110
|
-
|
111
107
|
def in_background(&blk)
|
112
108
|
Plugins.add_background blk
|
113
109
|
end
|
114
|
-
|
115
|
-
def workers_supported?
|
116
|
-
return false if Puma.jruby? || Puma.windows?
|
117
|
-
true
|
118
|
-
end
|
119
110
|
end
|
120
111
|
end
|
data/lib/puma/rack/builder.rb
CHANGED
@@ -67,10 +67,6 @@ module Puma::Rack
|
|
67
67
|
options[:environment] = e
|
68
68
|
}
|
69
69
|
|
70
|
-
opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
|
71
|
-
options[:daemonize] = d ? true : false
|
72
|
-
}
|
73
|
-
|
74
70
|
opts.on("-P", "--pid FILE", "file to store PID") { |f|
|
75
71
|
options[:pid] = ::File.expand_path(f)
|
76
72
|
}
|
data/lib/puma/reactor.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'puma/util'
|
4
|
-
require 'puma/minissl'
|
4
|
+
require 'puma/minissl' if ::Puma::HAS_SSL
|
5
5
|
|
6
6
|
require 'nio'
|
7
7
|
|
@@ -189,7 +189,12 @@ module Puma
|
|
189
189
|
if submon.value == @ready
|
190
190
|
false
|
191
191
|
else
|
192
|
-
submon.value.
|
192
|
+
if submon.value.can_close?
|
193
|
+
submon.value.close
|
194
|
+
else
|
195
|
+
# Pass remaining open client connections to the thread pool.
|
196
|
+
@app_pool << submon.value
|
197
|
+
end
|
193
198
|
begin
|
194
199
|
selector.deregister submon.value
|
195
200
|
rescue IOError
|
@@ -247,7 +252,7 @@ module Puma
|
|
247
252
|
c.close
|
248
253
|
clear_monitor mon
|
249
254
|
|
250
|
-
@events.ssl_error
|
255
|
+
@events.ssl_error e, addr, cert
|
251
256
|
|
252
257
|
# The client doesn't know HTTP well
|
253
258
|
rescue HttpParserError => e
|
@@ -258,7 +263,7 @@ module Puma
|
|
258
263
|
|
259
264
|
clear_monitor mon
|
260
265
|
|
261
|
-
@events.parse_error
|
266
|
+
@events.parse_error e, c
|
262
267
|
rescue StandardError => e
|
263
268
|
@server.lowlevel_error(e, c.env)
|
264
269
|
|
data/lib/puma/runner.rb
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
require 'puma/server'
|
4
4
|
require 'puma/const'
|
5
|
-
require 'puma/minissl/context_builder'
|
6
5
|
|
7
6
|
module Puma
|
8
7
|
# Generic class that is used by `Puma::Cluster` and `Puma::Single` to
|
@@ -18,10 +17,6 @@ module Puma
|
|
18
17
|
@started_at = Time.now
|
19
18
|
end
|
20
19
|
|
21
|
-
def daemon?
|
22
|
-
@options[:daemon]
|
23
|
-
end
|
24
|
-
|
25
20
|
def development?
|
26
21
|
@options[:environment] == "development"
|
27
22
|
end
|
@@ -34,7 +29,8 @@ module Puma
|
|
34
29
|
@events.log str
|
35
30
|
end
|
36
31
|
|
37
|
-
|
32
|
+
# @version 5.0.0
|
33
|
+
def stop_control
|
38
34
|
@control.stop(true) if @control
|
39
35
|
end
|
40
36
|
|
@@ -52,8 +48,6 @@ module Puma
|
|
52
48
|
|
53
49
|
require 'puma/app/status'
|
54
50
|
|
55
|
-
uri = URI.parse str
|
56
|
-
|
57
51
|
if token = @options[:control_auth_token]
|
58
52
|
token = nil if token.empty? || token == 'none'
|
59
53
|
end
|
@@ -64,30 +58,17 @@ module Puma
|
|
64
58
|
control.min_threads = 0
|
65
59
|
control.max_threads = 1
|
66
60
|
|
67
|
-
|
68
|
-
when "ssl"
|
69
|
-
log "* Starting control server on #{str}"
|
70
|
-
params = Util.parse_query uri.query
|
71
|
-
ctx = MiniSSL::ContextBuilder.new(params, @events).context
|
72
|
-
|
73
|
-
control.add_ssl_listener uri.host, uri.port, ctx
|
74
|
-
when "tcp"
|
75
|
-
log "* Starting control server on #{str}"
|
76
|
-
control.add_tcp_listener uri.host, uri.port
|
77
|
-
when "unix"
|
78
|
-
log "* Starting control server on #{str}"
|
79
|
-
path = "#{uri.host}#{uri.path}"
|
80
|
-
mask = @options[:control_url_umask]
|
81
|
-
|
82
|
-
control.add_unix_listener path, mask
|
83
|
-
else
|
84
|
-
error "Invalid control URI: #{str}"
|
85
|
-
end
|
61
|
+
control.binder.parse [str], self, 'Starting control server'
|
86
62
|
|
87
63
|
control.run
|
88
64
|
@control = control
|
89
65
|
end
|
90
66
|
|
67
|
+
# @version 5.0.0
|
68
|
+
def close_control_listeners
|
69
|
+
@control.binder.close_listeners if @control
|
70
|
+
end
|
71
|
+
|
91
72
|
def ruby_engine
|
92
73
|
if !defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby"
|
93
74
|
"ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
|
@@ -108,10 +89,6 @@ module Puma
|
|
108
89
|
log "* Version #{Puma::Const::PUMA_VERSION} (#{ruby_engine}), codename: #{Puma::Const::CODE_NAME}"
|
109
90
|
log "* Min threads: #{min_t}, max threads: #{max_t}"
|
110
91
|
log "* Environment: #{ENV['RACK_ENV']}"
|
111
|
-
|
112
|
-
if @options[:mode] == :tcp
|
113
|
-
log "* Mode: Lopez Express (tcp)"
|
114
|
-
end
|
115
92
|
end
|
116
93
|
|
117
94
|
def redirected_io?
|
@@ -150,7 +127,6 @@ module Puma
|
|
150
127
|
exit 1
|
151
128
|
end
|
152
129
|
|
153
|
-
# Load the app before we daemonize.
|
154
130
|
begin
|
155
131
|
@app = @launcher.config.app
|
156
132
|
rescue Exception => e
|
@@ -174,10 +150,6 @@ module Puma
|
|
174
150
|
server.max_threads = max_t
|
175
151
|
server.inherit_binder @launcher.binder
|
176
152
|
|
177
|
-
if @options[:mode] == :tcp
|
178
|
-
server.tcp_mode!
|
179
|
-
end
|
180
|
-
|
181
153
|
if @options[:early_hints]
|
182
154
|
server.early_hints = true
|
183
155
|
end
|
data/lib/puma/server.rb
CHANGED
@@ -9,10 +9,8 @@ require 'puma/null_io'
|
|
9
9
|
require 'puma/reactor'
|
10
10
|
require 'puma/client'
|
11
11
|
require 'puma/binder'
|
12
|
-
require 'puma/accept_nonblock'
|
13
12
|
require 'puma/util'
|
14
|
-
|
15
|
-
require 'puma/puma_http11'
|
13
|
+
require 'puma/io_buffer'
|
16
14
|
|
17
15
|
require 'socket'
|
18
16
|
require 'forwardable'
|
@@ -36,6 +34,7 @@ module Puma
|
|
36
34
|
|
37
35
|
attr_reader :thread
|
38
36
|
attr_reader :events
|
37
|
+
attr_reader :requests_count # @version 5.0.0
|
39
38
|
attr_accessor :app
|
40
39
|
|
41
40
|
attr_accessor :min_threads
|
@@ -57,8 +56,7 @@ module Puma
|
|
57
56
|
@app = app
|
58
57
|
@events = events
|
59
58
|
|
60
|
-
@check, @notify =
|
61
|
-
|
59
|
+
@check, @notify = nil
|
62
60
|
@status = :stop
|
63
61
|
|
64
62
|
@min_threads = 0
|
@@ -85,24 +83,34 @@ module Puma
|
|
85
83
|
@mode = :http
|
86
84
|
|
87
85
|
@precheck_closing = true
|
86
|
+
|
87
|
+
@requests_count = 0
|
88
88
|
end
|
89
89
|
|
90
90
|
attr_accessor :binder, :leak_stack_on_error, :early_hints
|
91
91
|
|
92
|
-
def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :
|
92
|
+
def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_ports
|
93
93
|
|
94
94
|
def inherit_binder(bind)
|
95
95
|
@binder = bind
|
96
96
|
end
|
97
97
|
|
98
|
-
|
99
|
-
|
98
|
+
class << self
|
99
|
+
# :nodoc:
|
100
|
+
# @version 5.0.0
|
101
|
+
def tcp_cork_supported?
|
102
|
+
RbConfig::CONFIG['host_os'] =~ /linux/ &&
|
103
|
+
Socket.const_defined?(:IPPROTO_TCP) &&
|
104
|
+
Socket.const_defined?(:TCP_CORK) &&
|
105
|
+
Socket.const_defined?(:TCP_INFO)
|
106
|
+
end
|
107
|
+
private :tcp_cork_supported?
|
100
108
|
end
|
101
109
|
|
102
110
|
# On Linux, use TCP_CORK to better control how the TCP stack
|
103
111
|
# packetizes our stream. This improves both latency and throughput.
|
104
112
|
#
|
105
|
-
if
|
113
|
+
if tcp_cork_supported?
|
106
114
|
UNPACK_TCP_STATE_FROM_TCP_INFO = "C".freeze
|
107
115
|
|
108
116
|
# 6 == Socket::IPPROTO_TCP
|
@@ -110,7 +118,7 @@ module Puma
|
|
110
118
|
# 1/0 == turn on/off
|
111
119
|
def cork_socket(socket)
|
112
120
|
begin
|
113
|
-
socket.setsockopt(
|
121
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 1) if socket.kind_of? TCPSocket
|
114
122
|
rescue IOError, SystemCallError
|
115
123
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
116
124
|
end
|
@@ -118,7 +126,7 @@ module Puma
|
|
118
126
|
|
119
127
|
def uncork_socket(socket)
|
120
128
|
begin
|
121
|
-
socket.setsockopt(
|
129
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_CORK, 0) if socket.kind_of? TCPSocket
|
122
130
|
rescue IOError, SystemCallError
|
123
131
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
124
132
|
end
|
@@ -129,7 +137,7 @@ module Puma
|
|
129
137
|
return false unless @precheck_closing
|
130
138
|
|
131
139
|
begin
|
132
|
-
tcp_info = socket.getsockopt(Socket::
|
140
|
+
tcp_info = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO)
|
133
141
|
rescue IOError, SystemCallError
|
134
142
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
135
143
|
@precheck_closing = false
|
@@ -172,107 +180,6 @@ module Puma
|
|
172
180
|
@thread_pool and @thread_pool.pool_capacity
|
173
181
|
end
|
174
182
|
|
175
|
-
# Lopez Mode == raw tcp apps
|
176
|
-
|
177
|
-
def run_lopez_mode(background=true)
|
178
|
-
@thread_pool = ThreadPool.new(@min_threads,
|
179
|
-
@max_threads,
|
180
|
-
Hash) do |client, tl|
|
181
|
-
|
182
|
-
io = client.to_io
|
183
|
-
addr = io.peeraddr.last
|
184
|
-
|
185
|
-
if addr.empty?
|
186
|
-
# Set unix socket addrs to localhost
|
187
|
-
addr = "127.0.0.1:0"
|
188
|
-
else
|
189
|
-
addr = "#{addr}:#{io.peeraddr[1]}"
|
190
|
-
end
|
191
|
-
|
192
|
-
env = { 'thread' => tl, REMOTE_ADDR => addr }
|
193
|
-
|
194
|
-
begin
|
195
|
-
@app.call env, client.to_io
|
196
|
-
rescue Object => e
|
197
|
-
STDERR.puts "! Detected exception at toplevel: #{e.message} (#{e.class})"
|
198
|
-
STDERR.puts e.backtrace
|
199
|
-
end
|
200
|
-
|
201
|
-
client.close unless env['detach']
|
202
|
-
end
|
203
|
-
|
204
|
-
@events.fire :state, :running
|
205
|
-
|
206
|
-
if background
|
207
|
-
@thread = Thread.new do
|
208
|
-
Puma.set_thread_name "server"
|
209
|
-
handle_servers_lopez_mode
|
210
|
-
end
|
211
|
-
return @thread
|
212
|
-
else
|
213
|
-
handle_servers_lopez_mode
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
def handle_servers_lopez_mode
|
218
|
-
begin
|
219
|
-
check = @check
|
220
|
-
sockets = [check] + @binder.ios
|
221
|
-
pool = @thread_pool
|
222
|
-
|
223
|
-
while @status == :run
|
224
|
-
begin
|
225
|
-
ios = IO.select sockets
|
226
|
-
ios.first.each do |sock|
|
227
|
-
if sock == check
|
228
|
-
break if handle_check
|
229
|
-
else
|
230
|
-
begin
|
231
|
-
if io = sock.accept_nonblock
|
232
|
-
client = Client.new io, nil
|
233
|
-
pool << client
|
234
|
-
end
|
235
|
-
rescue SystemCallError
|
236
|
-
# nothing
|
237
|
-
rescue Errno::ECONNABORTED
|
238
|
-
# client closed the socket even before accept
|
239
|
-
begin
|
240
|
-
io.close
|
241
|
-
rescue
|
242
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
243
|
-
end
|
244
|
-
end
|
245
|
-
end
|
246
|
-
end
|
247
|
-
rescue Object => e
|
248
|
-
@events.unknown_error self, e, "Listen loop"
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
@events.fire :state, @status
|
253
|
-
|
254
|
-
graceful_shutdown if @status == :stop || @status == :restart
|
255
|
-
|
256
|
-
rescue Exception => e
|
257
|
-
STDERR.puts "Exception handling servers: #{e.message} (#{e.class})"
|
258
|
-
STDERR.puts e.backtrace
|
259
|
-
ensure
|
260
|
-
begin
|
261
|
-
@check.close
|
262
|
-
rescue
|
263
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
264
|
-
end
|
265
|
-
|
266
|
-
# Prevent can't modify frozen IOError (RuntimeError)
|
267
|
-
begin
|
268
|
-
@notify.close
|
269
|
-
rescue IOError
|
270
|
-
# no biggy
|
271
|
-
end
|
272
|
-
end
|
273
|
-
|
274
|
-
@events.fire :state, :done
|
275
|
-
end
|
276
183
|
# Runs the server.
|
277
184
|
#
|
278
185
|
# If +background+ is true (the default) then a thread is spun
|
@@ -286,15 +193,9 @@ module Puma
|
|
286
193
|
|
287
194
|
@status = :run
|
288
195
|
|
289
|
-
if @mode == :tcp
|
290
|
-
return run_lopez_mode(background)
|
291
|
-
end
|
292
|
-
|
293
|
-
queue_requests = @queue_requests
|
294
|
-
|
295
196
|
@thread_pool = ThreadPool.new(@min_threads,
|
296
197
|
@max_threads,
|
297
|
-
IOBuffer) do |client, buffer|
|
198
|
+
::Puma::IOBuffer) do |client, buffer|
|
298
199
|
|
299
200
|
# Advertise this server into the thread
|
300
201
|
Thread.current[ThreadLocalKey] = self
|
@@ -302,10 +203,10 @@ module Puma
|
|
302
203
|
process_now = false
|
303
204
|
|
304
205
|
begin
|
305
|
-
if queue_requests
|
206
|
+
if @queue_requests
|
306
207
|
process_now = client.eagerly_finish
|
307
208
|
else
|
308
|
-
client.finish
|
209
|
+
client.finish(@first_data_timeout)
|
309
210
|
process_now = true
|
310
211
|
end
|
311
212
|
rescue MiniSSL::SSLError => e
|
@@ -315,14 +216,16 @@ module Puma
|
|
315
216
|
|
316
217
|
client.close
|
317
218
|
|
318
|
-
@events.ssl_error
|
219
|
+
@events.ssl_error e, addr, cert
|
319
220
|
rescue HttpParserError => e
|
320
221
|
client.write_error(400)
|
321
222
|
client.close
|
322
223
|
|
323
|
-
@events.parse_error
|
324
|
-
rescue ConnectionError, EOFError
|
224
|
+
@events.parse_error e, client
|
225
|
+
rescue ConnectionError, EOFError => e
|
325
226
|
client.close
|
227
|
+
|
228
|
+
@events.connection_error e, client
|
326
229
|
else
|
327
230
|
if process_now
|
328
231
|
process_client client, buffer
|
@@ -331,11 +234,14 @@ module Puma
|
|
331
234
|
@reactor.add client
|
332
235
|
end
|
333
236
|
end
|
237
|
+
|
238
|
+
process_now
|
334
239
|
end
|
335
240
|
|
241
|
+
@thread_pool.out_of_band_hook = @options[:out_of_band]
|
336
242
|
@thread_pool.clean_thread_locals = @options[:clean_thread_locals]
|
337
243
|
|
338
|
-
if queue_requests
|
244
|
+
if @queue_requests
|
339
245
|
@reactor = Reactor.new self, @thread_pool
|
340
246
|
@reactor.run_in_thread
|
341
247
|
end
|
@@ -362,6 +268,7 @@ module Puma
|
|
362
268
|
end
|
363
269
|
|
364
270
|
def handle_servers
|
271
|
+
@check, @notify = Puma::Util.pipe unless @notify
|
365
272
|
begin
|
366
273
|
check = @check
|
367
274
|
sockets = [check] + @binder.ios
|
@@ -385,51 +292,49 @@ module Puma
|
|
385
292
|
if sock == check
|
386
293
|
break if handle_check
|
387
294
|
else
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
end
|
403
|
-
rescue SystemCallError
|
404
|
-
# nothing
|
405
|
-
rescue Errno::ECONNABORTED
|
406
|
-
# client closed the socket even before accept
|
407
|
-
begin
|
408
|
-
io.close
|
409
|
-
rescue
|
410
|
-
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
411
|
-
end
|
295
|
+
pool.wait_until_not_full
|
296
|
+
pool.wait_for_less_busy_worker(
|
297
|
+
@options[:wait_for_less_busy_worker].to_f)
|
298
|
+
|
299
|
+
io = begin
|
300
|
+
sock.accept_nonblock
|
301
|
+
rescue IO::WaitReadable
|
302
|
+
next
|
303
|
+
end
|
304
|
+
client = Client.new io, @binder.env(sock)
|
305
|
+
if remote_addr_value
|
306
|
+
client.peerip = remote_addr_value
|
307
|
+
elsif remote_addr_header
|
308
|
+
client.remote_addr_header = remote_addr_header
|
412
309
|
end
|
310
|
+
pool << client
|
413
311
|
end
|
414
312
|
end
|
415
313
|
rescue Object => e
|
416
|
-
@events.unknown_error
|
314
|
+
@events.unknown_error e, nil, "Listen loop"
|
417
315
|
end
|
418
316
|
end
|
419
317
|
|
420
318
|
@events.fire :state, @status
|
421
319
|
|
422
|
-
graceful_shutdown if @status == :stop || @status == :restart
|
423
320
|
if queue_requests
|
321
|
+
@queue_requests = false
|
424
322
|
@reactor.clear!
|
425
323
|
@reactor.shutdown
|
426
324
|
end
|
325
|
+
graceful_shutdown if @status == :stop || @status == :restart
|
427
326
|
rescue Exception => e
|
428
|
-
|
429
|
-
STDERR.puts e.backtrace
|
327
|
+
@events.unknown_error e, nil, "Exception handling servers"
|
430
328
|
ensure
|
431
|
-
|
329
|
+
begin
|
330
|
+
@check.close unless @check.closed?
|
331
|
+
rescue Errno::EBADF, RuntimeError
|
332
|
+
# RuntimeError is Ruby 2.2 issue, can't modify frozen IOError
|
333
|
+
# Errno::EBADF is infrequently raised
|
334
|
+
end
|
432
335
|
@notify.close
|
336
|
+
@notify = nil
|
337
|
+
@check = nil
|
433
338
|
end
|
434
339
|
|
435
340
|
@events.fire :state, :done
|
@@ -476,7 +381,6 @@ module Puma
|
|
476
381
|
close_socket = false
|
477
382
|
return
|
478
383
|
when true
|
479
|
-
return unless @queue_requests
|
480
384
|
buffer.reset
|
481
385
|
|
482
386
|
ThreadPool.clean_thread_locals if clean_thread_locals
|
@@ -494,6 +398,7 @@ module Puma
|
|
494
398
|
end
|
495
399
|
|
496
400
|
unless client.reset(check_for_more_data)
|
401
|
+
return unless @queue_requests
|
497
402
|
close_socket = false
|
498
403
|
client.set_timeout @persistent_timeout
|
499
404
|
@reactor.add client
|
@@ -516,7 +421,7 @@ module Puma
|
|
516
421
|
|
517
422
|
close_socket = true
|
518
423
|
|
519
|
-
@events.ssl_error
|
424
|
+
@events.ssl_error e, addr, cert
|
520
425
|
|
521
426
|
# The client doesn't know HTTP well
|
522
427
|
rescue HttpParserError => e
|
@@ -524,7 +429,7 @@ module Puma
|
|
524
429
|
|
525
430
|
client.write_error(400)
|
526
431
|
|
527
|
-
@events.parse_error
|
432
|
+
@events.parse_error e, client
|
528
433
|
|
529
434
|
# Server error
|
530
435
|
rescue StandardError => e
|
@@ -532,8 +437,7 @@ module Puma
|
|
532
437
|
|
533
438
|
client.write_error(500)
|
534
439
|
|
535
|
-
@events.unknown_error
|
536
|
-
|
440
|
+
@events.unknown_error e, nil, "Read"
|
537
441
|
ensure
|
538
442
|
buffer.reset
|
539
443
|
|
@@ -543,7 +447,7 @@ module Puma
|
|
543
447
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
544
448
|
# Already closed
|
545
449
|
rescue StandardError => e
|
546
|
-
@events.unknown_error
|
450
|
+
@events.unknown_error e, nil, "Client"
|
547
451
|
end
|
548
452
|
end
|
549
453
|
end
|
@@ -579,7 +483,7 @@ module Puma
|
|
579
483
|
|
580
484
|
env[PATH_INFO] = env[REQUEST_PATH]
|
581
485
|
|
582
|
-
# From
|
486
|
+
# From https://www.ietf.org/rfc/rfc3875 :
|
583
487
|
# "Script authors should be aware that the REMOTE_ADDR and
|
584
488
|
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
585
489
|
# may not identify the ultimate source of the request.
|
@@ -626,6 +530,8 @@ module Puma
|
|
626
530
|
#
|
627
531
|
# Finally, it'll return +true+ on keep-alive connections.
|
628
532
|
def handle_request(req, lines)
|
533
|
+
@requests_count +=1
|
534
|
+
|
629
535
|
env = req.env
|
630
536
|
client = req.io
|
631
537
|
|
@@ -657,6 +563,7 @@ module Puma
|
|
657
563
|
headers.each_pair do |k, vs|
|
658
564
|
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
659
565
|
vs.to_s.split(NEWLINE).each do |v|
|
566
|
+
next if possible_header_injection?(v)
|
660
567
|
fast_write client, "#{k}: #{v}\r\n"
|
661
568
|
end
|
662
569
|
else
|
@@ -665,12 +572,44 @@ module Puma
|
|
665
572
|
end
|
666
573
|
|
667
574
|
fast_write client, "\r\n".freeze
|
668
|
-
rescue ConnectionError
|
575
|
+
rescue ConnectionError => e
|
576
|
+
@events.debug_error e
|
669
577
|
# noop, if we lost the socket we just won't send the early hints
|
670
578
|
end
|
671
579
|
}
|
672
580
|
end
|
673
581
|
|
582
|
+
# Fixup any headers with , in the name to have _ now. We emit
|
583
|
+
# headers with , in them during the parse phase to avoid ambiguity
|
584
|
+
# with the - to _ conversion for critical headers. But here for
|
585
|
+
# compatibility, we'll convert them back. This code is written to
|
586
|
+
# avoid allocation in the common case (ie there are no headers
|
587
|
+
# with , in their names), that's why it has the extra conditionals.
|
588
|
+
|
589
|
+
to_delete = nil
|
590
|
+
to_add = nil
|
591
|
+
|
592
|
+
env.each do |k,v|
|
593
|
+
if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
|
594
|
+
if to_delete
|
595
|
+
to_delete << k
|
596
|
+
else
|
597
|
+
to_delete = [k]
|
598
|
+
end
|
599
|
+
|
600
|
+
unless to_add
|
601
|
+
to_add = {}
|
602
|
+
end
|
603
|
+
|
604
|
+
to_add[k.tr(",", "_")] = v
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|
608
|
+
if to_delete
|
609
|
+
to_delete.each { |k| env.delete(k) }
|
610
|
+
env.merge! to_add
|
611
|
+
end
|
612
|
+
|
674
613
|
# A rack extension. If the app writes #call'ables to this
|
675
614
|
# array, we will invoke them when the request is done.
|
676
615
|
#
|
@@ -692,17 +631,14 @@ module Puma
|
|
692
631
|
return :async
|
693
632
|
end
|
694
633
|
rescue ThreadPool::ForceShutdown => e
|
695
|
-
@events.
|
696
|
-
@events.
|
697
|
-
|
698
|
-
status = 503
|
699
|
-
headers = {}
|
700
|
-
res_body = ["Request was internally terminated early\n"]
|
634
|
+
@events.unknown_error e, req, "Rack app"
|
635
|
+
@events.log "Detected force shutdown of a thread"
|
701
636
|
|
637
|
+
status, headers, res_body = lowlevel_error(e, env, 503)
|
702
638
|
rescue Exception => e
|
703
|
-
@events.unknown_error
|
639
|
+
@events.unknown_error e, req, "Rack app"
|
704
640
|
|
705
|
-
status, headers, res_body = lowlevel_error(e, env)
|
641
|
+
status, headers, res_body = lowlevel_error(e, env, 500)
|
706
642
|
end
|
707
643
|
|
708
644
|
content_length = nil
|
@@ -717,10 +653,10 @@ module Puma
|
|
717
653
|
line_ending = LINE_END
|
718
654
|
colon = COLON
|
719
655
|
|
720
|
-
http_11 =
|
656
|
+
http_11 = env[HTTP_VERSION] == HTTP_11
|
657
|
+
if http_11
|
721
658
|
allow_chunked = true
|
722
659
|
keep_alive = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
|
723
|
-
include_keepalive_header = false
|
724
660
|
|
725
661
|
# An optimization. The most common response is 200, so we can
|
726
662
|
# reply with the proper 200 status without having to compute
|
@@ -734,11 +670,9 @@ module Puma
|
|
734
670
|
|
735
671
|
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
736
672
|
end
|
737
|
-
true
|
738
673
|
else
|
739
674
|
allow_chunked = false
|
740
675
|
keep_alive = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
|
741
|
-
include_keepalive_header = keep_alive
|
742
676
|
|
743
677
|
# Same optimization as above for HTTP/1.1
|
744
678
|
#
|
@@ -750,14 +684,18 @@ module Puma
|
|
750
684
|
|
751
685
|
no_body ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
|
752
686
|
end
|
753
|
-
false
|
754
687
|
end
|
755
688
|
|
689
|
+
# regardless of what the client wants, we always close the connection
|
690
|
+
# if running without request queueing
|
691
|
+
keep_alive &&= @queue_requests
|
692
|
+
|
756
693
|
response_hijack = nil
|
757
694
|
|
758
695
|
headers.each do |k, vs|
|
759
696
|
case k.downcase
|
760
697
|
when CONTENT_LENGTH2
|
698
|
+
next if possible_header_injection?(vs)
|
761
699
|
content_length = vs
|
762
700
|
next
|
763
701
|
when TRANSFER_ENCODING
|
@@ -770,6 +708,7 @@ module Puma
|
|
770
708
|
|
771
709
|
if vs.respond_to?(:to_s) && !vs.to_s.empty?
|
772
710
|
vs.to_s.split(NEWLINE).each do |v|
|
711
|
+
next if possible_header_injection?(v)
|
773
712
|
lines.append k, colon, v, line_ending
|
774
713
|
end
|
775
714
|
else
|
@@ -777,10 +716,15 @@ module Puma
|
|
777
716
|
end
|
778
717
|
end
|
779
718
|
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
719
|
+
# HTTP/1.1 & 1.0 assume different defaults:
|
720
|
+
# - HTTP 1.0 assumes the connection will be closed if not specified
|
721
|
+
# - HTTP 1.1 assumes the connection will be kept alive if not specified.
|
722
|
+
# Only set the header if we're doing something which is not the default
|
723
|
+
# for this protocol version
|
724
|
+
if http_11
|
725
|
+
lines << CONNECTION_CLOSE if !keep_alive
|
726
|
+
else
|
727
|
+
lines << CONNECTION_KEEP_ALIVE if keep_alive
|
784
728
|
end
|
785
729
|
|
786
730
|
if no_body
|
@@ -907,19 +851,21 @@ module Puma
|
|
907
851
|
|
908
852
|
# A fallback rack response if +@app+ raises as exception.
|
909
853
|
#
|
910
|
-
def lowlevel_error(e, env)
|
854
|
+
def lowlevel_error(e, env, status=500)
|
911
855
|
if handler = @options[:lowlevel_error_handler]
|
912
856
|
if handler.arity == 1
|
913
857
|
return handler.call(e)
|
914
|
-
|
858
|
+
elsif handler.arity == 2
|
915
859
|
return handler.call(e, env)
|
860
|
+
else
|
861
|
+
return handler.call(e, env, status)
|
916
862
|
end
|
917
863
|
end
|
918
864
|
|
919
865
|
if @leak_stack_on_error
|
920
|
-
[
|
866
|
+
[status, {}, ["Puma caught this error: #{e.message} (#{e.class})\n#{e.backtrace.join("\n")}"]]
|
921
867
|
else
|
922
|
-
[
|
868
|
+
[status, {}, ["An unhandled lowlevel error occurred. The application logs may have details.\n"]]
|
923
869
|
end
|
924
870
|
end
|
925
871
|
|
@@ -977,9 +923,10 @@ module Puma
|
|
977
923
|
end
|
978
924
|
|
979
925
|
def notify_safely(message)
|
926
|
+
@check, @notify = Puma::Util.pipe unless @notify
|
980
927
|
begin
|
981
928
|
@notify << message
|
982
|
-
rescue IOError
|
929
|
+
rescue IOError, NoMethodError, Errno::EPIPE
|
983
930
|
# The server, in another thread, is shutting down
|
984
931
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
985
932
|
rescue RuntimeError => e
|
@@ -1006,8 +953,9 @@ module Puma
|
|
1006
953
|
@thread.join if @thread && sync
|
1007
954
|
end
|
1008
955
|
|
1009
|
-
def begin_restart
|
956
|
+
def begin_restart(sync=false)
|
1010
957
|
notify_safely(RESTART_COMMAND)
|
958
|
+
@thread.join if @thread && sync
|
1011
959
|
end
|
1012
960
|
|
1013
961
|
def fast_write(io, str)
|
@@ -1040,5 +988,20 @@ module Puma
|
|
1040
988
|
def shutting_down?
|
1041
989
|
@status == :stop || @status == :restart
|
1042
990
|
end
|
991
|
+
|
992
|
+
def possible_header_injection?(header_value)
|
993
|
+
HTTP_INJECTION_REGEX =~ header_value.to_s
|
994
|
+
end
|
995
|
+
private :possible_header_injection?
|
996
|
+
|
997
|
+
# List of methods invoked by #stats.
|
998
|
+
# @version 5.0.0
|
999
|
+
STAT_METHODS = [:backlog, :running, :pool_capacity, :max_threads, :requests_count].freeze
|
1000
|
+
|
1001
|
+
# Returns a hash of stats about the running server for reporting purposes.
|
1002
|
+
# @version 5.0.0
|
1003
|
+
def stats
|
1004
|
+
STAT_METHODS.map {|name| [name, send(name) || 0]}.to_h
|
1005
|
+
end
|
1043
1006
|
end
|
1044
1007
|
end
|