puma 3.12.6 → 6.3.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 +1806 -451
- data/LICENSE +23 -20
- data/README.md +217 -65
- data/bin/puma-wild +3 -9
- data/docs/architecture.md +59 -21
- data/docs/compile_options.md +55 -0
- data/docs/deployment.md +69 -58
- 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/docs/kubernetes.md +66 -0
- data/docs/nginx.md +2 -2
- 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 +94 -120
- 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 -2
- data/ext/puma_http11/ext_help.h +1 -1
- data/ext/puma_http11/extconf.rb +61 -3
- data/ext/puma_http11/http11_parser.c +103 -117
- data/ext/puma_http11/http11_parser.h +2 -2
- data/ext/puma_http11/http11_parser.java.rl +22 -38
- data/ext/puma_http11/http11_parser.rl +3 -3
- data/ext/puma_http11/http11_parser_common.rl +6 -6
- data/ext/puma_http11/mini_ssl.c +389 -99
- 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 +248 -92
- data/ext/puma_http11/puma_http11.c +49 -57
- data/lib/puma/app/status.rb +71 -49
- data/lib/puma/binder.rb +244 -150
- data/lib/puma/cli.rb +38 -34
- data/lib/puma/client.rb +388 -244
- data/lib/puma/cluster/worker.rb +180 -0
- data/lib/puma/cluster/worker_handle.rb +97 -0
- data/lib/puma/cluster.rb +261 -243
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +116 -88
- data/lib/puma/const.rb +154 -104
- data/lib/puma/control_cli.rb +115 -70
- data/lib/puma/detect.rb +33 -2
- data/lib/puma/dsl.rb +764 -134
- data/lib/puma/error_logger.rb +113 -0
- data/lib/puma/events.rb +16 -112
- data/lib/puma/io_buffer.rb +42 -5
- 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 +184 -133
- data/lib/puma/log_writer.rb +147 -0
- data/lib/puma/minissl/context_builder.rb +93 -0
- data/lib/puma/minissl.rb +263 -70
- data/lib/puma/null_io.rb +18 -1
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +3 -1
- data/lib/puma/plugin.rb +7 -13
- data/lib/puma/rack/builder.rb +9 -11
- data/lib/puma/rack/urlmap.rb +2 -0
- data/lib/puma/rack_default.rb +21 -4
- data/lib/puma/reactor.rb +93 -315
- data/lib/puma/request.rb +671 -0
- data/lib/puma/runner.rb +94 -69
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +327 -772
- data/lib/puma/single.rb +20 -74
- data/lib/puma/state_file.rb +45 -8
- data/lib/puma/thread_pool.rb +146 -92
- data/lib/puma/util.rb +22 -10
- data/lib/puma.rb +60 -5
- data/lib/rack/handler/puma.rb +116 -90
- data/tools/Dockerfile +16 -0
- data/tools/trickletest.rb +0 -1
- metadata +54 -32
- 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/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,26 +1,17 @@
|
|
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
|
21
12
|
# "file" specified options, take precedence over any "default" options.
|
22
13
|
#
|
23
|
-
# User input is
|
14
|
+
# User input is preferred over "defaults":
|
24
15
|
# user_options = { foo: "bar" }
|
25
16
|
# default_options = { foo: "zoo" }
|
26
17
|
# options = UserFileDefaultOptions.new(user_options, default_options)
|
@@ -32,7 +23,7 @@ module Puma
|
|
32
23
|
# puts options.all_of(:foo)
|
33
24
|
# # => ["bar", "zoo"]
|
34
25
|
#
|
35
|
-
# A "file" option can be set. This config will be
|
26
|
+
# A "file" option can be set. This config will be preferred over "default" options
|
36
27
|
# but will defer to any available "user" specified options.
|
37
28
|
#
|
38
29
|
# user_options = { foo: "bar" }
|
@@ -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,25 +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
|
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'],
|
190
233
|
}
|
191
234
|
end
|
192
235
|
|
@@ -202,7 +245,7 @@ module Puma
|
|
202
245
|
return [] if files == ['-']
|
203
246
|
return files if files.any?
|
204
247
|
|
205
|
-
first_default_file = %W(config/puma/#{
|
248
|
+
first_default_file = %W(config/puma/#{@options[:environment]}.rb config/puma.rb).find do |f|
|
206
249
|
File.exist?(f)
|
207
250
|
end
|
208
251
|
|
@@ -244,16 +287,8 @@ module Puma
|
|
244
287
|
def app
|
245
288
|
found = options[:app] || load_rackup
|
246
289
|
|
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
290
|
if @options[:log_requests]
|
256
|
-
|
291
|
+
require_relative 'commonlogger'
|
257
292
|
logger = @options[:logger]
|
258
293
|
found = CommonLogger.new(found, logger)
|
259
294
|
end
|
@@ -266,16 +301,31 @@ module Puma
|
|
266
301
|
@options[:environment]
|
267
302
|
end
|
268
303
|
|
269
|
-
def environment_str
|
270
|
-
environment.respond_to?(:call) ? environment.call : environment
|
271
|
-
end
|
272
|
-
|
273
304
|
def load_plugin(name)
|
274
305
|
@plugins.create name
|
275
306
|
end
|
276
307
|
|
277
|
-
|
278
|
-
|
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
|
279
329
|
end
|
280
330
|
|
281
331
|
def self.temp_path
|
@@ -287,10 +337,6 @@ module Puma
|
|
287
337
|
|
288
338
|
private
|
289
339
|
|
290
|
-
def infer_tag
|
291
|
-
File.basename(Dir.getwd)
|
292
|
-
end
|
293
|
-
|
294
340
|
# Load and use the normal Rack builder if we can, otherwise
|
295
341
|
# fallback to our minimal version.
|
296
342
|
def rack_builder
|
@@ -318,6 +364,8 @@ module Puma
|
|
318
364
|
raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
|
319
365
|
|
320
366
|
rack_app, rack_options = rack_builder.parse_file(rackup)
|
367
|
+
rack_options = rack_options || {}
|
368
|
+
|
321
369
|
@options.file_options.merge!(rack_options)
|
322
370
|
|
323
371
|
config_ru_binds = []
|
@@ -331,31 +379,11 @@ module Puma
|
|
331
379
|
end
|
332
380
|
|
333
381
|
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
|
382
|
+
require 'securerandom' unless defined?(SecureRandom)
|
355
383
|
|
356
|
-
|
384
|
+
SecureRandom.hex(16)
|
357
385
|
end
|
358
386
|
end
|
359
387
|
end
|
360
388
|
|
361
|
-
|
389
|
+
require_relative 'dsl'
|