puma 3.12.1 → 5.6.4
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 +1553 -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/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -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 +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 +51 -1
- 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 +319 -96
- 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 +120 -65
- data/ext/puma_http11/puma_http11.c +35 -51
- data/lib/puma/app/status.rb +68 -49
- data/lib/puma/binder.rb +234 -137
- data/lib/puma/cli.rb +28 -18
- data/lib/puma/client.rb +343 -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 +109 -67
- data/lib/puma/detect.rb +29 -2
- data/lib/puma/dsl.rb +615 -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 +182 -69
- data/lib/puma/minissl/context_builder.rb +81 -0
- data/lib/puma/minissl.rb +161 -61
- data/lib/puma/null_io.rb +13 -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 +472 -0
- data/lib/puma/runner.rb +48 -55
- data/lib/puma/server.rb +303 -695
- data/lib/puma/single.rb +11 -67
- data/lib/puma/state_file.rb +47 -8
- data/lib/puma/systemd.rb +46 -0
- data/lib/puma/thread_pool.rb +132 -82
- data/lib/puma/util.rb +21 -7
- data/lib/puma.rb +54 -0
- data/lib/rack/handler/puma.rb +5 -6
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +45 -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/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.4".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,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
|
37
|
+
|
38
|
+
# @version 5.0.0
|
39
|
+
PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}.freeze
|
15
40
|
|
16
41
|
def initialize(argv, stdout=STDOUT, stderr=STDERR)
|
17
42
|
@state = nil
|
@@ -22,6 +47,7 @@ module Puma
|
|
22
47
|
@control_auth_token = nil
|
23
48
|
@config_file = nil
|
24
49
|
@command = nil
|
50
|
+
@environment = ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']
|
25
51
|
|
26
52
|
@argv = argv.dup
|
27
53
|
@stdout = stdout
|
@@ -29,7 +55,7 @@ module Puma
|
|
29
55
|
@cli_options = {}
|
30
56
|
|
31
57
|
opts = OptionParser.new do |o|
|
32
|
-
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("|")})"
|
33
59
|
|
34
60
|
o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
|
35
61
|
@state = arg
|
@@ -59,13 +85,18 @@ module Puma
|
|
59
85
|
@config_file = arg
|
60
86
|
end
|
61
87
|
|
88
|
+
o.on "-e", "--environment ENVIRONMENT",
|
89
|
+
"The environment to run the Rack app on (default development)" do |arg|
|
90
|
+
@environment = arg
|
91
|
+
end
|
92
|
+
|
62
93
|
o.on_tail("-H", "--help", "Show this message") do
|
63
94
|
@stdout.puts o
|
64
95
|
exit
|
65
96
|
end
|
66
97
|
|
67
98
|
o.on_tail("-V", "--version", "Show version") do
|
68
|
-
puts Const::PUMA_VERSION
|
99
|
+
@stdout.puts Const::PUMA_VERSION
|
69
100
|
exit
|
70
101
|
end
|
71
102
|
end
|
@@ -75,9 +106,22 @@ module Puma
|
|
75
106
|
|
76
107
|
@command = argv.shift
|
77
108
|
|
109
|
+
# check presence of command
|
110
|
+
unless @command
|
111
|
+
raise "Available commands: #{CMD_PATH_SIG_MAP.keys.join(", ")}"
|
112
|
+
end
|
113
|
+
|
114
|
+
unless CMD_PATH_SIG_MAP.key? @command
|
115
|
+
raise "Invalid command: #{@command}"
|
116
|
+
end
|
117
|
+
|
78
118
|
unless @config_file == '-'
|
79
|
-
|
80
|
-
|
119
|
+
environment = @environment || 'development'
|
120
|
+
|
121
|
+
if @config_file.nil?
|
122
|
+
@config_file = %W(config/puma/#{environment}.rb config/puma.rb).find do |f|
|
123
|
+
File.exist?(f)
|
124
|
+
end
|
81
125
|
end
|
82
126
|
|
83
127
|
if @config_file
|
@@ -89,19 +133,8 @@ module Puma
|
|
89
133
|
@pidfile ||= config.options[:pidfile]
|
90
134
|
end
|
91
135
|
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
136
|
rescue => e
|
103
137
|
@stdout.puts e.message
|
104
|
-
@stdout.puts e.backtrace
|
105
138
|
exit 1
|
106
139
|
end
|
107
140
|
|
@@ -123,7 +156,7 @@ module Puma
|
|
123
156
|
@pid = sf.pid
|
124
157
|
elsif @pidfile
|
125
158
|
# get pid from pid_file
|
126
|
-
@pid = File.
|
159
|
+
@pid = File.read(@pidfile, mode: 'rb:UTF-8').to_i
|
127
160
|
end
|
128
161
|
end
|
129
162
|
|
@@ -131,17 +164,29 @@ module Puma
|
|
131
164
|
uri = URI.parse @control_url
|
132
165
|
|
133
166
|
# create server object by scheme
|
134
|
-
server =
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|
+
# check for abstract UNIXSocket
|
180
|
+
UNIXSocket.new(@control_url.start_with?('unix://@') ?
|
181
|
+
"\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}")
|
182
|
+
else
|
183
|
+
raise "Invalid scheme: #{uri.scheme}"
|
184
|
+
end
|
185
|
+
|
186
|
+
if @command == 'status'
|
187
|
+
message 'Puma is started'
|
188
|
+
elsif NO_REQ_COMMANDS.include? @command
|
189
|
+
raise "Invalid request command: #{@command}"
|
145
190
|
else
|
146
191
|
url = "/#{@command}"
|
147
192
|
|
@@ -149,10 +194,10 @@ module Puma
|
|
149
194
|
url = url + "?token=#{@control_auth_token}"
|
150
195
|
end
|
151
196
|
|
152
|
-
server
|
197
|
+
server.syswrite "GET #{url} HTTP/1.0\r\n\r\n"
|
153
198
|
|
154
199
|
unless data = server.read
|
155
|
-
raise
|
200
|
+
raise 'Server closed connection before responding'
|
156
201
|
end
|
157
202
|
|
158
203
|
response = data.split("\r\n")
|
@@ -161,57 +206,55 @@ module Puma
|
|
161
206
|
raise "Server sent empty response"
|
162
207
|
end
|
163
208
|
|
164
|
-
|
209
|
+
@http, @code, @message = response.first.split(' ',3)
|
165
210
|
|
166
|
-
if @code ==
|
167
|
-
raise
|
168
|
-
elsif @code ==
|
211
|
+
if @code == '403'
|
212
|
+
raise 'Unauthorized access to server (wrong auth token)'
|
213
|
+
elsif @code == '404'
|
169
214
|
raise "Command error: #{response.last}"
|
170
|
-
elsif @code !=
|
215
|
+
elsif @code != '200'
|
171
216
|
raise "Bad response from server: #{@code}"
|
172
217
|
end
|
173
218
|
|
174
219
|
message "Command #{@command} sent success"
|
175
|
-
message response.last if @command
|
220
|
+
message response.last if PRINTABLE_COMMANDS.include?(@command)
|
176
221
|
end
|
177
222
|
ensure
|
178
|
-
|
223
|
+
if server
|
224
|
+
if uri.scheme == 'ssl'
|
225
|
+
server.sysclose
|
226
|
+
else
|
227
|
+
server.close unless server.closed?
|
228
|
+
end
|
229
|
+
end
|
179
230
|
end
|
180
231
|
|
181
232
|
def send_signal
|
182
233
|
unless @pid
|
183
|
-
raise
|
234
|
+
raise 'Neither pid nor control url available'
|
184
235
|
end
|
185
236
|
|
186
237
|
begin
|
238
|
+
sig = CMD_PATH_SIG_MAP[@command]
|
187
239
|
|
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"
|
240
|
+
if sig.nil?
|
241
|
+
@stdout.puts "'#{@command}' not available via pid only"
|
242
|
+
@stdout.flush unless @stdout.sync
|
204
243
|
return
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
244
|
+
elsif sig.start_with? 'SIG'
|
245
|
+
Process.kill sig, @pid
|
246
|
+
elsif @command == 'status'
|
247
|
+
begin
|
248
|
+
Process.kill 0, @pid
|
249
|
+
@stdout.puts 'Puma is started'
|
250
|
+
@stdout.flush unless @stdout.sync
|
251
|
+
rescue Errno::ESRCH
|
252
|
+
raise 'Puma is not running'
|
253
|
+
end
|
210
254
|
return
|
211
255
|
end
|
212
|
-
|
213
256
|
rescue SystemCallError
|
214
|
-
if @command ==
|
257
|
+
if @command == 'restart'
|
215
258
|
start
|
216
259
|
else
|
217
260
|
raise "No pid '#{@pid}' found"
|
@@ -222,23 +265,21 @@ module Puma
|
|
222
265
|
end
|
223
266
|
|
224
267
|
def run
|
225
|
-
return start if @command ==
|
226
|
-
|
268
|
+
return start if @command == 'start'
|
227
269
|
prepare_configuration
|
228
270
|
|
229
|
-
if Puma.windows?
|
271
|
+
if Puma.windows? || @control_url
|
230
272
|
send_request
|
231
273
|
else
|
232
|
-
|
274
|
+
send_signal
|
233
275
|
end
|
234
276
|
|
235
277
|
rescue => e
|
236
278
|
message e.message
|
237
|
-
message e.backtrace
|
238
279
|
exit 1
|
239
280
|
end
|
240
281
|
|
241
|
-
|
282
|
+
private
|
242
283
|
def start
|
243
284
|
require 'puma/cli'
|
244
285
|
|
@@ -250,6 +291,7 @@ module Puma
|
|
250
291
|
run_args += ["--control-url", @control_url] if @control_url
|
251
292
|
run_args += ["--control-token", @control_auth_token] if @control_auth_token
|
252
293
|
run_args += ["-C", @config_file] if @config_file
|
294
|
+
run_args += ["-e", @environment] if @environment
|
253
295
|
|
254
296
|
events = Puma::Events.new @stdout, @stderr
|
255
297
|
|
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
|