puma 5.0.3 → 5.2.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 +4 -4
- data/History.md +110 -40
- data/README.md +48 -18
- data/docs/compile_options.md +19 -0
- data/docs/deployment.md +1 -1
- data/docs/fork_worker.md +2 -0
- data/docs/kubernetes.md +66 -0
- data/docs/plugins.md +1 -1
- data/docs/rails_dev_mode.md +29 -0
- data/docs/stats.md +142 -0
- data/docs/systemd.md +24 -2
- data/ext/puma_http11/extconf.rb +18 -5
- data/ext/puma_http11/http11_parser.c +45 -47
- data/ext/puma_http11/http11_parser.java.rl +1 -1
- data/ext/puma_http11/http11_parser.rl +1 -1
- data/ext/puma_http11/mini_ssl.c +162 -84
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +5 -7
- data/ext/puma_http11/puma_http11.c +8 -2
- data/lib/puma.rb +20 -10
- data/lib/puma/app/status.rb +4 -7
- data/lib/puma/binder.rb +60 -24
- data/lib/puma/cli.rb +4 -0
- data/lib/puma/client.rb +4 -9
- data/lib/puma/cluster.rb +13 -7
- data/lib/puma/cluster/worker.rb +8 -2
- data/lib/puma/cluster/worker_handle.rb +5 -2
- data/lib/puma/configuration.rb +13 -1
- data/lib/puma/const.rb +11 -3
- data/lib/puma/control_cli.rb +73 -70
- data/lib/puma/detect.rb +14 -10
- data/lib/puma/dsl.rb +100 -22
- data/lib/puma/error_logger.rb +10 -3
- data/lib/puma/events.rb +18 -3
- data/lib/puma/json.rb +96 -0
- data/lib/puma/launcher.rb +52 -6
- data/lib/puma/minissl.rb +48 -17
- data/lib/puma/minissl/context_builder.rb +6 -0
- data/lib/puma/null_io.rb +4 -0
- data/lib/puma/reactor.rb +19 -12
- data/lib/puma/request.rb +20 -7
- data/lib/puma/runner.rb +14 -7
- data/lib/puma/server.rb +20 -75
- data/lib/puma/state_file.rb +5 -3
- data/lib/puma/systemd.rb +46 -0
- data/lib/rack/handler/puma.rb +1 -0
- metadata +12 -6
data/lib/puma.rb
CHANGED
@@ -12,12 +12,31 @@ require 'thread'
|
|
12
12
|
|
13
13
|
require 'puma/puma_http11'
|
14
14
|
require 'puma/detect'
|
15
|
+
require 'puma/json'
|
15
16
|
|
16
17
|
module Puma
|
17
18
|
autoload :Const, 'puma/const'
|
18
19
|
autoload :Server, 'puma/server'
|
19
20
|
autoload :Launcher, 'puma/launcher'
|
20
21
|
|
22
|
+
# at present, MiniSSL::Engine is only defined in extension code (puma_http11),
|
23
|
+
# not in minissl.rb
|
24
|
+
HAS_SSL = const_defined?(:MiniSSL, false) && MiniSSL.const_defined?(:Engine, false)
|
25
|
+
|
26
|
+
if HAS_SSL
|
27
|
+
require 'puma/minissl'
|
28
|
+
else
|
29
|
+
module MiniSSL
|
30
|
+
# this class is defined so that it exists when Puma is compiled
|
31
|
+
# without ssl support, as Server and Reactor use it in rescue statements.
|
32
|
+
class SSLError < StandardError ; end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.ssl?
|
37
|
+
HAS_SSL
|
38
|
+
end
|
39
|
+
|
21
40
|
# @!attribute [rw] stats_object=
|
22
41
|
def self.stats_object=(val)
|
23
42
|
@get_stats = val
|
@@ -25,8 +44,7 @@ module Puma
|
|
25
44
|
|
26
45
|
# @!attribute [rw] stats_object
|
27
46
|
def self.stats
|
28
|
-
|
29
|
-
@get_stats.stats.to_json
|
47
|
+
Puma::JSON.generate @get_stats.stats
|
30
48
|
end
|
31
49
|
|
32
50
|
# @!attribute [r] stats_hash
|
@@ -40,12 +58,4 @@ module Puma
|
|
40
58
|
return unless Thread.current.respond_to?(:name=)
|
41
59
|
Thread.current.name = "puma #{name}"
|
42
60
|
end
|
43
|
-
|
44
|
-
unless HAS_SSL
|
45
|
-
module MiniSSL
|
46
|
-
# this class is defined so that it exists when Puma is compiled
|
47
|
-
# without ssl support, as Server and Reactor use it in rescue statements.
|
48
|
-
class SSLError < StandardError ; end
|
49
|
-
end
|
50
|
-
end
|
51
61
|
end
|
data/lib/puma/app/status.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require 'puma/json'
|
2
3
|
|
3
4
|
module Puma
|
4
5
|
module App
|
@@ -22,10 +23,6 @@ module Puma
|
|
22
23
|
return rack_response(403, 'Invalid auth token', 'text/plain')
|
23
24
|
end
|
24
25
|
|
25
|
-
if env['PATH_INFO'] =~ /\/(gc-stats|stats|thread-backtraces)$/
|
26
|
-
require 'json'
|
27
|
-
end
|
28
|
-
|
29
26
|
# resp_type is processed by following case statement, return
|
30
27
|
# is a number (status) or a string used as the body of a 200 response
|
31
28
|
resp_type =
|
@@ -49,17 +46,17 @@ module Puma
|
|
49
46
|
GC.start ; 200
|
50
47
|
|
51
48
|
when 'gc-stats'
|
52
|
-
GC.stat
|
49
|
+
Puma::JSON.generate GC.stat
|
53
50
|
|
54
51
|
when 'stats'
|
55
|
-
@launcher.stats
|
52
|
+
Puma::JSON.generate @launcher.stats
|
56
53
|
|
57
54
|
when 'thread-backtraces'
|
58
55
|
backtraces = []
|
59
56
|
@launcher.thread_status do |name, backtrace|
|
60
57
|
backtraces << { name: name, backtrace: backtrace }
|
61
58
|
end
|
62
|
-
backtraces
|
59
|
+
Puma::JSON.generate backtraces
|
63
60
|
|
64
61
|
else
|
65
62
|
return rack_response(404, "Unsupported action", 'text/plain')
|
data/lib/puma/binder.rb
CHANGED
@@ -111,6 +111,43 @@ module Puma
|
|
111
111
|
["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
|
112
112
|
end
|
113
113
|
|
114
|
+
# Synthesize binds from systemd socket activation
|
115
|
+
#
|
116
|
+
# When systemd socket activation is enabled, it can be tedious to keep the
|
117
|
+
# binds in sync. This method can synthesize any binds based on the received
|
118
|
+
# activated sockets. Any existing matching binds will be respected.
|
119
|
+
#
|
120
|
+
# When only_matching is true in, all binds that do not match an activated
|
121
|
+
# socket is removed in place.
|
122
|
+
#
|
123
|
+
# It's a noop if no activated sockets were received.
|
124
|
+
def synthesize_binds_from_activated_fs(binds, only_matching)
|
125
|
+
return binds unless activated_sockets.any?
|
126
|
+
|
127
|
+
activated_binds = []
|
128
|
+
|
129
|
+
activated_sockets.keys.each do |proto, addr, port|
|
130
|
+
if port
|
131
|
+
tcp_url = "#{proto}://#{addr}:#{port}"
|
132
|
+
ssl_url = "ssl://#{addr}:#{port}"
|
133
|
+
ssl_url_prefix = "#{ssl_url}?"
|
134
|
+
|
135
|
+
existing = binds.find { |bind| bind == tcp_url || bind == ssl_url || bind.start_with?(ssl_url_prefix) }
|
136
|
+
|
137
|
+
activated_binds << (existing || tcp_url)
|
138
|
+
else
|
139
|
+
# TODO: can there be a SSL bind without a port?
|
140
|
+
activated_binds << "#{proto}://#{addr}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
if only_matching
|
145
|
+
activated_binds
|
146
|
+
else
|
147
|
+
binds | activated_binds
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
114
151
|
def parse(binds, logger, log_msg = 'Listening')
|
115
152
|
binds.each do |str|
|
116
153
|
uri = URI.parse str
|
@@ -123,6 +160,7 @@ module Puma
|
|
123
160
|
io = inherit_tcp_listener uri.host, uri.port, sock
|
124
161
|
logger.log "* Activated #{str}"
|
125
162
|
else
|
163
|
+
ios_len = @ios.length
|
126
164
|
params = Util.parse_query uri.query
|
127
165
|
|
128
166
|
opt = params.key?('low_latency')
|
@@ -130,14 +168,8 @@ module Puma
|
|
130
168
|
|
131
169
|
io = add_tcp_listener uri.host, uri.port, opt, bak
|
132
170
|
|
133
|
-
@ios.each do |i|
|
134
|
-
|
135
|
-
addr = if i.local_address.ipv6?
|
136
|
-
"[#{i.local_address.ip_unpack[0]}]:#{i.local_address.ip_unpack[1]}"
|
137
|
-
else
|
138
|
-
i.local_address.ip_unpack.join(':')
|
139
|
-
end
|
140
|
-
|
171
|
+
@ios[ios_len..-1].each do |i|
|
172
|
+
addr = loc_addr_str i
|
141
173
|
logger.log "* #{log_msg} on http://#{addr}"
|
142
174
|
end
|
143
175
|
end
|
@@ -192,8 +224,13 @@ module Puma
|
|
192
224
|
io = inherit_ssl_listener sock, ctx
|
193
225
|
logger.log "* Activated #{str}"
|
194
226
|
else
|
227
|
+
ios_len = @ios.length
|
195
228
|
io = add_ssl_listener uri.host, uri.port, ctx
|
196
|
-
|
229
|
+
|
230
|
+
@ios[ios_len..-1].each do |i|
|
231
|
+
addr = loc_addr_str i
|
232
|
+
logger.log "* #{log_msg} on ssl://#{addr}?#{uri.query}"
|
233
|
+
end
|
197
234
|
end
|
198
235
|
|
199
236
|
@listeners << [str, io] if io
|
@@ -260,11 +297,7 @@ module Puma
|
|
260
297
|
end
|
261
298
|
|
262
299
|
def inherit_tcp_listener(host, port, fd)
|
263
|
-
|
264
|
-
s = fd
|
265
|
-
else
|
266
|
-
s = TCPServer.for_fd(fd)
|
267
|
-
end
|
300
|
+
s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
|
268
301
|
|
269
302
|
@ios << s
|
270
303
|
s
|
@@ -302,11 +335,8 @@ module Puma
|
|
302
335
|
def inherit_ssl_listener(fd, ctx)
|
303
336
|
raise "Puma compiled without SSL support" unless HAS_SSL
|
304
337
|
|
305
|
-
|
306
|
-
|
307
|
-
else
|
308
|
-
s = TCPServer.for_fd(fd)
|
309
|
-
end
|
338
|
+
s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
|
339
|
+
|
310
340
|
ssl = MiniSSL::Server.new(s, ctx)
|
311
341
|
|
312
342
|
env = @proto_env.dup
|
@@ -361,11 +391,8 @@ module Puma
|
|
361
391
|
def inherit_unix_listener(path, fd)
|
362
392
|
@unix_paths << path unless File.exist? path
|
363
393
|
|
364
|
-
|
365
|
-
|
366
|
-
else
|
367
|
-
s = UNIXServer.for_fd fd
|
368
|
-
end
|
394
|
+
s = fd.kind_of?(::TCPServer) ? fd : ::UNIXServer.for_fd(fd)
|
395
|
+
|
369
396
|
@ios << s
|
370
397
|
|
371
398
|
env = @proto_env.dup
|
@@ -407,6 +434,15 @@ module Puma
|
|
407
434
|
end.map { |addrinfo| addrinfo.ip_address }.uniq
|
408
435
|
end
|
409
436
|
|
437
|
+
def loc_addr_str(io)
|
438
|
+
loc_addr = io.to_io.local_address
|
439
|
+
if loc_addr.ipv6?
|
440
|
+
"[#{loc_addr.ip_unpack[0]}]:#{loc_addr.ip_unpack[1]}"
|
441
|
+
else
|
442
|
+
loc_addr.ip_unpack.join(':')
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
410
446
|
# @version 5.0.0
|
411
447
|
def socket_activation_fd(int)
|
412
448
|
int + 3 # 3 is the magic number you add to follow the SA protocol
|
data/lib/puma/cli.rb
CHANGED
@@ -104,6 +104,10 @@ module Puma
|
|
104
104
|
user_config.bind arg
|
105
105
|
end
|
106
106
|
|
107
|
+
o.on "--bind-to-activated-sockets [only]", "Bind to all activated sockets" do |arg|
|
108
|
+
user_config.bind_to_activated_sockets(arg || true)
|
109
|
+
end
|
110
|
+
|
107
111
|
o.on "-C", "--config PATH", "Load PATH as a config file" do |arg|
|
108
112
|
file_config.load arg
|
109
113
|
end
|
data/lib/puma/client.rb
CHANGED
@@ -239,13 +239,8 @@ module Puma
|
|
239
239
|
# @version 5.0.0
|
240
240
|
#
|
241
241
|
def can_close?
|
242
|
-
# Allow connection to close if
|
243
|
-
|
244
|
-
#
|
245
|
-
# From RFC 2616 section 8.1.4:
|
246
|
-
# Servers SHOULD always respond to at least one request per connection,
|
247
|
-
# if at all possible.
|
248
|
-
@requests_served > 0 && @parsed_bytes == 0
|
242
|
+
# Allow connection to close if we're not in the middle of parsing a request.
|
243
|
+
@parsed_bytes == 0
|
249
244
|
end
|
250
245
|
|
251
246
|
private
|
@@ -379,7 +374,7 @@ module Puma
|
|
379
374
|
end
|
380
375
|
|
381
376
|
if decode_chunk(chunk)
|
382
|
-
@env[CONTENT_LENGTH] = @chunked_content_length
|
377
|
+
@env[CONTENT_LENGTH] = @chunked_content_length.to_s
|
383
378
|
return true
|
384
379
|
end
|
385
380
|
end
|
@@ -396,7 +391,7 @@ module Puma
|
|
396
391
|
@chunked_content_length = 0
|
397
392
|
|
398
393
|
if decode_chunk(body)
|
399
|
-
@env[CONTENT_LENGTH] = @chunked_content_length
|
394
|
+
@env[CONTENT_LENGTH] = @chunked_content_length.to_s
|
400
395
|
return true
|
401
396
|
end
|
402
397
|
end
|
data/lib/puma/cluster.rb
CHANGED
@@ -114,7 +114,7 @@ module Puma
|
|
114
114
|
debug "Workers to cull: #{workers_to_cull.inspect}"
|
115
115
|
|
116
116
|
workers_to_cull.each do |worker|
|
117
|
-
log "- Worker #{worker.index} (
|
117
|
+
log "- Worker #{worker.index} (PID: #{worker.pid}) terminating"
|
118
118
|
worker.term
|
119
119
|
end
|
120
120
|
end
|
@@ -186,10 +186,12 @@ module Puma
|
|
186
186
|
pipes[:wakeup] = @wakeup
|
187
187
|
end
|
188
188
|
|
189
|
+
server = start_server if preload?
|
189
190
|
new_worker = Worker.new index: index,
|
190
191
|
master: master,
|
191
192
|
launcher: @launcher,
|
192
|
-
pipes: pipes
|
193
|
+
pipes: pipes,
|
194
|
+
server: server
|
193
195
|
new_worker.run
|
194
196
|
end
|
195
197
|
|
@@ -327,15 +329,19 @@ module Puma
|
|
327
329
|
|
328
330
|
output_header "cluster"
|
329
331
|
|
330
|
-
|
332
|
+
# This is aligned with the output from Runner, see Runner#output_header
|
333
|
+
log "* Workers: #{@options[:workers]}"
|
331
334
|
|
332
|
-
|
335
|
+
# Threads explicitly marked as fork safe will be ignored.
|
336
|
+
# Used in Rails, but may be used by anyone.
|
337
|
+
before = Thread.list.reject { |t| t.thread_variable_get(:fork_safe) }
|
333
338
|
|
334
339
|
if preload?
|
340
|
+
log "* Restarts: (\u2714) hot (\u2716) phased"
|
335
341
|
log "* Preloading application"
|
336
342
|
load_and_bind
|
337
343
|
|
338
|
-
after = Thread.list
|
344
|
+
after = Thread.list.reject { |t| t.thread_variable_get(:fork_safe) }
|
339
345
|
|
340
346
|
if after.size > before.size
|
341
347
|
threads = (after - before)
|
@@ -349,7 +355,7 @@ module Puma
|
|
349
355
|
end
|
350
356
|
end
|
351
357
|
else
|
352
|
-
log "*
|
358
|
+
log "* Restarts: (\u2714) hot (\u2714) phased"
|
353
359
|
|
354
360
|
unless @launcher.config.app_configured?
|
355
361
|
error "No application configured, nothing to run"
|
@@ -428,7 +434,7 @@ module Puma
|
|
428
434
|
case req
|
429
435
|
when "b"
|
430
436
|
w.boot!
|
431
|
-
log "- Worker #{w.index} (
|
437
|
+
log "- Worker #{w.index} (PID: #{pid}) booted, phase: #{w.phase}"
|
432
438
|
@next_check = Time.now
|
433
439
|
when "e"
|
434
440
|
# external term, see worker method, Signal.trap "SIGTERM"
|
data/lib/puma/cluster/worker.rb
CHANGED
@@ -108,11 +108,17 @@ module Puma
|
|
108
108
|
server_thread = server.run
|
109
109
|
stat_thread ||= Thread.new(@worker_write) do |io|
|
110
110
|
Puma.set_thread_name "stat payload"
|
111
|
+
base_payload = "p#{Process.pid}"
|
111
112
|
|
112
113
|
while true
|
113
114
|
begin
|
114
|
-
|
115
|
-
|
115
|
+
b = server.backlog || 0
|
116
|
+
r = server.running || 0
|
117
|
+
t = server.pool_capacity || 0
|
118
|
+
m = server.max_threads || 0
|
119
|
+
rc = server.requests_count || 0
|
120
|
+
payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m}, "requests_count": #{rc} }\n!
|
121
|
+
io << payload
|
116
122
|
rescue IOError
|
117
123
|
Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
|
118
124
|
break
|
@@ -42,8 +42,11 @@ module Puma
|
|
42
42
|
|
43
43
|
def ping!(status)
|
44
44
|
@last_checkin = Time.now
|
45
|
-
|
46
|
-
@last_status =
|
45
|
+
captures = status.match(/{ "backlog":(?<backlog>\d*), "running":(?<running>\d*), "pool_capacity":(?<pool_capacity>\d*), "max_threads": (?<max_threads>\d*), "requests_count": (?<requests_count>\d*) }/)
|
46
|
+
@last_status = captures.names.inject({}) do |hash, key|
|
47
|
+
hash[key.to_sym] = captures[key].to_i
|
48
|
+
hash
|
49
|
+
end
|
47
50
|
end
|
48
51
|
|
49
52
|
# @see Puma::Cluster#check_workers
|
data/lib/puma/configuration.rb
CHANGED
@@ -92,6 +92,12 @@ module Puma
|
|
92
92
|
end
|
93
93
|
end
|
94
94
|
end
|
95
|
+
|
96
|
+
def final_options
|
97
|
+
default_options
|
98
|
+
.merge(file_options)
|
99
|
+
.merge(user_options)
|
100
|
+
end
|
95
101
|
end
|
96
102
|
|
97
103
|
# The main configuration class of Puma.
|
@@ -198,7 +204,9 @@ module Puma
|
|
198
204
|
:logger => STDOUT,
|
199
205
|
:persistent_timeout => Const::PERSISTENT_TIMEOUT,
|
200
206
|
:first_data_timeout => Const::FIRST_DATA_TIMEOUT,
|
201
|
-
:raise_exception_on_sigterm => true
|
207
|
+
:raise_exception_on_sigterm => true,
|
208
|
+
:max_fast_inline => Const::MAX_FAST_INLINE,
|
209
|
+
:io_selector_backend => :auto
|
202
210
|
}
|
203
211
|
end
|
204
212
|
|
@@ -289,6 +297,10 @@ module Puma
|
|
289
297
|
end
|
290
298
|
end
|
291
299
|
|
300
|
+
def final_options
|
301
|
+
@options.final_options
|
302
|
+
end
|
303
|
+
|
292
304
|
def self.temp_path
|
293
305
|
require 'tmpdir'
|
294
306
|
|
data/lib/puma/const.rb
CHANGED
@@ -100,8 +100,8 @@ module Puma
|
|
100
100
|
# too taxing on performance.
|
101
101
|
module Const
|
102
102
|
|
103
|
-
PUMA_VERSION = VERSION = "5.
|
104
|
-
CODE_NAME = "
|
103
|
+
PUMA_VERSION = VERSION = "5.2.1".freeze
|
104
|
+
CODE_NAME = "Fettisdagsbulle".freeze
|
105
105
|
|
106
106
|
PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
|
107
107
|
|
@@ -228,7 +228,6 @@ module Puma
|
|
228
228
|
COLON = ": ".freeze
|
229
229
|
|
230
230
|
NEWLINE = "\n".freeze
|
231
|
-
HTTP_INJECTION_REGEX = /[\r\n]/.freeze
|
232
231
|
|
233
232
|
HIJACK_P = "rack.hijack?".freeze
|
234
233
|
HIJACK = "rack.hijack".freeze
|
@@ -239,5 +238,14 @@ module Puma
|
|
239
238
|
# Mininum interval to checks worker health
|
240
239
|
WORKER_CHECK_INTERVAL = 5
|
241
240
|
|
241
|
+
# Illegal character in the key or value of response header
|
242
|
+
DQUOTE = "\"".freeze
|
243
|
+
HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
|
244
|
+
ILLEGAL_HEADER_KEY_REGEX = /[\x00-\x20#{DQUOTE}#{HTTP_HEADER_DELIMITER}]/.freeze
|
245
|
+
# header values can contain HTAB?
|
246
|
+
ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/.freeze
|
247
|
+
|
248
|
+
# Banned keys of response header
|
249
|
+
BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
|
242
250
|
end
|
243
251
|
end
|
data/lib/puma/control_cli.rb
CHANGED
@@ -11,10 +11,32 @@ require 'socket'
|
|
11
11
|
module Puma
|
12
12
|
class ControlCLI
|
13
13
|
|
14
|
-
|
14
|
+
# values must be string or nil
|
15
|
+
# value of `nil` means command cannot be processed via signal
|
16
|
+
# @version 5.0.3
|
17
|
+
CMD_PATH_SIG_MAP = {
|
18
|
+
'gc' => nil,
|
19
|
+
'gc-stats' => nil,
|
20
|
+
'halt' => 'SIGQUIT',
|
21
|
+
'phased-restart' => 'SIGUSR1',
|
22
|
+
'refork' => 'SIGURG',
|
23
|
+
'reload-worker-directory' => nil,
|
24
|
+
'restart' => 'SIGUSR2',
|
25
|
+
'start' => nil,
|
26
|
+
'stats' => nil,
|
27
|
+
'status' => '',
|
28
|
+
'stop' => 'SIGTERM',
|
29
|
+
'thread-backtraces' => nil
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
# @deprecated 6.0.0
|
33
|
+
COMMANDS = CMD_PATH_SIG_MAP.keys.freeze
|
34
|
+
|
35
|
+
# commands that cannot be used in a request
|
36
|
+
NO_REQ_COMMANDS = %w{refork}.freeze
|
15
37
|
|
16
38
|
# @version 5.0.0
|
17
|
-
PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}
|
39
|
+
PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}.freeze
|
18
40
|
|
19
41
|
def initialize(argv, stdout=STDOUT, stderr=STDERR)
|
20
42
|
@state = nil
|
@@ -33,7 +55,7 @@ module Puma
|
|
33
55
|
@cli_options = {}
|
34
56
|
|
35
57
|
opts = OptionParser.new do |o|
|
36
|
-
o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{
|
58
|
+
o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{CMD_PATH_SIG_MAP.keys.join("|")})"
|
37
59
|
|
38
60
|
o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
|
39
61
|
@state = arg
|
@@ -74,7 +96,7 @@ module Puma
|
|
74
96
|
end
|
75
97
|
|
76
98
|
o.on_tail("-V", "--version", "Show version") do
|
77
|
-
puts Const::PUMA_VERSION
|
99
|
+
@stdout.puts Const::PUMA_VERSION
|
78
100
|
exit
|
79
101
|
end
|
80
102
|
end
|
@@ -86,10 +108,10 @@ module Puma
|
|
86
108
|
|
87
109
|
# check presence of command
|
88
110
|
unless @command
|
89
|
-
raise "Available commands: #{
|
111
|
+
raise "Available commands: #{CMD_PATH_SIG_MAP.keys.join(", ")}"
|
90
112
|
end
|
91
113
|
|
92
|
-
unless
|
114
|
+
unless CMD_PATH_SIG_MAP.key? @command
|
93
115
|
raise "Invalid command: #{@command}"
|
94
116
|
end
|
95
117
|
|
@@ -134,7 +156,7 @@ module Puma
|
|
134
156
|
@pid = sf.pid
|
135
157
|
elsif @pidfile
|
136
158
|
# get pid from pid_file
|
137
|
-
File.
|
159
|
+
@pid = File.read(@pidfile, mode: 'rb:UTF-8').to_i
|
138
160
|
end
|
139
161
|
end
|
140
162
|
|
@@ -142,24 +164,27 @@ module Puma
|
|
142
164
|
uri = URI.parse @control_url
|
143
165
|
|
144
166
|
# create server object by scheme
|
145
|
-
server =
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
167
|
+
server =
|
168
|
+
case uri.scheme
|
169
|
+
when 'ssl'
|
170
|
+
require 'openssl'
|
171
|
+
OpenSSL::SSL::SSLSocket.new(
|
172
|
+
TCPSocket.new(uri.host, uri.port),
|
173
|
+
OpenSSL::SSL::SSLContext.new)
|
174
|
+
.tap { |ssl| ssl.sync_close = true } # default is false
|
175
|
+
.tap(&:connect)
|
176
|
+
when 'tcp'
|
177
|
+
TCPSocket.new uri.host, uri.port
|
178
|
+
when 'unix'
|
179
|
+
UNIXSocket.new "#{uri.host}#{uri.path}"
|
180
|
+
else
|
181
|
+
raise "Invalid scheme: #{uri.scheme}"
|
182
|
+
end
|
183
|
+
|
184
|
+
if @command == 'status'
|
185
|
+
message 'Puma is started'
|
186
|
+
elsif NO_REQ_COMMANDS.include? @command
|
187
|
+
raise "Invalid request command: #{@command}"
|
163
188
|
else
|
164
189
|
url = "/#{@command}"
|
165
190
|
|
@@ -167,10 +192,10 @@ module Puma
|
|
167
192
|
url = url + "?token=#{@control_auth_token}"
|
168
193
|
end
|
169
194
|
|
170
|
-
server
|
195
|
+
server.syswrite "GET #{url} HTTP/1.0\r\n\r\n"
|
171
196
|
|
172
197
|
unless data = server.read
|
173
|
-
raise
|
198
|
+
raise 'Server closed connection before responding'
|
174
199
|
end
|
175
200
|
|
176
201
|
response = data.split("\r\n")
|
@@ -179,13 +204,13 @@ module Puma
|
|
179
204
|
raise "Server sent empty response"
|
180
205
|
end
|
181
206
|
|
182
|
-
|
207
|
+
@http, @code, @message = response.first.split(' ',3)
|
183
208
|
|
184
|
-
if @code ==
|
185
|
-
raise
|
186
|
-
elsif @code ==
|
209
|
+
if @code == '403'
|
210
|
+
raise 'Unauthorized access to server (wrong auth token)'
|
211
|
+
elsif @code == '404'
|
187
212
|
raise "Command error: #{response.last}"
|
188
|
-
elsif @code !=
|
213
|
+
elsif @code != '200'
|
189
214
|
raise "Bad response from server: #{@code}"
|
190
215
|
end
|
191
216
|
|
@@ -194,7 +219,7 @@ module Puma
|
|
194
219
|
end
|
195
220
|
ensure
|
196
221
|
if server
|
197
|
-
if uri.scheme ==
|
222
|
+
if uri.scheme == 'ssl'
|
198
223
|
server.sysclose
|
199
224
|
else
|
200
225
|
server.close unless server.closed?
|
@@ -204,51 +229,30 @@ module Puma
|
|
204
229
|
|
205
230
|
def send_signal
|
206
231
|
unless @pid
|
207
|
-
raise
|
232
|
+
raise 'Neither pid nor control url available'
|
208
233
|
end
|
209
234
|
|
210
235
|
begin
|
236
|
+
sig = CMD_PATH_SIG_MAP[@command]
|
211
237
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
when "halt"
|
217
|
-
Process.kill "QUIT", @pid
|
218
|
-
|
219
|
-
when "stop"
|
220
|
-
Process.kill "SIGTERM", @pid
|
221
|
-
|
222
|
-
when "stats"
|
223
|
-
puts "Stats not available via pid only"
|
224
|
-
return
|
225
|
-
|
226
|
-
when "reload-worker-directory"
|
227
|
-
puts "reload-worker-directory not available via pid only"
|
238
|
+
if sig.nil?
|
239
|
+
@stdout.puts "'#{@command}' not available via pid only"
|
240
|
+
@stdout.flush unless @stdout.sync
|
228
241
|
return
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
when "status"
|
242
|
+
elsif sig.start_with? 'SIG'
|
243
|
+
Process.kill sig, @pid
|
244
|
+
elsif @command == 'status'
|
234
245
|
begin
|
235
246
|
Process.kill 0, @pid
|
236
|
-
puts
|
247
|
+
@stdout.puts 'Puma is started'
|
248
|
+
@stdout.flush unless @stdout.sync
|
237
249
|
rescue Errno::ESRCH
|
238
|
-
raise
|
250
|
+
raise 'Puma is not running'
|
239
251
|
end
|
240
|
-
|
241
|
-
return
|
242
|
-
|
243
|
-
when "refork"
|
244
|
-
Process.kill "SIGURG", @pid
|
245
|
-
|
246
|
-
else
|
247
252
|
return
|
248
253
|
end
|
249
|
-
|
250
254
|
rescue SystemCallError
|
251
|
-
if @command ==
|
255
|
+
if @command == 'restart'
|
252
256
|
start
|
253
257
|
else
|
254
258
|
raise "No pid '#{@pid}' found"
|
@@ -259,14 +263,13 @@ module Puma
|
|
259
263
|
end
|
260
264
|
|
261
265
|
def run
|
262
|
-
return start if @command ==
|
263
|
-
|
266
|
+
return start if @command == 'start'
|
264
267
|
prepare_configuration
|
265
268
|
|
266
|
-
if Puma.windows?
|
269
|
+
if Puma.windows? || @control_url
|
267
270
|
send_request
|
268
271
|
else
|
269
|
-
|
272
|
+
send_signal
|
270
273
|
end
|
271
274
|
|
272
275
|
rescue => e
|