puma 3.6.0 → 3.12.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 +5 -5
- data/{History.txt → History.md} +293 -79
- data/README.md +143 -227
- data/docs/architecture.md +36 -0
- data/{DEPLOYMENT.md → docs/deployment.md} +0 -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/plugins.md +28 -0
- data/docs/restart.md +39 -0
- data/docs/signals.md +56 -3
- data/docs/systemd.md +124 -22
- data/ext/puma_http11/extconf.rb +2 -0
- data/ext/puma_http11/http11_parser.c +85 -84
- data/ext/puma_http11/http11_parser.h +1 -0
- data/ext/puma_http11/http11_parser.rl +10 -9
- data/ext/puma_http11/io_buffer.c +7 -7
- data/ext/puma_http11/mini_ssl.c +62 -6
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +13 -16
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +15 -2
- data/ext/puma_http11/puma_http11.c +1 -0
- data/lib/puma.rb +13 -5
- data/lib/puma/app/status.rb +8 -0
- data/lib/puma/binder.rb +21 -14
- data/lib/puma/cli.rb +49 -33
- data/lib/puma/client.rb +39 -4
- data/lib/puma/cluster.rb +51 -11
- data/lib/puma/commonlogger.rb +19 -20
- data/lib/puma/compat.rb +3 -7
- data/lib/puma/configuration.rb +133 -130
- data/lib/puma/const.rb +13 -37
- data/lib/puma/control_cli.rb +38 -35
- data/lib/puma/convenient.rb +3 -3
- data/lib/puma/detect.rb +3 -1
- data/lib/puma/dsl.rb +80 -58
- data/lib/puma/events.rb +6 -8
- data/lib/puma/io_buffer.rb +1 -1
- data/lib/puma/jruby_restart.rb +0 -1
- data/lib/puma/launcher.rb +52 -30
- data/lib/puma/minissl.rb +73 -4
- data/lib/puma/null_io.rb +6 -13
- data/lib/puma/plugin/tmp_restart.rb +1 -2
- data/lib/puma/rack/builder.rb +3 -0
- data/lib/puma/rack/urlmap.rb +9 -8
- data/lib/puma/reactor.rb +135 -0
- data/lib/puma/runner.rb +23 -1
- data/lib/puma/server.rb +117 -34
- data/lib/puma/single.rb +14 -3
- data/lib/puma/thread_pool.rb +67 -20
- data/lib/puma/util.rb +1 -5
- data/lib/rack/handler/puma.rb +58 -17
- data/tools/jungle/README.md +12 -2
- data/tools/jungle/init.d/README.md +9 -2
- data/tools/jungle/init.d/puma +32 -62
- data/tools/jungle/init.d/run-puma +5 -1
- data/tools/jungle/rc.d/README.md +74 -0
- data/tools/jungle/rc.d/puma +61 -0
- data/tools/jungle/rc.d/puma.conf +10 -0
- data/tools/trickletest.rb +1 -1
- metadata +22 -92
- data/Gemfile +0 -13
- data/Manifest.txt +0 -77
- data/Rakefile +0 -158
- data/lib/puma/rack/backports/uri/common_18.rb +0 -59
- data/lib/puma/rack/backports/uri/common_192.rb +0 -55
- data/puma.gemspec +0 -52
data/lib/puma/commonlogger.rb
CHANGED
@@ -21,6 +21,13 @@ module Puma
|
|
21
21
|
# %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
|
22
22
|
FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}
|
23
23
|
|
24
|
+
HIJACK_FORMAT = %{%s - %s [%s] "%s %s%s %s" HIJACKED -1 %0.4f\n}
|
25
|
+
|
26
|
+
CONTENT_LENGTH = 'Content-Length'.freeze
|
27
|
+
PATH_INFO = 'PATH_INFO'.freeze
|
28
|
+
QUERY_STRING = 'QUERY_STRING'.freeze
|
29
|
+
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
30
|
+
|
24
31
|
def initialize(app, logger=nil)
|
25
32
|
@app = app
|
26
33
|
@logger = logger
|
@@ -42,36 +49,23 @@ module Puma
|
|
42
49
|
[status, header, body]
|
43
50
|
end
|
44
51
|
|
45
|
-
HIJACK_FORMAT = %{%s - %s [%s] "%s %s%s %s" HIJACKED -1 %0.4f\n}
|
46
|
-
|
47
52
|
private
|
48
53
|
|
49
54
|
def log_hijacking(env, status, header, began_at)
|
50
55
|
now = Time.now
|
51
56
|
|
52
|
-
|
53
|
-
logger.write HIJACK_FORMAT % [
|
57
|
+
msg = HIJACK_FORMAT % [
|
54
58
|
env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
|
55
59
|
env["REMOTE_USER"] || "-",
|
56
60
|
now.strftime("%d/%b/%Y %H:%M:%S"),
|
57
|
-
env[
|
58
|
-
env[
|
59
|
-
env[
|
61
|
+
env[REQUEST_METHOD],
|
62
|
+
env[PATH_INFO],
|
63
|
+
env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
|
60
64
|
env["HTTP_VERSION"],
|
61
65
|
now - began_at ]
|
62
|
-
end
|
63
66
|
|
64
|
-
|
65
|
-
|
66
|
-
SCRIPT_NAME = 'SCRIPT_NAME'.freeze
|
67
|
-
QUERY_STRING = 'QUERY_STRING'.freeze
|
68
|
-
CACHE_CONTROL = 'Cache-Control'.freeze
|
69
|
-
CONTENT_LENGTH = 'Content-Length'.freeze
|
70
|
-
CONTENT_TYPE = 'Content-Type'.freeze
|
71
|
-
|
72
|
-
GET = 'GET'.freeze
|
73
|
-
HEAD = 'HEAD'.freeze
|
74
|
-
|
67
|
+
write(msg)
|
68
|
+
end
|
75
69
|
|
76
70
|
def log(env, status, header, began_at)
|
77
71
|
now = Time.now
|
@@ -83,13 +77,18 @@ module Puma
|
|
83
77
|
now.strftime("%d/%b/%Y:%H:%M:%S %z"),
|
84
78
|
env[REQUEST_METHOD],
|
85
79
|
env[PATH_INFO],
|
86
|
-
env[QUERY_STRING].empty? ? "" : "
|
80
|
+
env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
|
87
81
|
env["HTTP_VERSION"],
|
88
82
|
status.to_s[0..3],
|
89
83
|
length,
|
90
84
|
now - began_at ]
|
91
85
|
|
86
|
+
write(msg)
|
87
|
+
end
|
88
|
+
|
89
|
+
def write(msg)
|
92
90
|
logger = @logger || env['rack.errors']
|
91
|
+
|
93
92
|
# Standard library logger doesn't support write but it supports << which actually
|
94
93
|
# calls to write on the log device without formatting
|
95
94
|
if logger.respond_to?(:write)
|
data/lib/puma/compat.rb
CHANGED
@@ -6,13 +6,9 @@ class String
|
|
6
6
|
end
|
7
7
|
|
8
8
|
unless method_defined? :byteslice
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
def byteslice(*arg)
|
13
|
-
enc = self.encoding
|
14
|
-
self.dup.force_encoding(Encoding::ASCII_8BIT).slice(*arg).force_encoding(enc)
|
15
|
-
end
|
9
|
+
def byteslice(*arg)
|
10
|
+
enc = self.encoding
|
11
|
+
self.dup.force_encoding(Encoding::ASCII_8BIT).slice(*arg).force_encoding(enc)
|
16
12
|
end
|
17
13
|
end
|
18
14
|
end
|
data/lib/puma/configuration.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'puma/rack/builder'
|
2
2
|
require 'puma/plugin'
|
3
|
+
require 'puma/const'
|
3
4
|
|
4
5
|
module Puma
|
5
6
|
|
@@ -12,147 +13,147 @@ module Puma
|
|
12
13
|
DefaultWorkerShutdownTimeout = 30
|
13
14
|
end
|
14
15
|
|
15
|
-
class
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
16
|
+
# A class used for storing "leveled" configuration options.
|
17
|
+
#
|
18
|
+
# In this class any "user" specified options take precedence over any
|
19
|
+
# "file" specified options, take precedence over any "default" options.
|
20
|
+
#
|
21
|
+
# User input is prefered over "defaults":
|
22
|
+
# user_options = { foo: "bar" }
|
23
|
+
# default_options = { foo: "zoo" }
|
24
|
+
# options = UserFileDefaultOptions.new(user_options, default_options)
|
25
|
+
# puts options[:foo]
|
26
|
+
# # => "bar"
|
27
|
+
#
|
28
|
+
# All values can be accessed via `all_of`
|
29
|
+
#
|
30
|
+
# puts options.all_of(:foo)
|
31
|
+
# # => ["bar", "zoo"]
|
32
|
+
#
|
33
|
+
# A "file" option can be set. This config will be prefered over "default" options
|
34
|
+
# but will defer to any available "user" specified options.
|
35
|
+
#
|
36
|
+
# user_options = { foo: "bar" }
|
37
|
+
# default_options = { rackup: "zoo.rb" }
|
38
|
+
# options = UserFileDefaultOptions.new(user_options, default_options)
|
39
|
+
# options.file_options[:rackup] = "sup.rb"
|
40
|
+
# puts options[:rackup]
|
41
|
+
# # => "sup.rb"
|
42
|
+
#
|
43
|
+
# The "default" options can be set via procs. These are resolved during runtime
|
44
|
+
# via calls to `finalize_values`
|
45
|
+
class UserFileDefaultOptions
|
46
|
+
def initialize(user_options, default_options)
|
47
|
+
@user_options = user_options
|
48
|
+
@file_options = {}
|
49
|
+
@default_options = default_options
|
50
|
+
end
|
51
|
+
|
52
|
+
attr_reader :user_options, :file_options, :default_options
|
31
53
|
|
32
54
|
def [](key)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
v = @defaults[key]
|
40
|
-
if v.respond_to? :call
|
41
|
-
v.call
|
42
|
-
else
|
43
|
-
v
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def fetch(key, default=nil)
|
48
|
-
val = self[key]
|
49
|
-
return val if val
|
50
|
-
default
|
51
|
-
end
|
52
|
-
|
53
|
-
attr_reader :cur
|
54
|
-
|
55
|
-
def all_of(key)
|
56
|
-
all = []
|
57
|
-
|
58
|
-
@set.each do |o|
|
59
|
-
if v = o[key]
|
60
|
-
if v.kind_of? Array
|
61
|
-
all += v
|
62
|
-
else
|
63
|
-
all << v
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
all
|
55
|
+
return user_options[key] if user_options.key?(key)
|
56
|
+
return file_options[key] if file_options.key?(key)
|
57
|
+
return default_options[key] if default_options.key?(key)
|
69
58
|
end
|
70
59
|
|
71
|
-
def []=(key,
|
72
|
-
|
60
|
+
def []=(key, value)
|
61
|
+
user_options[key] = value
|
73
62
|
end
|
74
63
|
|
75
|
-
def
|
76
|
-
|
77
|
-
if o.key? key
|
78
|
-
return true
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
@default.key? key
|
64
|
+
def fetch(key, default_value = nil)
|
65
|
+
self[key] || default_value
|
83
66
|
end
|
84
67
|
|
85
|
-
def
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
end
|
90
|
-
|
91
|
-
def flatten
|
92
|
-
options = {}
|
93
|
-
|
94
|
-
@set.each do |o|
|
95
|
-
o.each do |k,v|
|
96
|
-
options[k] ||= v
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
options
|
101
|
-
end
|
68
|
+
def all_of(key)
|
69
|
+
user = user_options[key]
|
70
|
+
file = file_options[key]
|
71
|
+
default = default_options[key]
|
102
72
|
|
103
|
-
|
104
|
-
|
73
|
+
user = [user] unless user.is_a?(Array)
|
74
|
+
file = [file] unless file.is_a?(Array)
|
75
|
+
default = [default] unless default.is_a?(Array)
|
105
76
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
end
|
77
|
+
user.compact!
|
78
|
+
file.compact!
|
79
|
+
default.compact!
|
110
80
|
|
111
|
-
|
112
|
-
end
|
81
|
+
user + file + default
|
113
82
|
end
|
114
83
|
|
115
|
-
def
|
116
|
-
@
|
84
|
+
def finalize_values
|
85
|
+
@default_options.each do |k,v|
|
117
86
|
if v.respond_to? :call
|
118
|
-
@
|
87
|
+
@default_options[k] = v.call
|
119
88
|
end
|
120
89
|
end
|
121
90
|
end
|
122
91
|
end
|
123
92
|
|
93
|
+
# The main configuration class of Puma.
|
94
|
+
#
|
95
|
+
# It can be initialized with a set of "user" options and "default" options.
|
96
|
+
# Defaults will be merged with `Configuration.puma_default_options`.
|
97
|
+
#
|
98
|
+
# This class works together with 2 main other classes the `UserFileDefaultOptions`
|
99
|
+
# which stores configuration options in order so the precedence is that user
|
100
|
+
# set configuration wins over "file" based configuration wins over "default"
|
101
|
+
# configuration. These configurations are set via the `DSL` class. This
|
102
|
+
# class powers the Puma config file syntax and does double duty as a configuration
|
103
|
+
# DSL used by the `Puma::CLI` and Puma rack handler.
|
104
|
+
#
|
105
|
+
# It also handles loading plugins.
|
106
|
+
#
|
107
|
+
# > Note: `:port` and `:host` are not valid keys. By they time they make it to the
|
108
|
+
# configuration options they are expected to be incorporated into a `:binds` key.
|
109
|
+
# Under the hood the DSL maps `port` and `host` calls to `:binds`
|
110
|
+
#
|
111
|
+
# config = Configuration.new({}) do |user_config, file_config, default_config|
|
112
|
+
# user_config.port 3003
|
113
|
+
# end
|
114
|
+
# config.load
|
115
|
+
# puts config.options[:port]
|
116
|
+
# # => 3003
|
117
|
+
#
|
118
|
+
# It is expected that `load` is called on the configuration instance after setting
|
119
|
+
# config. This method expands any values in `config_file` and puts them into the
|
120
|
+
# correct configuration option hash.
|
121
|
+
#
|
122
|
+
# Once all configuration is complete it is expected that `clamp` will be called
|
123
|
+
# on the instance. This will expand any procs stored under "default" values. This
|
124
|
+
# is done because an environment variable may have been modified while loading
|
125
|
+
# configuration files.
|
124
126
|
class Configuration
|
125
127
|
include ConfigDefault
|
126
128
|
|
127
|
-
def
|
128
|
-
|
129
|
+
def initialize(user_options={}, default_options = {}, &block)
|
130
|
+
default_options = self.puma_default_options.merge(default_options)
|
129
131
|
|
130
|
-
|
132
|
+
@options = UserFileDefaultOptions.new(user_options, default_options)
|
133
|
+
@plugins = PluginLoader.new
|
134
|
+
@user_dsl = DSL.new(@options.user_options, self)
|
135
|
+
@file_dsl = DSL.new(@options.file_options, self)
|
136
|
+
@default_dsl = DSL.new(@options.default_options, self)
|
131
137
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
def initialize(options={}, &blk)
|
136
|
-
@options = LeveledOptions.new(default_options, options)
|
137
|
-
|
138
|
-
@plugins = PluginLoader.new
|
139
|
-
|
140
|
-
if blk
|
141
|
-
configure(&blk)
|
138
|
+
if block
|
139
|
+
configure(&block)
|
142
140
|
end
|
143
141
|
end
|
144
142
|
|
145
143
|
attr_reader :options, :plugins
|
146
144
|
|
147
|
-
def configure
|
148
|
-
@
|
149
|
-
|
145
|
+
def configure
|
146
|
+
yield @user_dsl, @file_dsl, @default_dsl
|
147
|
+
ensure
|
148
|
+
@user_dsl._offer_plugins
|
149
|
+
@file_dsl._offer_plugins
|
150
|
+
@default_dsl._offer_plugins
|
150
151
|
end
|
151
152
|
|
152
153
|
def initialize_copy(other)
|
153
|
-
@conf
|
154
|
+
@conf = nil
|
154
155
|
@cli_options = nil
|
155
|
-
@options
|
156
|
+
@options = @options.dup
|
156
157
|
end
|
157
158
|
|
158
159
|
def flatten
|
@@ -164,7 +165,7 @@ module Puma
|
|
164
165
|
self
|
165
166
|
end
|
166
167
|
|
167
|
-
def
|
168
|
+
def puma_default_options
|
168
169
|
{
|
169
170
|
:min_threads => 0,
|
170
171
|
:max_threads => 16,
|
@@ -179,38 +180,37 @@ module Puma
|
|
179
180
|
:worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
|
180
181
|
:remote_address => :socket,
|
181
182
|
:tag => method(:infer_tag),
|
182
|
-
:environment =>
|
183
|
+
:environment => -> { ENV['RACK_ENV'] || "development" },
|
183
184
|
:rackup => DefaultRackup,
|
184
185
|
:logger => STDOUT,
|
185
|
-
:persistent_timeout => Const::PERSISTENT_TIMEOUT
|
186
|
+
:persistent_timeout => Const::PERSISTENT_TIMEOUT,
|
187
|
+
:first_data_timeout => Const::FIRST_DATA_TIMEOUT
|
186
188
|
}
|
187
189
|
end
|
188
190
|
|
189
191
|
def load
|
190
|
-
|
192
|
+
config_files.each { |config_file| @file_dsl._load_from(config_file) }
|
191
193
|
|
192
|
-
|
193
|
-
|
194
|
-
File.exist?(f)
|
195
|
-
}
|
194
|
+
@options
|
195
|
+
end
|
196
196
|
|
197
|
-
|
198
|
-
|
199
|
-
files = []
|
200
|
-
end
|
197
|
+
def config_files
|
198
|
+
files = @options.all_of(:config_files)
|
201
199
|
|
202
|
-
files
|
203
|
-
|
200
|
+
return [] if files == ['-']
|
201
|
+
return files if files.any?
|
204
202
|
|
205
|
-
|
203
|
+
first_default_file = %W(config/puma/#{environment_str}.rb config/puma.rb).find do |f|
|
204
|
+
File.exist?(f)
|
206
205
|
end
|
206
|
+
|
207
|
+
[first_default_file]
|
207
208
|
end
|
208
209
|
|
209
210
|
# Call once all configuration (included from rackup files)
|
210
211
|
# is loaded to flesh out any defaults
|
211
212
|
def clamp
|
212
|
-
@options.
|
213
|
-
@options.force_defaults
|
213
|
+
@options.finalize_values
|
214
214
|
end
|
215
215
|
|
216
216
|
# Injects the Configuration object into the env
|
@@ -251,6 +251,7 @@ module Puma
|
|
251
251
|
end
|
252
252
|
|
253
253
|
if @options[:log_requests]
|
254
|
+
require 'puma/commonlogger'
|
254
255
|
logger = @options[:logger]
|
255
256
|
found = CommonLogger.new(found, logger)
|
256
257
|
end
|
@@ -263,6 +264,10 @@ module Puma
|
|
263
264
|
@options[:environment]
|
264
265
|
end
|
265
266
|
|
267
|
+
def environment_str
|
268
|
+
environment.respond_to?(:call) ? environment.call : environment
|
269
|
+
end
|
270
|
+
|
266
271
|
def load_plugin(name)
|
267
272
|
@plugins.create name
|
268
273
|
end
|
@@ -310,17 +315,15 @@ module Puma
|
|
310
315
|
def load_rackup
|
311
316
|
raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
|
312
317
|
|
313
|
-
@options.shift
|
314
|
-
|
315
318
|
rack_app, rack_options = rack_builder.parse_file(rackup)
|
316
|
-
@options.merge!(rack_options)
|
319
|
+
@options.file_options.merge!(rack_options)
|
317
320
|
|
318
321
|
config_ru_binds = []
|
319
322
|
rack_options.each do |k, v|
|
320
323
|
config_ru_binds << v if k.to_s.start_with?("bind")
|
321
324
|
end
|
322
325
|
|
323
|
-
@options[:binds] = config_ru_binds unless config_ru_binds.empty?
|
326
|
+
@options.file_options[:binds] = config_ru_binds unless config_ru_binds.empty?
|
324
327
|
|
325
328
|
rack_app
|
326
329
|
end
|
@@ -342,7 +345,7 @@ module Puma
|
|
342
345
|
end
|
343
346
|
|
344
347
|
if bytes
|
345
|
-
token = ""
|
348
|
+
token = "".dup
|
346
349
|
bytes.each_byte { |b| token << b.to_s(16) }
|
347
350
|
else
|
348
351
|
token = (0..count).to_a.map { rand(255).to_s(16) }.join
|