puma 4.3.12 → 6.3.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 +1729 -521
- data/LICENSE +23 -20
- data/README.md +169 -45
- 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/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 +2 -2
- 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 +84 -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 +278 -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 +53 -39
- data/lib/puma/binder.rb +237 -121
- data/lib/puma/cli.rb +34 -34
- data/lib/puma/client.rb +172 -98
- data/lib/puma/cluster/worker.rb +180 -0
- data/lib/puma/cluster/worker_handle.rb +97 -0
- data/lib/puma/cluster.rb +226 -231
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +114 -87
- data/lib/puma/const.rb +139 -95
- data/lib/puma/control_cli.rb +99 -79
- data/lib/puma/detect.rb +33 -2
- data/lib/puma/dsl.rb +516 -110
- data/lib/puma/error_logger.rb +113 -0
- data/lib/puma/events.rb +16 -115
- data/lib/puma/io_buffer.rb +44 -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 +164 -155
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +36 -19
- data/lib/puma/minissl.rb +230 -55
- data/lib/puma/null_io.rb +18 -1
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +1 -1
- data/lib/puma/plugin.rb +3 -12
- data/lib/puma/rack/builder.rb +7 -11
- data/lib/puma/rack/urlmap.rb +0 -0
- data/lib/puma/rack_default.rb +19 -4
- data/lib/puma/reactor.rb +93 -368
- data/lib/puma/request.rb +671 -0
- data/lib/puma/runner.rb +92 -75
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +321 -794
- data/lib/puma/single.rb +20 -74
- data/lib/puma/state_file.rb +45 -8
- data/lib/puma/thread_pool.rb +140 -68
- data/lib/puma/util.rb +21 -4
- data/lib/puma.rb +54 -7
- data/lib/rack/handler/puma.rb +113 -87
- data/tools/{docker/Dockerfile → Dockerfile} +1 -1
- data/tools/trickletest.rb +0 -0
- metadata +33 -24
- 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/commonlogger.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module Puma
|
4
4
|
# Rack::CommonLogger forwards every request to the given +app+, and
|
5
5
|
# logs a line in the
|
6
|
-
# {Apache common log format}[
|
6
|
+
# {Apache common log format}[https://httpd.apache.org/docs/2.4/logs.html#common]
|
7
7
|
# to the +logger+.
|
8
8
|
#
|
9
9
|
# If +logger+ is nil, CommonLogger will fall back +rack.errors+, which is
|
@@ -16,7 +16,7 @@ module Puma
|
|
16
16
|
# (which is called without arguments in order to make the error appear for
|
17
17
|
# sure)
|
18
18
|
class CommonLogger
|
19
|
-
# Common Log Format:
|
19
|
+
# Common Log Format: https://httpd.apache.org/docs/2.4/logs.html#common
|
20
20
|
#
|
21
21
|
# lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 -
|
22
22
|
#
|
@@ -25,10 +25,17 @@ module Puma
|
|
25
25
|
|
26
26
|
HIJACK_FORMAT = %{%s - %s [%s] "%s %s%s %s" HIJACKED -1 %0.4f\n}
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
LOG_TIME_FORMAT = '%d/%b/%Y:%H:%M:%S %z'
|
29
|
+
|
30
|
+
CONTENT_LENGTH = 'Content-Length' # should be lower case from app,
|
31
|
+
# Util::HeaderHash allows mixed
|
32
|
+
HTTP_VERSION = Const::HTTP_VERSION
|
33
|
+
HTTP_X_FORWARDED_FOR = Const::HTTP_X_FORWARDED_FOR
|
34
|
+
PATH_INFO = Const::PATH_INFO
|
35
|
+
QUERY_STRING = Const::QUERY_STRING
|
36
|
+
REMOTE_ADDR = Const::REMOTE_ADDR
|
37
|
+
REMOTE_USER = 'REMOTE_USER'
|
38
|
+
REQUEST_METHOD = Const::REQUEST_METHOD
|
32
39
|
|
33
40
|
def initialize(app, logger=nil)
|
34
41
|
@app = app
|
@@ -57,13 +64,13 @@ module Puma
|
|
57
64
|
now = Time.now
|
58
65
|
|
59
66
|
msg = HIJACK_FORMAT % [
|
60
|
-
env[
|
61
|
-
env[
|
62
|
-
now.strftime(
|
67
|
+
env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-",
|
68
|
+
env[REMOTE_USER] || "-",
|
69
|
+
now.strftime(LOG_TIME_FORMAT),
|
63
70
|
env[REQUEST_METHOD],
|
64
71
|
env[PATH_INFO],
|
65
72
|
env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
|
66
|
-
env[
|
73
|
+
env[HTTP_VERSION],
|
67
74
|
now - began_at ]
|
68
75
|
|
69
76
|
write(msg)
|
@@ -74,13 +81,13 @@ module Puma
|
|
74
81
|
length = extract_content_length(header)
|
75
82
|
|
76
83
|
msg = FORMAT % [
|
77
|
-
env[
|
78
|
-
env[
|
79
|
-
now.strftime(
|
84
|
+
env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-",
|
85
|
+
env[REMOTE_USER] || "-",
|
86
|
+
now.strftime(LOG_TIME_FORMAT),
|
80
87
|
env[REQUEST_METHOD],
|
81
88
|
env[PATH_INFO],
|
82
89
|
env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
|
83
|
-
env[
|
90
|
+
env[HTTP_VERSION],
|
84
91
|
status.to_s[0..3],
|
85
92
|
length,
|
86
93
|
now - began_at ]
|
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,50 @@ 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
|
+
silence_fork_callback_warning: false,
|
161
|
+
tag: File.basename(Dir.getwd),
|
162
|
+
tcp_host: '0.0.0.0'.freeze,
|
163
|
+
tcp_port: 9292,
|
164
|
+
wait_for_less_busy_worker: 0.005,
|
165
|
+
worker_boot_timeout: 60,
|
166
|
+
worker_check_interval: 5,
|
167
|
+
worker_culling_strategy: :youngest,
|
168
|
+
worker_shutdown_timeout: 30,
|
169
|
+
worker_timeout: 60,
|
170
|
+
workers: 0,
|
171
|
+
http_content_length_limit: nil
|
172
|
+
}
|
130
173
|
|
131
174
|
def initialize(user_options={}, default_options = {}, &block)
|
132
175
|
default_options = self.puma_default_options.merge(default_options)
|
@@ -137,6 +180,10 @@ module Puma
|
|
137
180
|
@file_dsl = DSL.new(@options.file_options, self)
|
138
181
|
@default_dsl = DSL.new(@options.default_options, self)
|
139
182
|
|
183
|
+
if !@options[:prune_bundler]
|
184
|
+
default_options[:preload_app] = (@options[:workers] > 1) && Puma.forkable?
|
185
|
+
end
|
186
|
+
|
140
187
|
if block
|
141
188
|
configure(&block)
|
142
189
|
end
|
@@ -168,26 +215,21 @@ module Puma
|
|
168
215
|
end
|
169
216
|
|
170
217
|
def puma_default_options
|
218
|
+
defaults = DEFAULTS.dup
|
219
|
+
puma_options_from_env.each { |k,v| defaults[k] = v if v }
|
220
|
+
defaults
|
221
|
+
end
|
222
|
+
|
223
|
+
def puma_options_from_env
|
224
|
+
min = ENV['PUMA_MIN_THREADS'] || ENV['MIN_THREADS']
|
225
|
+
max = ENV['PUMA_MAX_THREADS'] || ENV['MAX_THREADS']
|
226
|
+
workers = ENV['WEB_CONCURRENCY']
|
227
|
+
|
171
228
|
{
|
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
|
229
|
+
min_threads: min && Integer(min),
|
230
|
+
max_threads: max && Integer(max),
|
231
|
+
workers: workers && Integer(workers),
|
232
|
+
environment: ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'],
|
191
233
|
}
|
192
234
|
end
|
193
235
|
|
@@ -203,7 +245,7 @@ module Puma
|
|
203
245
|
return [] if files == ['-']
|
204
246
|
return files if files.any?
|
205
247
|
|
206
|
-
first_default_file = %W(config/puma/#{
|
248
|
+
first_default_file = %W(config/puma/#{@options[:environment]}.rb config/puma.rb).find do |f|
|
207
249
|
File.exist?(f)
|
208
250
|
end
|
209
251
|
|
@@ -245,16 +287,8 @@ module Puma
|
|
245
287
|
def app
|
246
288
|
found = options[:app] || load_rackup
|
247
289
|
|
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
290
|
if @options[:log_requests]
|
257
|
-
|
291
|
+
require_relative 'commonlogger'
|
258
292
|
logger = @options[:logger]
|
259
293
|
found = CommonLogger.new(found, logger)
|
260
294
|
end
|
@@ -267,16 +301,31 @@ module Puma
|
|
267
301
|
@options[:environment]
|
268
302
|
end
|
269
303
|
|
270
|
-
def environment_str
|
271
|
-
environment.respond_to?(:call) ? environment.call : environment
|
272
|
-
end
|
273
|
-
|
274
304
|
def load_plugin(name)
|
275
305
|
@plugins.create name
|
276
306
|
end
|
277
307
|
|
278
|
-
|
279
|
-
|
308
|
+
# @param key [:Symbol] hook to run
|
309
|
+
# @param arg [Launcher, Int] `:on_restart` passes Launcher
|
310
|
+
#
|
311
|
+
def run_hooks(key, arg, log_writer, hook_data = nil)
|
312
|
+
@options.all_of(key).each do |b|
|
313
|
+
begin
|
314
|
+
if Array === b
|
315
|
+
hook_data[b[1]] ||= Hash.new
|
316
|
+
b[0].call arg, hook_data[b[1]]
|
317
|
+
else
|
318
|
+
b.call arg
|
319
|
+
end
|
320
|
+
rescue => e
|
321
|
+
log_writer.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
|
322
|
+
log_writer.debug e.backtrace.join("\n")
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
def final_options
|
328
|
+
@options.final_options
|
280
329
|
end
|
281
330
|
|
282
331
|
def self.temp_path
|
@@ -288,10 +337,6 @@ module Puma
|
|
288
337
|
|
289
338
|
private
|
290
339
|
|
291
|
-
def infer_tag
|
292
|
-
File.basename(Dir.getwd)
|
293
|
-
end
|
294
|
-
|
295
340
|
# Load and use the normal Rack builder if we can, otherwise
|
296
341
|
# fallback to our minimal version.
|
297
342
|
def rack_builder
|
@@ -319,6 +364,8 @@ module Puma
|
|
319
364
|
raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
|
320
365
|
|
321
366
|
rack_app, rack_options = rack_builder.parse_file(rackup)
|
367
|
+
rack_options = rack_options || {}
|
368
|
+
|
322
369
|
@options.file_options.merge!(rack_options)
|
323
370
|
|
324
371
|
config_ru_binds = []
|
@@ -332,31 +379,11 @@ module Puma
|
|
332
379
|
end
|
333
380
|
|
334
381
|
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
|
382
|
+
require 'securerandom' unless defined?(SecureRandom)
|
356
383
|
|
357
|
-
|
384
|
+
SecureRandom.hex(16)
|
358
385
|
end
|
359
386
|
end
|
360
387
|
end
|
361
388
|
|
362
|
-
|
389
|
+
require_relative 'dsl'
|