puma 3.12.1 → 5.6.7
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 +1608 -447
- data/LICENSE +23 -20
- data/README.md +175 -63
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +59 -21
- data/docs/compile_options.md +21 -0
- data/docs/deployment.md +69 -58
- data/docs/fork_worker.md +33 -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/docs/kubernetes.md +66 -0
- data/docs/nginx.md +1 -1
- data/docs/plugins.md +22 -12
- data/docs/rails_dev_mode.md +28 -0
- data/docs/restart.md +47 -22
- data/docs/signals.md +13 -11
- data/docs/stats.md +142 -0
- data/docs/systemd.md +95 -120
- data/ext/puma_http11/PumaHttp11Service.java +2 -2
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +57 -2
- data/ext/puma_http11/http11_parser.c +105 -117
- data/ext/puma_http11/http11_parser.h +1 -1
- data/ext/puma_http11/http11_parser.java.rl +22 -38
- data/ext/puma_http11/http11_parser.rl +4 -2
- data/ext/puma_http11/http11_parser_common.rl +4 -4
- data/ext/puma_http11/mini_ssl.c +339 -98
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +124 -71
- data/ext/puma_http11/puma_http11.c +35 -51
- data/lib/puma/app/status.rb +71 -49
- data/lib/puma/binder.rb +234 -137
- data/lib/puma/cli.rb +28 -18
- data/lib/puma/client.rb +350 -230
- data/lib/puma/cluster/worker.rb +173 -0
- data/lib/puma/cluster/worker_handle.rb +94 -0
- data/lib/puma/cluster.rb +247 -232
- data/lib/puma/commonlogger.rb +2 -2
- data/lib/puma/configuration.rb +61 -51
- data/lib/puma/const.rb +42 -21
- data/lib/puma/control_cli.rb +115 -67
- data/lib/puma/detect.rb +29 -2
- data/lib/puma/dsl.rb +619 -123
- data/lib/puma/error_logger.rb +104 -0
- data/lib/puma/events.rb +55 -31
- data/lib/puma/io_buffer.rb +7 -5
- data/lib/puma/jruby_restart.rb +0 -58
- data/lib/puma/json_serialization.rb +96 -0
- data/lib/puma/launcher.rb +193 -69
- data/lib/puma/minissl/context_builder.rb +81 -0
- data/lib/puma/minissl.rb +170 -65
- data/lib/puma/null_io.rb +18 -1
- data/lib/puma/plugin/tmp_restart.rb +2 -0
- data/lib/puma/plugin.rb +7 -13
- data/lib/puma/queue_close.rb +26 -0
- data/lib/puma/rack/builder.rb +3 -5
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +2 -0
- data/lib/puma/reactor.rb +85 -316
- data/lib/puma/request.rb +476 -0
- data/lib/puma/runner.rb +48 -55
- data/lib/puma/server.rb +305 -695
- data/lib/puma/single.rb +11 -67
- data/lib/puma/state_file.rb +48 -8
- data/lib/puma/systemd.rb +46 -0
- data/lib/puma/thread_pool.rb +132 -82
- data/lib/puma/util.rb +33 -10
- data/lib/puma.rb +56 -0
- data/lib/rack/handler/puma.rb +5 -6
- data/lib/rack/version_restriction.rb +15 -0
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +46 -29
- data/ext/puma_http11/io_buffer.c +0 -155
- data/lib/puma/accept_nonblock.rb +0 -23
- data/lib/puma/compat.rb +0 -14
- data/lib/puma/convenient.rb +0 -25
- data/lib/puma/daemon_ext.rb +0 -33
- data/lib/puma/delegation.rb +0 -13
- data/lib/puma/java_io_buffer.rb +0 -47
- data/lib/puma/rack/backports/uri/common_193.rb +0 -33
- 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/{tools → docs}/jungle/rc.d/puma.conf +0 -0
data/lib/puma/configuration.rb
CHANGED
@@ -11,6 +11,7 @@ module Puma
|
|
11
11
|
|
12
12
|
DefaultTCPHost = "0.0.0.0"
|
13
13
|
DefaultTCPPort = 9292
|
14
|
+
DefaultWorkerCheckInterval = 5
|
14
15
|
DefaultWorkerTimeout = 60
|
15
16
|
DefaultWorkerShutdownTimeout = 30
|
16
17
|
end
|
@@ -20,7 +21,7 @@ module Puma
|
|
20
21
|
# In this class any "user" specified options take precedence over any
|
21
22
|
# "file" specified options, take precedence over any "default" options.
|
22
23
|
#
|
23
|
-
# User input is
|
24
|
+
# User input is preferred over "defaults":
|
24
25
|
# user_options = { foo: "bar" }
|
25
26
|
# default_options = { foo: "zoo" }
|
26
27
|
# options = UserFileDefaultOptions.new(user_options, default_options)
|
@@ -32,7 +33,7 @@ module Puma
|
|
32
33
|
# puts options.all_of(:foo)
|
33
34
|
# # => ["bar", "zoo"]
|
34
35
|
#
|
35
|
-
# A "file" option can be set. This config will be
|
36
|
+
# A "file" option can be set. This config will be preferred over "default" options
|
36
37
|
# but will defer to any available "user" specified options.
|
37
38
|
#
|
38
39
|
# user_options = { foo: "bar" }
|
@@ -54,9 +55,7 @@ module Puma
|
|
54
55
|
attr_reader :user_options, :file_options, :default_options
|
55
56
|
|
56
57
|
def [](key)
|
57
|
-
|
58
|
-
return file_options[key] if file_options.key?(key)
|
59
|
-
return default_options[key] if default_options.key?(key)
|
58
|
+
fetch(key)
|
60
59
|
end
|
61
60
|
|
62
61
|
def []=(key, value)
|
@@ -64,7 +63,11 @@ module Puma
|
|
64
63
|
end
|
65
64
|
|
66
65
|
def fetch(key, default_value = nil)
|
67
|
-
|
66
|
+
return user_options[key] if user_options.key?(key)
|
67
|
+
return file_options[key] if file_options.key?(key)
|
68
|
+
return default_options[key] if default_options.key?(key)
|
69
|
+
|
70
|
+
default_value
|
68
71
|
end
|
69
72
|
|
70
73
|
def all_of(key)
|
@@ -90,6 +93,12 @@ module Puma
|
|
90
93
|
end
|
91
94
|
end
|
92
95
|
end
|
96
|
+
|
97
|
+
def final_options
|
98
|
+
default_options
|
99
|
+
.merge(file_options)
|
100
|
+
.merge(user_options)
|
101
|
+
end
|
93
102
|
end
|
94
103
|
|
95
104
|
# The main configuration class of Puma.
|
@@ -106,16 +115,17 @@ module Puma
|
|
106
115
|
#
|
107
116
|
# It also handles loading plugins.
|
108
117
|
#
|
109
|
-
#
|
118
|
+
# [Note:]
|
119
|
+
# `:port` and `:host` are not valid keys. By the time they make it to the
|
110
120
|
# configuration options they are expected to be incorporated into a `:binds` key.
|
111
121
|
# Under the hood the DSL maps `port` and `host` calls to `:binds`
|
112
122
|
#
|
113
|
-
#
|
114
|
-
#
|
115
|
-
#
|
116
|
-
#
|
117
|
-
#
|
118
|
-
#
|
123
|
+
# config = Configuration.new({}) do |user_config, file_config, default_config|
|
124
|
+
# user_config.port 3003
|
125
|
+
# end
|
126
|
+
# config.load
|
127
|
+
# puts config.options[:port]
|
128
|
+
# # => 3003
|
119
129
|
#
|
120
130
|
# It is expected that `load` is called on the configuration instance after setting
|
121
131
|
# config. This method expands any values in `config_file` and puts them into the
|
@@ -137,6 +147,10 @@ module Puma
|
|
137
147
|
@file_dsl = DSL.new(@options.file_options, self)
|
138
148
|
@default_dsl = DSL.new(@options.default_options, self)
|
139
149
|
|
150
|
+
if !@options[:prune_bundler]
|
151
|
+
default_options[:preload_app] = (@options[:workers] > 1) && Puma.forkable?
|
152
|
+
end
|
153
|
+
|
140
154
|
if block
|
141
155
|
configure(&block)
|
142
156
|
end
|
@@ -167,26 +181,37 @@ module Puma
|
|
167
181
|
self
|
168
182
|
end
|
169
183
|
|
184
|
+
# @version 5.0.0
|
185
|
+
def default_max_threads
|
186
|
+
Puma.mri? ? 5 : 16
|
187
|
+
end
|
188
|
+
|
170
189
|
def puma_default_options
|
171
190
|
{
|
172
|
-
:min_threads => 0,
|
173
|
-
:max_threads =>
|
191
|
+
:min_threads => Integer(ENV['PUMA_MIN_THREADS'] || ENV['MIN_THREADS'] || 0),
|
192
|
+
:max_threads => Integer(ENV['PUMA_MAX_THREADS'] || ENV['MAX_THREADS'] || default_max_threads),
|
174
193
|
:log_requests => false,
|
175
194
|
:debug => false,
|
176
195
|
:binds => ["tcp://#{DefaultTCPHost}:#{DefaultTCPPort}"],
|
177
|
-
:workers => 0,
|
178
|
-
:
|
196
|
+
:workers => Integer(ENV['WEB_CONCURRENCY'] || 0),
|
197
|
+
:silence_single_worker_warning => false,
|
179
198
|
:mode => :http,
|
199
|
+
:worker_check_interval => DefaultWorkerCheckInterval,
|
180
200
|
:worker_timeout => DefaultWorkerTimeout,
|
181
201
|
:worker_boot_timeout => DefaultWorkerTimeout,
|
182
202
|
:worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
|
203
|
+
:worker_culling_strategy => :youngest,
|
183
204
|
:remote_address => :socket,
|
184
205
|
:tag => method(:infer_tag),
|
185
|
-
:environment => -> { ENV['RACK_ENV'] ||
|
206
|
+
:environment => -> { ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development' },
|
186
207
|
:rackup => DefaultRackup,
|
187
208
|
:logger => STDOUT,
|
188
209
|
:persistent_timeout => Const::PERSISTENT_TIMEOUT,
|
189
|
-
:first_data_timeout => Const::FIRST_DATA_TIMEOUT
|
210
|
+
:first_data_timeout => Const::FIRST_DATA_TIMEOUT,
|
211
|
+
:raise_exception_on_sigterm => true,
|
212
|
+
:max_fast_inline => Const::MAX_FAST_INLINE,
|
213
|
+
:io_selector_backend => :auto,
|
214
|
+
:mutate_stdout_and_stderr_to_sync_on_write => true,
|
190
215
|
}
|
191
216
|
end
|
192
217
|
|
@@ -244,14 +269,6 @@ module Puma
|
|
244
269
|
def app
|
245
270
|
found = options[:app] || load_rackup
|
246
271
|
|
247
|
-
if @options[:mode] == :tcp
|
248
|
-
require 'puma/tcp_logger'
|
249
|
-
|
250
|
-
logger = @options[:logger]
|
251
|
-
quiet = !@options[:log_requests]
|
252
|
-
return TCPLogger.new(logger, found, quiet)
|
253
|
-
end
|
254
|
-
|
255
272
|
if @options[:log_requests]
|
256
273
|
require 'puma/commonlogger'
|
257
274
|
logger = @options[:logger]
|
@@ -274,8 +291,19 @@ module Puma
|
|
274
291
|
@plugins.create name
|
275
292
|
end
|
276
293
|
|
277
|
-
def run_hooks(key, arg)
|
278
|
-
@options.all_of(key).each
|
294
|
+
def run_hooks(key, arg, events)
|
295
|
+
@options.all_of(key).each do |b|
|
296
|
+
begin
|
297
|
+
b.call arg
|
298
|
+
rescue => e
|
299
|
+
events.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
|
300
|
+
events.debug e.backtrace.join("\n")
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def final_options
|
306
|
+
@options.final_options
|
279
307
|
end
|
280
308
|
|
281
309
|
def self.temp_path
|
@@ -318,6 +346,8 @@ module Puma
|
|
318
346
|
raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
|
319
347
|
|
320
348
|
rack_app, rack_options = rack_builder.parse_file(rackup)
|
349
|
+
rack_options = rack_options || {}
|
350
|
+
|
321
351
|
@options.file_options.merge!(rack_options)
|
322
352
|
|
323
353
|
config_ru_binds = []
|
@@ -331,29 +361,9 @@ module Puma
|
|
331
361
|
end
|
332
362
|
|
333
363
|
def self.random_token
|
334
|
-
|
335
|
-
require 'openssl'
|
336
|
-
rescue LoadError
|
337
|
-
end
|
338
|
-
|
339
|
-
count = 16
|
340
|
-
|
341
|
-
bytes = nil
|
342
|
-
|
343
|
-
if defined? OpenSSL::Random
|
344
|
-
bytes = OpenSSL::Random.random_bytes(count)
|
345
|
-
elsif File.exist?("/dev/urandom")
|
346
|
-
File.open('/dev/urandom') { |f| bytes = f.read(count) }
|
347
|
-
end
|
348
|
-
|
349
|
-
if bytes
|
350
|
-
token = "".dup
|
351
|
-
bytes.each_byte { |b| token << b.to_s(16) }
|
352
|
-
else
|
353
|
-
token = (0..count).to_a.map { rand(255).to_s(16) }.join
|
354
|
-
end
|
364
|
+
require 'securerandom' unless defined?(SecureRandom)
|
355
365
|
|
356
|
-
|
366
|
+
SecureRandom.hex(16)
|
357
367
|
end
|
358
368
|
end
|
359
369
|
end
|
data/lib/puma/const.rb
CHANGED
@@ -76,7 +76,7 @@ module Puma
|
|
76
76
|
508 => 'Loop Detected',
|
77
77
|
510 => 'Not Extended',
|
78
78
|
511 => 'Network Authentication Required'
|
79
|
-
}
|
79
|
+
}.freeze
|
80
80
|
|
81
81
|
# For some HTTP status codes the client only expects headers.
|
82
82
|
#
|
@@ -85,7 +85,7 @@ module Puma
|
|
85
85
|
204 => true,
|
86
86
|
205 => true,
|
87
87
|
304 => true
|
88
|
-
}
|
88
|
+
}.freeze
|
89
89
|
|
90
90
|
# Frequently used constants when constructing requests or responses. Many times
|
91
91
|
# the constant just refers to a string with the same contents. Using these constants
|
@@ -100,8 +100,9 @@ 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 = "5.6.7".freeze
|
104
|
+
CODE_NAME = "Birdie's Version".freeze
|
105
|
+
|
105
106
|
PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
|
106
107
|
|
107
108
|
FAST_TRACK_KA_TIMEOUT = 0.2
|
@@ -118,31 +119,37 @@ module Puma
|
|
118
119
|
# sending data back
|
119
120
|
WRITE_TIMEOUT = 10
|
120
121
|
|
122
|
+
# How many requests to attempt inline before sending a client back to
|
123
|
+
# the reactor to be subject to normal ordering. The idea here is that
|
124
|
+
# we amortize the cost of going back to the reactor for a well behaved
|
125
|
+
# but very "greedy" client across 10 requests. This prevents a not
|
126
|
+
# well behaved client from monopolizing the thread forever.
|
127
|
+
MAX_FAST_INLINE = 10
|
128
|
+
|
121
129
|
# The original URI requested by the client.
|
122
130
|
REQUEST_URI= 'REQUEST_URI'.freeze
|
123
131
|
REQUEST_PATH = 'REQUEST_PATH'.freeze
|
124
132
|
QUERY_STRING = 'QUERY_STRING'.freeze
|
133
|
+
CONTENT_LENGTH = "CONTENT_LENGTH".freeze
|
125
134
|
|
126
135
|
PATH_INFO = 'PATH_INFO'.freeze
|
127
136
|
|
128
137
|
PUMA_TMP_BASE = "puma".freeze
|
129
138
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
# A common header for indicating the server is too busy. Not used yet.
|
145
|
-
ERROR_503_RESPONSE = "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
|
139
|
+
ERROR_RESPONSE = {
|
140
|
+
# Indicate that we couldn't parse the request
|
141
|
+
400 => "HTTP/1.1 400 Bad Request\r\n\r\n".freeze,
|
142
|
+
# The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
|
143
|
+
404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze,
|
144
|
+
# The standard empty 408 response for requests that timed out.
|
145
|
+
408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
|
146
|
+
# Indicate that there was an internal error, obviously.
|
147
|
+
500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
|
148
|
+
# Incorrect or invalid header value
|
149
|
+
501 => "HTTP/1.1 501 Not Implemented\r\n\r\n".freeze,
|
150
|
+
# A common header for indicating the server is too busy. Not used yet.
|
151
|
+
503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
|
152
|
+
}.freeze
|
146
153
|
|
147
154
|
# The basic max request size we'll try to read.
|
148
155
|
CHUNK_SIZE = 16 * 1024
|
@@ -160,6 +167,9 @@ module Puma
|
|
160
167
|
LINE_END = "\r\n".freeze
|
161
168
|
REMOTE_ADDR = "REMOTE_ADDR".freeze
|
162
169
|
HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR".freeze
|
170
|
+
HTTP_X_FORWARDED_SSL = "HTTP_X_FORWARDED_SSL".freeze
|
171
|
+
HTTP_X_FORWARDED_SCHEME = "HTTP_X_FORWARDED_SCHEME".freeze
|
172
|
+
HTTP_X_FORWARDED_PROTO = "HTTP_X_FORWARDED_PROTO".freeze
|
163
173
|
|
164
174
|
SERVER_NAME = "SERVER_NAME".freeze
|
165
175
|
SERVER_PORT = "SERVER_PORT".freeze
|
@@ -168,7 +178,6 @@ module Puma
|
|
168
178
|
PORT_443 = "443".freeze
|
169
179
|
LOCALHOST = "localhost".freeze
|
170
180
|
LOCALHOST_IP = "127.0.0.1".freeze
|
171
|
-
LOCALHOST_ADDR = "127.0.0.1:0".freeze
|
172
181
|
|
173
182
|
SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
|
174
183
|
HTTP_11 = "HTTP/1.1".freeze
|
@@ -227,5 +236,17 @@ module Puma
|
|
227
236
|
HIJACK_IO = "rack.hijack_io".freeze
|
228
237
|
|
229
238
|
EARLY_HINTS = "rack.early_hints".freeze
|
239
|
+
|
240
|
+
# Illegal character in the key or value of response header
|
241
|
+
DQUOTE = "\"".freeze
|
242
|
+
HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
|
243
|
+
ILLEGAL_HEADER_KEY_REGEX = /[\x00-\x20#{DQUOTE}#{HTTP_HEADER_DELIMITER}]/.freeze
|
244
|
+
# header values can contain HTAB?
|
245
|
+
ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/.freeze
|
246
|
+
|
247
|
+
# Banned keys of response header
|
248
|
+
BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
|
249
|
+
|
250
|
+
PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
|
230
251
|
end
|
231
252
|
end
|
data/lib/puma/control_cli.rb
CHANGED
@@ -11,7 +11,36 @@ 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
|
+
# @deprecated 6.0.0
|
37
|
+
COMMANDS = CMD_PATH_SIG_MAP.keys.freeze
|
38
|
+
|
39
|
+
# commands that cannot be used in a request
|
40
|
+
NO_REQ_COMMANDS = %w[info reopen-log worker-count-down worker-count-up].freeze
|
41
|
+
|
42
|
+
# @version 5.0.0
|
43
|
+
PRINTABLE_COMMANDS = %w[gc-stats stats thread-backtraces].freeze
|
15
44
|
|
16
45
|
def initialize(argv, stdout=STDOUT, stderr=STDERR)
|
17
46
|
@state = nil
|
@@ -22,6 +51,7 @@ module Puma
|
|
22
51
|
@control_auth_token = nil
|
23
52
|
@config_file = nil
|
24
53
|
@command = nil
|
54
|
+
@environment = ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']
|
25
55
|
|
26
56
|
@argv = argv.dup
|
27
57
|
@stdout = stdout
|
@@ -29,7 +59,7 @@ module Puma
|
|
29
59
|
@cli_options = {}
|
30
60
|
|
31
61
|
opts = OptionParser.new do |o|
|
32
|
-
o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{
|
62
|
+
o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{CMD_PATH_SIG_MAP.keys.join("|")})"
|
33
63
|
|
34
64
|
o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
|
35
65
|
@state = arg
|
@@ -59,13 +89,18 @@ module Puma
|
|
59
89
|
@config_file = arg
|
60
90
|
end
|
61
91
|
|
92
|
+
o.on "-e", "--environment ENVIRONMENT",
|
93
|
+
"The environment to run the Rack app on (default development)" do |arg|
|
94
|
+
@environment = arg
|
95
|
+
end
|
96
|
+
|
62
97
|
o.on_tail("-H", "--help", "Show this message") do
|
63
98
|
@stdout.puts o
|
64
99
|
exit
|
65
100
|
end
|
66
101
|
|
67
102
|
o.on_tail("-V", "--version", "Show version") do
|
68
|
-
puts Const::PUMA_VERSION
|
103
|
+
@stdout.puts Const::PUMA_VERSION
|
69
104
|
exit
|
70
105
|
end
|
71
106
|
end
|
@@ -75,9 +110,22 @@ module Puma
|
|
75
110
|
|
76
111
|
@command = argv.shift
|
77
112
|
|
113
|
+
# check presence of command
|
114
|
+
unless @command
|
115
|
+
raise "Available commands: #{CMD_PATH_SIG_MAP.keys.join(", ")}"
|
116
|
+
end
|
117
|
+
|
118
|
+
unless CMD_PATH_SIG_MAP.key? @command
|
119
|
+
raise "Invalid command: #{@command}"
|
120
|
+
end
|
121
|
+
|
78
122
|
unless @config_file == '-'
|
79
|
-
|
80
|
-
|
123
|
+
environment = @environment || 'development'
|
124
|
+
|
125
|
+
if @config_file.nil?
|
126
|
+
@config_file = %W(config/puma/#{environment}.rb config/puma.rb).find do |f|
|
127
|
+
File.exist?(f)
|
128
|
+
end
|
81
129
|
end
|
82
130
|
|
83
131
|
if @config_file
|
@@ -89,19 +137,8 @@ module Puma
|
|
89
137
|
@pidfile ||= config.options[:pidfile]
|
90
138
|
end
|
91
139
|
end
|
92
|
-
|
93
|
-
# check present of command
|
94
|
-
unless @command
|
95
|
-
raise "Available commands: #{COMMANDS.join(", ")}"
|
96
|
-
end
|
97
|
-
|
98
|
-
unless COMMANDS.include? @command
|
99
|
-
raise "Invalid command: #{@command}"
|
100
|
-
end
|
101
|
-
|
102
140
|
rescue => e
|
103
141
|
@stdout.puts e.message
|
104
|
-
@stdout.puts e.backtrace
|
105
142
|
exit 1
|
106
143
|
end
|
107
144
|
|
@@ -123,7 +160,7 @@ module Puma
|
|
123
160
|
@pid = sf.pid
|
124
161
|
elsif @pidfile
|
125
162
|
# get pid from pid_file
|
126
|
-
@pid = File.
|
163
|
+
@pid = File.read(@pidfile, mode: 'rb:UTF-8').to_i
|
127
164
|
end
|
128
165
|
end
|
129
166
|
|
@@ -131,17 +168,27 @@ module Puma
|
|
131
168
|
uri = URI.parse @control_url
|
132
169
|
|
133
170
|
# create server object by scheme
|
134
|
-
server =
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
171
|
+
server =
|
172
|
+
case uri.scheme
|
173
|
+
when 'ssl'
|
174
|
+
require 'openssl'
|
175
|
+
OpenSSL::SSL::SSLSocket.new(
|
176
|
+
TCPSocket.new(uri.host, uri.port),
|
177
|
+
OpenSSL::SSL::SSLContext.new)
|
178
|
+
.tap { |ssl| ssl.sync_close = true } # default is false
|
179
|
+
.tap(&:connect)
|
180
|
+
when 'tcp'
|
181
|
+
TCPSocket.new uri.host, uri.port
|
182
|
+
when 'unix'
|
183
|
+
# check for abstract UNIXSocket
|
184
|
+
UNIXSocket.new(@control_url.start_with?('unix://@') ?
|
185
|
+
"\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}")
|
186
|
+
else
|
187
|
+
raise "Invalid scheme: #{uri.scheme}"
|
188
|
+
end
|
189
|
+
|
190
|
+
if @command == 'status'
|
191
|
+
message 'Puma is started'
|
145
192
|
else
|
146
193
|
url = "/#{@command}"
|
147
194
|
|
@@ -149,10 +196,10 @@ module Puma
|
|
149
196
|
url = url + "?token=#{@control_auth_token}"
|
150
197
|
end
|
151
198
|
|
152
|
-
server
|
199
|
+
server.syswrite "GET #{url} HTTP/1.0\r\n\r\n"
|
153
200
|
|
154
201
|
unless data = server.read
|
155
|
-
raise
|
202
|
+
raise 'Server closed connection before responding'
|
156
203
|
end
|
157
204
|
|
158
205
|
response = data.split("\r\n")
|
@@ -161,57 +208,59 @@ module Puma
|
|
161
208
|
raise "Server sent empty response"
|
162
209
|
end
|
163
210
|
|
164
|
-
|
211
|
+
@http, @code, @message = response.first.split(' ',3)
|
165
212
|
|
166
|
-
if @code ==
|
167
|
-
raise
|
168
|
-
elsif @code ==
|
213
|
+
if @code == '403'
|
214
|
+
raise 'Unauthorized access to server (wrong auth token)'
|
215
|
+
elsif @code == '404'
|
169
216
|
raise "Command error: #{response.last}"
|
170
|
-
elsif @code !=
|
217
|
+
elsif @code != '200'
|
171
218
|
raise "Bad response from server: #{@code}"
|
172
219
|
end
|
173
220
|
|
174
221
|
message "Command #{@command} sent success"
|
175
|
-
message response.last if @command
|
222
|
+
message response.last if PRINTABLE_COMMANDS.include?(@command)
|
176
223
|
end
|
177
224
|
ensure
|
178
|
-
|
225
|
+
if server
|
226
|
+
if uri.scheme == 'ssl'
|
227
|
+
server.sysclose
|
228
|
+
else
|
229
|
+
server.close unless server.closed?
|
230
|
+
end
|
231
|
+
end
|
179
232
|
end
|
180
233
|
|
181
234
|
def send_signal
|
182
235
|
unless @pid
|
183
|
-
raise
|
236
|
+
raise 'Neither pid nor control url available'
|
184
237
|
end
|
185
238
|
|
186
239
|
begin
|
240
|
+
sig = CMD_PATH_SIG_MAP[@command]
|
187
241
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
when "halt"
|
193
|
-
Process.kill "QUIT", @pid
|
194
|
-
|
195
|
-
when "stop"
|
196
|
-
Process.kill "SIGTERM", @pid
|
197
|
-
|
198
|
-
when "stats"
|
199
|
-
puts "Stats not available via pid only"
|
200
|
-
return
|
201
|
-
|
202
|
-
when "reload-worker-directory"
|
203
|
-
puts "reload-worker-directory not available via pid only"
|
242
|
+
if sig.nil?
|
243
|
+
@stdout.puts "'#{@command}' not available via pid only"
|
244
|
+
@stdout.flush unless @stdout.sync
|
204
245
|
return
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
246
|
+
elsif sig.start_with? 'SIG'
|
247
|
+
if Signal.list.key? sig.sub(/\ASIG/, '')
|
248
|
+
Process.kill sig, @pid
|
249
|
+
else
|
250
|
+
raise "Signal '#{sig}' not available'"
|
251
|
+
end
|
252
|
+
elsif @command == 'status'
|
253
|
+
begin
|
254
|
+
Process.kill 0, @pid
|
255
|
+
@stdout.puts 'Puma is started'
|
256
|
+
@stdout.flush unless @stdout.sync
|
257
|
+
rescue Errno::ESRCH
|
258
|
+
raise 'Puma is not running'
|
259
|
+
end
|
210
260
|
return
|
211
261
|
end
|
212
|
-
|
213
262
|
rescue SystemCallError
|
214
|
-
if @command ==
|
263
|
+
if @command == 'restart'
|
215
264
|
start
|
216
265
|
else
|
217
266
|
raise "No pid '#{@pid}' found"
|
@@ -222,23 +271,21 @@ module Puma
|
|
222
271
|
end
|
223
272
|
|
224
273
|
def run
|
225
|
-
return start if @command ==
|
226
|
-
|
274
|
+
return start if @command == 'start'
|
227
275
|
prepare_configuration
|
228
276
|
|
229
|
-
if Puma.windows?
|
277
|
+
if Puma.windows? || @control_url && !NO_REQ_COMMANDS.include?(@command)
|
230
278
|
send_request
|
231
279
|
else
|
232
|
-
|
280
|
+
send_signal
|
233
281
|
end
|
234
282
|
|
235
283
|
rescue => e
|
236
284
|
message e.message
|
237
|
-
message e.backtrace
|
238
285
|
exit 1
|
239
286
|
end
|
240
287
|
|
241
|
-
|
288
|
+
private
|
242
289
|
def start
|
243
290
|
require 'puma/cli'
|
244
291
|
|
@@ -250,6 +297,7 @@ module Puma
|
|
250
297
|
run_args += ["--control-url", @control_url] if @control_url
|
251
298
|
run_args += ["--control-token", @control_auth_token] if @control_auth_token
|
252
299
|
run_args += ["-C", @config_file] if @config_file
|
300
|
+
run_args += ["-e", @environment] if @environment
|
253
301
|
|
254
302
|
events = Puma::Events.new @stdout, @stderr
|
255
303
|
|
data/lib/puma/detect.rb
CHANGED
@@ -1,15 +1,42 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# This file can be loaded independently of puma.rb, so it cannot have any code
|
4
|
+
# that assumes puma.rb is loaded.
|
5
|
+
|
6
|
+
|
3
7
|
module Puma
|
4
|
-
|
8
|
+
# @version 5.2.1
|
9
|
+
HAS_FORK = ::Process.respond_to? :fork
|
10
|
+
|
11
|
+
IS_JRUBY = Object.const_defined? :JRUBY_VERSION
|
12
|
+
|
13
|
+
IS_OSX = RUBY_PLATFORM.include? 'darwin'
|
14
|
+
|
15
|
+
IS_WINDOWS = !!(RUBY_PLATFORM =~ /mswin|ming|cygwin/) ||
|
16
|
+
IS_JRUBY && RUBY_DESCRIPTION.include?('mswin')
|
17
|
+
|
18
|
+
# @version 5.2.0
|
19
|
+
IS_MRI = (RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?)
|
5
20
|
|
6
21
|
def self.jruby?
|
7
22
|
IS_JRUBY
|
8
23
|
end
|
9
24
|
|
10
|
-
|
25
|
+
def self.osx?
|
26
|
+
IS_OSX
|
27
|
+
end
|
11
28
|
|
12
29
|
def self.windows?
|
13
30
|
IS_WINDOWS
|
14
31
|
end
|
32
|
+
|
33
|
+
# @version 5.0.0
|
34
|
+
def self.mri?
|
35
|
+
IS_MRI
|
36
|
+
end
|
37
|
+
|
38
|
+
# @version 5.0.0
|
39
|
+
def self.forkable?
|
40
|
+
HAS_FORK
|
41
|
+
end
|
15
42
|
end
|