puma 4.3.12 → 6.0.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 +4 -4
- data/History.md +1591 -521
- data/LICENSE +23 -20
- data/README.md +130 -42
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +63 -26
- data/docs/compile_options.md +55 -0
- data/docs/deployment.md +60 -69
- data/docs/fork_worker.md +31 -0
- data/docs/jungle/README.md +9 -0
- data/{tools → docs}/jungle/rc.d/README.md +1 -1
- data/{tools → docs}/jungle/rc.d/puma +2 -2
- data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +66 -0
- data/docs/nginx.md +1 -1
- data/docs/plugins.md +15 -15
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +46 -23
- data/docs/signals.md +13 -11
- data/docs/stats.md +142 -0
- data/docs/systemd.md +85 -128
- data/docs/testing_benchmarks_local_files.md +150 -0
- data/docs/testing_test_rackup_ci_files.md +36 -0
- data/ext/puma_http11/PumaHttp11Service.java +2 -4
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +49 -12
- data/ext/puma_http11/http11_parser.c +46 -48
- data/ext/puma_http11/http11_parser.h +2 -2
- data/ext/puma_http11/http11_parser.java.rl +3 -3
- data/ext/puma_http11/http11_parser.rl +3 -3
- data/ext/puma_http11/http11_parser_common.rl +2 -2
- data/ext/puma_http11/mini_ssl.c +250 -93
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +6 -6
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +4 -6
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +241 -96
- data/ext/puma_http11/puma_http11.c +46 -57
- data/lib/puma/app/status.rb +52 -38
- data/lib/puma/binder.rb +232 -119
- data/lib/puma/cli.rb +33 -33
- data/lib/puma/client.rb +125 -87
- data/lib/puma/cluster/worker.rb +175 -0
- data/lib/puma/cluster/worker_handle.rb +97 -0
- data/lib/puma/cluster.rb +224 -229
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +112 -87
- data/lib/puma/const.rb +25 -22
- data/lib/puma/control_cli.rb +99 -79
- data/lib/puma/detect.rb +31 -2
- data/lib/puma/dsl.rb +423 -110
- data/lib/puma/error_logger.rb +112 -0
- data/lib/puma/events.rb +16 -115
- data/lib/puma/io_buffer.rb +34 -2
- data/lib/puma/jruby_restart.rb +2 -59
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher/bundle_pruner.rb +104 -0
- data/lib/puma/launcher.rb +170 -148
- data/lib/puma/log_writer.rb +137 -0
- data/lib/puma/minissl/context_builder.rb +35 -19
- data/lib/puma/minissl.rb +213 -55
- data/lib/puma/null_io.rb +18 -1
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/plugin.rb +3 -12
- data/lib/puma/rack/builder.rb +5 -9
- data/lib/puma/rack_default.rb +1 -1
- data/lib/puma/reactor.rb +85 -369
- data/lib/puma/request.rb +607 -0
- data/lib/puma/runner.rb +83 -77
- data/lib/puma/server.rb +305 -789
- data/lib/puma/single.rb +18 -74
- data/lib/puma/state_file.rb +45 -8
- data/lib/puma/systemd.rb +47 -0
- data/lib/puma/thread_pool.rb +137 -66
- data/lib/puma/util.rb +21 -4
- data/lib/puma.rb +54 -5
- data/lib/rack/handler/puma.rb +11 -12
- data/tools/{docker/Dockerfile → Dockerfile} +1 -1
- metadata +31 -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/accept_nonblock.rb +0 -29
- 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/tools/jungle/upstart/README.md +0 -61
- data/tools/jungle/upstart/puma-manager.conf +0 -31
- data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/configuration.rb
CHANGED
@@ -1,20 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
require_relative 'rack/builder'
|
4
|
+
require_relative 'plugin'
|
5
|
+
require_relative 'const'
|
6
|
+
# note that dsl is loaded at end of file, requires ConfigDefault constants
|
6
7
|
|
7
8
|
module Puma
|
8
|
-
|
9
|
-
module ConfigDefault
|
10
|
-
DefaultRackup = "config.ru"
|
11
|
-
|
12
|
-
DefaultTCPHost = "0.0.0.0"
|
13
|
-
DefaultTCPPort = 9292
|
14
|
-
DefaultWorkerTimeout = 60
|
15
|
-
DefaultWorkerShutdownTimeout = 30
|
16
|
-
end
|
17
|
-
|
18
9
|
# A class used for storing "leveled" configuration options.
|
19
10
|
#
|
20
11
|
# In this class any "user" specified options take precedence over any
|
@@ -54,9 +45,7 @@ module Puma
|
|
54
45
|
attr_reader :user_options, :file_options, :default_options
|
55
46
|
|
56
47
|
def [](key)
|
57
|
-
|
58
|
-
return file_options[key] if file_options.key?(key)
|
59
|
-
return default_options[key] if default_options.key?(key)
|
48
|
+
fetch(key)
|
60
49
|
end
|
61
50
|
|
62
51
|
def []=(key, value)
|
@@ -64,7 +53,11 @@ module Puma
|
|
64
53
|
end
|
65
54
|
|
66
55
|
def fetch(key, default_value = nil)
|
67
|
-
|
56
|
+
return user_options[key] if user_options.key?(key)
|
57
|
+
return file_options[key] if file_options.key?(key)
|
58
|
+
return default_options[key] if default_options.key?(key)
|
59
|
+
|
60
|
+
default_value
|
68
61
|
end
|
69
62
|
|
70
63
|
def all_of(key)
|
@@ -90,6 +83,12 @@ module Puma
|
|
90
83
|
end
|
91
84
|
end
|
92
85
|
end
|
86
|
+
|
87
|
+
def final_options
|
88
|
+
default_options
|
89
|
+
.merge(file_options)
|
90
|
+
.merge(user_options)
|
91
|
+
end
|
93
92
|
end
|
94
93
|
|
95
94
|
# The main configuration class of Puma.
|
@@ -106,16 +105,17 @@ module Puma
|
|
106
105
|
#
|
107
106
|
# It also handles loading plugins.
|
108
107
|
#
|
109
|
-
#
|
108
|
+
# [Note:]
|
109
|
+
# `:port` and `:host` are not valid keys. By the time they make it to the
|
110
110
|
# configuration options they are expected to be incorporated into a `:binds` key.
|
111
111
|
# Under the hood the DSL maps `port` and `host` calls to `:binds`
|
112
112
|
#
|
113
|
-
#
|
114
|
-
#
|
115
|
-
#
|
116
|
-
#
|
117
|
-
#
|
118
|
-
#
|
113
|
+
# config = Configuration.new({}) do |user_config, file_config, default_config|
|
114
|
+
# user_config.port 3003
|
115
|
+
# end
|
116
|
+
# config.load
|
117
|
+
# puts config.options[:port]
|
118
|
+
# # => 3003
|
119
119
|
#
|
120
120
|
# It is expected that `load` is called on the configuration instance after setting
|
121
121
|
# config. This method expands any values in `config_file` and puts them into the
|
@@ -126,7 +126,48 @@ module Puma
|
|
126
126
|
# is done because an environment variable may have been modified while loading
|
127
127
|
# configuration files.
|
128
128
|
class Configuration
|
129
|
-
|
129
|
+
DEFAULTS = {
|
130
|
+
auto_trim_time: 30,
|
131
|
+
binds: ['tcp://0.0.0.0:9292'.freeze],
|
132
|
+
clean_thread_locals: false,
|
133
|
+
debug: false,
|
134
|
+
early_hints: nil,
|
135
|
+
environment: 'development'.freeze,
|
136
|
+
# Number of seconds to wait until we get the first data for the request
|
137
|
+
first_data_timeout: 30,
|
138
|
+
io_selector_backend: :auto,
|
139
|
+
log_requests: false,
|
140
|
+
logger: STDOUT,
|
141
|
+
# How many requests to attempt inline before sending a client back to
|
142
|
+
# the reactor to be subject to normal ordering. The idea here is that
|
143
|
+
# we amortize the cost of going back to the reactor for a well behaved
|
144
|
+
# but very "greedy" client across 10 requests. This prevents a not
|
145
|
+
# well behaved client from monopolizing the thread forever.
|
146
|
+
max_fast_inline: 10,
|
147
|
+
max_threads: Puma.mri? ? 5 : 16,
|
148
|
+
min_threads: 0,
|
149
|
+
mode: :http,
|
150
|
+
mutate_stdout_and_stderr_to_sync_on_write: true,
|
151
|
+
out_of_band: [],
|
152
|
+
# Number of seconds for another request within a persistent session.
|
153
|
+
persistent_timeout: 20,
|
154
|
+
queue_requests: true,
|
155
|
+
rackup: 'config.ru'.freeze,
|
156
|
+
raise_exception_on_sigterm: true,
|
157
|
+
reaping_time: 1,
|
158
|
+
remote_address: :socket,
|
159
|
+
silence_single_worker_warning: false,
|
160
|
+
tag: File.basename(Dir.getwd),
|
161
|
+
tcp_host: '0.0.0.0'.freeze,
|
162
|
+
tcp_port: 9292,
|
163
|
+
wait_for_less_busy_worker: 0.005,
|
164
|
+
worker_boot_timeout: 60,
|
165
|
+
worker_check_interval: 5,
|
166
|
+
worker_culling_strategy: :youngest,
|
167
|
+
worker_shutdown_timeout: 30,
|
168
|
+
worker_timeout: 60,
|
169
|
+
workers: 0,
|
170
|
+
}
|
130
171
|
|
131
172
|
def initialize(user_options={}, default_options = {}, &block)
|
132
173
|
default_options = self.puma_default_options.merge(default_options)
|
@@ -137,6 +178,10 @@ module Puma
|
|
137
178
|
@file_dsl = DSL.new(@options.file_options, self)
|
138
179
|
@default_dsl = DSL.new(@options.default_options, self)
|
139
180
|
|
181
|
+
if !@options[:prune_bundler]
|
182
|
+
default_options[:preload_app] = (@options[:workers] > 1) && Puma.forkable?
|
183
|
+
end
|
184
|
+
|
140
185
|
if block
|
141
186
|
configure(&block)
|
142
187
|
end
|
@@ -168,26 +213,21 @@ module Puma
|
|
168
213
|
end
|
169
214
|
|
170
215
|
def puma_default_options
|
216
|
+
defaults = DEFAULTS.dup
|
217
|
+
puma_options_from_env.each { |k,v| defaults[k] = v if v }
|
218
|
+
defaults
|
219
|
+
end
|
220
|
+
|
221
|
+
def puma_options_from_env
|
222
|
+
min = ENV['PUMA_MIN_THREADS'] || ENV['MIN_THREADS']
|
223
|
+
max = ENV['PUMA_MAX_THREADS'] || ENV['MAX_THREADS']
|
224
|
+
workers = ENV['WEB_CONCURRENCY']
|
225
|
+
|
171
226
|
{
|
172
|
-
:
|
173
|
-
:
|
174
|
-
:
|
175
|
-
:
|
176
|
-
:binds => ["tcp://#{DefaultTCPHost}:#{DefaultTCPPort}"],
|
177
|
-
:workers => 0,
|
178
|
-
:daemon => false,
|
179
|
-
:mode => :http,
|
180
|
-
:worker_timeout => DefaultWorkerTimeout,
|
181
|
-
:worker_boot_timeout => DefaultWorkerTimeout,
|
182
|
-
:worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
|
183
|
-
:remote_address => :socket,
|
184
|
-
:tag => method(:infer_tag),
|
185
|
-
:environment => -> { ENV['RACK_ENV'] || "development" },
|
186
|
-
:rackup => DefaultRackup,
|
187
|
-
:logger => STDOUT,
|
188
|
-
:persistent_timeout => Const::PERSISTENT_TIMEOUT,
|
189
|
-
:first_data_timeout => Const::FIRST_DATA_TIMEOUT,
|
190
|
-
:raise_exception_on_sigterm => true
|
227
|
+
min_threads: min && Integer(min),
|
228
|
+
max_threads: max && Integer(max),
|
229
|
+
workers: workers && Integer(workers),
|
230
|
+
environment: ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'],
|
191
231
|
}
|
192
232
|
end
|
193
233
|
|
@@ -203,7 +243,7 @@ module Puma
|
|
203
243
|
return [] if files == ['-']
|
204
244
|
return files if files.any?
|
205
245
|
|
206
|
-
first_default_file = %W(config/puma/#{
|
246
|
+
first_default_file = %W(config/puma/#{@options[:environment]}.rb config/puma.rb).find do |f|
|
207
247
|
File.exist?(f)
|
208
248
|
end
|
209
249
|
|
@@ -245,16 +285,8 @@ module Puma
|
|
245
285
|
def app
|
246
286
|
found = options[:app] || load_rackup
|
247
287
|
|
248
|
-
if @options[:mode] == :tcp
|
249
|
-
require 'puma/tcp_logger'
|
250
|
-
|
251
|
-
logger = @options[:logger]
|
252
|
-
quiet = !@options[:log_requests]
|
253
|
-
return TCPLogger.new(logger, found, quiet)
|
254
|
-
end
|
255
|
-
|
256
288
|
if @options[:log_requests]
|
257
|
-
|
289
|
+
require_relative 'commonlogger'
|
258
290
|
logger = @options[:logger]
|
259
291
|
found = CommonLogger.new(found, logger)
|
260
292
|
end
|
@@ -267,16 +299,31 @@ module Puma
|
|
267
299
|
@options[:environment]
|
268
300
|
end
|
269
301
|
|
270
|
-
def environment_str
|
271
|
-
environment.respond_to?(:call) ? environment.call : environment
|
272
|
-
end
|
273
|
-
|
274
302
|
def load_plugin(name)
|
275
303
|
@plugins.create name
|
276
304
|
end
|
277
305
|
|
278
|
-
|
279
|
-
|
306
|
+
# @param key [:Symbol] hook to run
|
307
|
+
# @param arg [Launcher, Int] `:on_restart` passes Launcher
|
308
|
+
#
|
309
|
+
def run_hooks(key, arg, log_writer, hook_data = nil)
|
310
|
+
@options.all_of(key).each do |b|
|
311
|
+
begin
|
312
|
+
if Array === b
|
313
|
+
hook_data[b[1]] ||= Hash.new
|
314
|
+
b[0].call arg, hook_data[b[1]]
|
315
|
+
else
|
316
|
+
b.call arg
|
317
|
+
end
|
318
|
+
rescue => e
|
319
|
+
log_writer.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
|
320
|
+
log_writer.debug e.backtrace.join("\n")
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def final_options
|
326
|
+
@options.final_options
|
280
327
|
end
|
281
328
|
|
282
329
|
def self.temp_path
|
@@ -288,10 +335,6 @@ module Puma
|
|
288
335
|
|
289
336
|
private
|
290
337
|
|
291
|
-
def infer_tag
|
292
|
-
File.basename(Dir.getwd)
|
293
|
-
end
|
294
|
-
|
295
338
|
# Load and use the normal Rack builder if we can, otherwise
|
296
339
|
# fallback to our minimal version.
|
297
340
|
def rack_builder
|
@@ -319,6 +362,8 @@ module Puma
|
|
319
362
|
raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
|
320
363
|
|
321
364
|
rack_app, rack_options = rack_builder.parse_file(rackup)
|
365
|
+
rack_options = rack_options || {}
|
366
|
+
|
322
367
|
@options.file_options.merge!(rack_options)
|
323
368
|
|
324
369
|
config_ru_binds = []
|
@@ -332,31 +377,11 @@ module Puma
|
|
332
377
|
end
|
333
378
|
|
334
379
|
def self.random_token
|
335
|
-
|
336
|
-
require 'openssl'
|
337
|
-
rescue LoadError
|
338
|
-
end
|
339
|
-
|
340
|
-
count = 16
|
341
|
-
|
342
|
-
bytes = nil
|
343
|
-
|
344
|
-
if defined? OpenSSL::Random
|
345
|
-
bytes = OpenSSL::Random.random_bytes(count)
|
346
|
-
elsif File.exist?("/dev/urandom")
|
347
|
-
File.open('/dev/urandom') { |f| bytes = f.read(count) }
|
348
|
-
end
|
349
|
-
|
350
|
-
if bytes
|
351
|
-
token = "".dup
|
352
|
-
bytes.each_byte { |b| token << b.to_s(16) }
|
353
|
-
else
|
354
|
-
token = (0..count).to_a.map { rand(255).to_s(16) }.join
|
355
|
-
end
|
380
|
+
require 'securerandom' unless defined?(SecureRandom)
|
356
381
|
|
357
|
-
|
382
|
+
SecureRandom.hex(16)
|
358
383
|
end
|
359
384
|
end
|
360
385
|
end
|
361
386
|
|
362
|
-
|
387
|
+
require_relative 'dsl'
|
data/lib/puma/const.rb
CHANGED
@@ -100,31 +100,17 @@ module Puma
|
|
100
100
|
# too taxing on performance.
|
101
101
|
module Const
|
102
102
|
|
103
|
-
PUMA_VERSION = VERSION = "
|
104
|
-
CODE_NAME = "
|
103
|
+
PUMA_VERSION = VERSION = "6.0.0".freeze
|
104
|
+
CODE_NAME = "Sunflower".freeze
|
105
|
+
|
105
106
|
PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
|
106
107
|
|
107
108
|
FAST_TRACK_KA_TIMEOUT = 0.2
|
108
109
|
|
109
|
-
# The default number of seconds for another request within a persistent
|
110
|
-
# session.
|
111
|
-
PERSISTENT_TIMEOUT = 20
|
112
|
-
|
113
|
-
# The default number of seconds to wait until we get the first data
|
114
|
-
# for the request
|
115
|
-
FIRST_DATA_TIMEOUT = 30
|
116
|
-
|
117
110
|
# How long to wait when getting some write blocking on the socket when
|
118
111
|
# sending data back
|
119
112
|
WRITE_TIMEOUT = 10
|
120
113
|
|
121
|
-
# How many requests to attempt inline before sending a client back to
|
122
|
-
# the reactor to be subject to normal ordering. The idea here is that
|
123
|
-
# we amortize the cost of going back to the reactor for a well behaved
|
124
|
-
# but very "greedy" client across 10 requests. This prevents a not
|
125
|
-
# well behaved client from monopolizing the thread forever.
|
126
|
-
MAX_FAST_INLINE = 10
|
127
|
-
|
128
114
|
# The original URI requested by the client.
|
129
115
|
REQUEST_URI= 'REQUEST_URI'.freeze
|
130
116
|
REQUEST_PATH = 'REQUEST_PATH'.freeze
|
@@ -162,6 +148,14 @@ module Puma
|
|
162
148
|
|
163
149
|
REQUEST_METHOD = "REQUEST_METHOD".freeze
|
164
150
|
HEAD = "HEAD".freeze
|
151
|
+
GET = "GET".freeze
|
152
|
+
POST = "POST".freeze
|
153
|
+
PUT = "PUT".freeze
|
154
|
+
DELETE = "DELETE".freeze
|
155
|
+
OPTIONS = "OPTIONS".freeze
|
156
|
+
TRACE = "TRACE".freeze
|
157
|
+
PATCH = "PATCH".freeze
|
158
|
+
SUPPORTED_HTTP_METHODS = [HEAD, GET, POST, PUT, DELETE, OPTIONS, TRACE, PATCH].freeze
|
165
159
|
# ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
|
166
160
|
LINE_END = "\r\n".freeze
|
167
161
|
REMOTE_ADDR = "REMOTE_ADDR".freeze
|
@@ -176,8 +170,10 @@ module Puma
|
|
176
170
|
PORT_80 = "80".freeze
|
177
171
|
PORT_443 = "443".freeze
|
178
172
|
LOCALHOST = "localhost".freeze
|
179
|
-
|
180
|
-
|
173
|
+
LOCALHOST_IPV4 = "127.0.0.1".freeze
|
174
|
+
LOCALHOST_IPV6 = "::1".freeze
|
175
|
+
UNSPECIFIED_IPV4 = "0.0.0.0".freeze
|
176
|
+
UNSPECIFIED_IPV6 = "::".freeze
|
181
177
|
|
182
178
|
SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
|
183
179
|
HTTP_11 = "HTTP/1.1".freeze
|
@@ -230,7 +226,6 @@ module Puma
|
|
230
226
|
COLON = ": ".freeze
|
231
227
|
|
232
228
|
NEWLINE = "\n".freeze
|
233
|
-
HTTP_INJECTION_REGEX = /[\r\n]/.freeze
|
234
229
|
|
235
230
|
HIJACK_P = "rack.hijack?".freeze
|
236
231
|
HIJACK = "rack.hijack".freeze
|
@@ -238,8 +233,16 @@ module Puma
|
|
238
233
|
|
239
234
|
EARLY_HINTS = "rack.early_hints".freeze
|
240
235
|
|
241
|
-
#
|
242
|
-
|
236
|
+
# Illegal character in the key or value of response header
|
237
|
+
DQUOTE = "\"".freeze
|
238
|
+
HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
|
239
|
+
ILLEGAL_HEADER_KEY_REGEX = /[\x00-\x20#{DQUOTE}#{HTTP_HEADER_DELIMITER}]/.freeze
|
240
|
+
# header values can contain HTAB?
|
241
|
+
ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/.freeze
|
242
|
+
|
243
|
+
# Banned keys of response header
|
244
|
+
BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
|
243
245
|
|
246
|
+
PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
|
244
247
|
end
|
245
248
|
end
|
data/lib/puma/control_cli.rb
CHANGED
@@ -11,7 +11,33 @@ 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
|
+
'info' => 'SIGINFO',
|
22
|
+
'phased-restart' => 'SIGUSR1',
|
23
|
+
'refork' => 'SIGURG',
|
24
|
+
'reload-worker-directory' => nil,
|
25
|
+
'reopen-log' => 'SIGHUP',
|
26
|
+
'restart' => 'SIGUSR2',
|
27
|
+
'start' => nil,
|
28
|
+
'stats' => nil,
|
29
|
+
'status' => '',
|
30
|
+
'stop' => 'SIGTERM',
|
31
|
+
'thread-backtraces' => nil,
|
32
|
+
'worker-count-down' => 'SIGTTOU',
|
33
|
+
'worker-count-up' => 'SIGTTIN'
|
34
|
+
}.freeze
|
35
|
+
|
36
|
+
# commands that cannot be used in a request
|
37
|
+
NO_REQ_COMMANDS = %w[info reopen-log worker-count-down worker-count-up].freeze
|
38
|
+
|
39
|
+
# @version 5.0.0
|
40
|
+
PRINTABLE_COMMANDS = %w[gc-stats stats thread-backtraces].freeze
|
15
41
|
|
16
42
|
def initialize(argv, stdout=STDOUT, stderr=STDERR)
|
17
43
|
@state = nil
|
@@ -22,7 +48,7 @@ module Puma
|
|
22
48
|
@control_auth_token = nil
|
23
49
|
@config_file = nil
|
24
50
|
@command = nil
|
25
|
-
@environment = ENV['RACK_ENV']
|
51
|
+
@environment = ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']
|
26
52
|
|
27
53
|
@argv = argv.dup
|
28
54
|
@stdout = stdout
|
@@ -30,7 +56,7 @@ module Puma
|
|
30
56
|
@cli_options = {}
|
31
57
|
|
32
58
|
opts = OptionParser.new do |o|
|
33
|
-
o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{
|
59
|
+
o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{CMD_PATH_SIG_MAP.keys.join("|")})"
|
34
60
|
|
35
61
|
o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
|
36
62
|
@state = arg
|
@@ -71,7 +97,7 @@ module Puma
|
|
71
97
|
end
|
72
98
|
|
73
99
|
o.on_tail("-V", "--version", "Show version") do
|
74
|
-
puts Const::PUMA_VERSION
|
100
|
+
@stdout.puts Const::PUMA_VERSION
|
75
101
|
exit
|
76
102
|
end
|
77
103
|
end
|
@@ -81,6 +107,15 @@ module Puma
|
|
81
107
|
|
82
108
|
@command = argv.shift
|
83
109
|
|
110
|
+
# check presence of command
|
111
|
+
unless @command
|
112
|
+
raise "Available commands: #{CMD_PATH_SIG_MAP.keys.join(", ")}"
|
113
|
+
end
|
114
|
+
|
115
|
+
unless CMD_PATH_SIG_MAP.key? @command
|
116
|
+
raise "Invalid command: #{@command}"
|
117
|
+
end
|
118
|
+
|
84
119
|
unless @config_file == '-'
|
85
120
|
environment = @environment || 'development'
|
86
121
|
|
@@ -99,16 +134,6 @@ module Puma
|
|
99
134
|
@pidfile ||= config.options[:pidfile]
|
100
135
|
end
|
101
136
|
end
|
102
|
-
|
103
|
-
# check present of command
|
104
|
-
unless @command
|
105
|
-
raise "Available commands: #{COMMANDS.join(", ")}"
|
106
|
-
end
|
107
|
-
|
108
|
-
unless COMMANDS.include? @command
|
109
|
-
raise "Invalid command: #{@command}"
|
110
|
-
end
|
111
|
-
|
112
137
|
rescue => e
|
113
138
|
@stdout.puts e.message
|
114
139
|
exit 1
|
@@ -132,7 +157,7 @@ module Puma
|
|
132
157
|
@pid = sf.pid
|
133
158
|
elsif @pidfile
|
134
159
|
# get pid from pid_file
|
135
|
-
File.
|
160
|
+
@pid = File.read(@pidfile, mode: 'rb:UTF-8').to_i
|
136
161
|
end
|
137
162
|
end
|
138
163
|
|
@@ -140,23 +165,27 @@ module Puma
|
|
140
165
|
uri = URI.parse @control_url
|
141
166
|
|
142
167
|
# create server object by scheme
|
143
|
-
server =
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
168
|
+
server =
|
169
|
+
case uri.scheme
|
170
|
+
when 'ssl'
|
171
|
+
require 'openssl'
|
172
|
+
OpenSSL::SSL::SSLSocket.new(
|
173
|
+
TCPSocket.new(uri.host, uri.port),
|
174
|
+
OpenSSL::SSL::SSLContext.new)
|
175
|
+
.tap { |ssl| ssl.sync_close = true } # default is false
|
176
|
+
.tap(&:connect)
|
177
|
+
when 'tcp'
|
178
|
+
TCPSocket.new uri.host, uri.port
|
179
|
+
when 'unix'
|
180
|
+
# check for abstract UNIXSocket
|
181
|
+
UNIXSocket.new(@control_url.start_with?('unix://@') ?
|
182
|
+
"\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}")
|
183
|
+
else
|
184
|
+
raise "Invalid scheme: #{uri.scheme}"
|
185
|
+
end
|
186
|
+
|
187
|
+
if @command == 'status'
|
188
|
+
message 'Puma is started'
|
160
189
|
else
|
161
190
|
url = "/#{@command}"
|
162
191
|
|
@@ -164,10 +193,10 @@ module Puma
|
|
164
193
|
url = url + "?token=#{@control_auth_token}"
|
165
194
|
end
|
166
195
|
|
167
|
-
server
|
196
|
+
server.syswrite "GET #{url} HTTP/1.0\r\n\r\n"
|
168
197
|
|
169
198
|
unless data = server.read
|
170
|
-
raise
|
199
|
+
raise 'Server closed connection before responding'
|
171
200
|
end
|
172
201
|
|
173
202
|
response = data.split("\r\n")
|
@@ -176,67 +205,59 @@ module Puma
|
|
176
205
|
raise "Server sent empty response"
|
177
206
|
end
|
178
207
|
|
179
|
-
|
208
|
+
@http, @code, @message = response.first.split(' ',3)
|
180
209
|
|
181
|
-
if @code ==
|
182
|
-
raise
|
183
|
-
elsif @code ==
|
210
|
+
if @code == '403'
|
211
|
+
raise 'Unauthorized access to server (wrong auth token)'
|
212
|
+
elsif @code == '404'
|
184
213
|
raise "Command error: #{response.last}"
|
185
|
-
elsif @code !=
|
214
|
+
elsif @code != '200'
|
186
215
|
raise "Bad response from server: #{@code}"
|
187
216
|
end
|
188
217
|
|
189
218
|
message "Command #{@command} sent success"
|
190
|
-
message response.last if @command
|
219
|
+
message response.last if PRINTABLE_COMMANDS.include?(@command)
|
191
220
|
end
|
192
221
|
ensure
|
193
|
-
|
222
|
+
if server
|
223
|
+
if uri.scheme == 'ssl'
|
224
|
+
server.sysclose
|
225
|
+
else
|
226
|
+
server.close unless server.closed?
|
227
|
+
end
|
228
|
+
end
|
194
229
|
end
|
195
230
|
|
196
231
|
def send_signal
|
197
232
|
unless @pid
|
198
|
-
raise
|
233
|
+
raise 'Neither pid nor control url available'
|
199
234
|
end
|
200
235
|
|
201
236
|
begin
|
237
|
+
sig = CMD_PATH_SIG_MAP[@command]
|
202
238
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
when "halt"
|
208
|
-
Process.kill "QUIT", @pid
|
209
|
-
|
210
|
-
when "stop"
|
211
|
-
Process.kill "SIGTERM", @pid
|
212
|
-
|
213
|
-
when "stats"
|
214
|
-
puts "Stats not available via pid only"
|
215
|
-
return
|
216
|
-
|
217
|
-
when "reload-worker-directory"
|
218
|
-
puts "reload-worker-directory not available via pid only"
|
239
|
+
if sig.nil?
|
240
|
+
@stdout.puts "'#{@command}' not available via pid only"
|
241
|
+
@stdout.flush unless @stdout.sync
|
219
242
|
return
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
243
|
+
elsif sig.start_with? 'SIG'
|
244
|
+
if Signal.list.key? sig.sub(/\ASIG/, '')
|
245
|
+
Process.kill sig, @pid
|
246
|
+
else
|
247
|
+
raise "Signal '#{sig}' not available'"
|
248
|
+
end
|
249
|
+
elsif @command == 'status'
|
225
250
|
begin
|
226
251
|
Process.kill 0, @pid
|
227
|
-
puts
|
252
|
+
@stdout.puts 'Puma is started'
|
253
|
+
@stdout.flush unless @stdout.sync
|
228
254
|
rescue Errno::ESRCH
|
229
|
-
raise
|
255
|
+
raise 'Puma is not running'
|
230
256
|
end
|
231
|
-
|
232
|
-
return
|
233
|
-
|
234
|
-
else
|
235
257
|
return
|
236
258
|
end
|
237
|
-
|
238
259
|
rescue SystemCallError
|
239
|
-
if @command ==
|
260
|
+
if @command == 'restart'
|
240
261
|
start
|
241
262
|
else
|
242
263
|
raise "No pid '#{@pid}' found"
|
@@ -247,14 +268,13 @@ module Puma
|
|
247
268
|
end
|
248
269
|
|
249
270
|
def run
|
250
|
-
return start if @command ==
|
251
|
-
|
271
|
+
return start if @command == 'start'
|
252
272
|
prepare_configuration
|
253
273
|
|
254
|
-
if Puma.windows?
|
274
|
+
if Puma.windows? || @control_url && !NO_REQ_COMMANDS.include?(@command)
|
255
275
|
send_request
|
256
276
|
else
|
257
|
-
|
277
|
+
send_signal
|
258
278
|
end
|
259
279
|
|
260
280
|
rescue => e
|
@@ -262,9 +282,9 @@ module Puma
|
|
262
282
|
exit 1
|
263
283
|
end
|
264
284
|
|
265
|
-
|
285
|
+
private
|
266
286
|
def start
|
267
|
-
|
287
|
+
require_relative 'cli'
|
268
288
|
|
269
289
|
run_args = []
|
270
290
|
|
@@ -276,13 +296,13 @@ module Puma
|
|
276
296
|
run_args += ["-C", @config_file] if @config_file
|
277
297
|
run_args += ["-e", @environment] if @environment
|
278
298
|
|
279
|
-
|
299
|
+
log_writer = Puma::LogWriter.new(@stdout, @stderr)
|
280
300
|
|
281
301
|
# replace $0 because puma use it to generate restart command
|
282
302
|
puma_cmd = $0.gsub(/pumactl$/, 'puma')
|
283
303
|
$0 = puma_cmd if File.exist?(puma_cmd)
|
284
304
|
|
285
|
-
cli = Puma::CLI.new run_args,
|
305
|
+
cli = Puma::CLI.new run_args, log_writer
|
286
306
|
cli.run
|
287
307
|
end
|
288
308
|
end
|