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.

Files changed (66) hide show
  1. checksums.yaml +5 -5
  2. data/{History.txt → History.md} +293 -79
  3. data/README.md +143 -227
  4. data/docs/architecture.md +36 -0
  5. data/{DEPLOYMENT.md → docs/deployment.md} +0 -0
  6. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  7. data/docs/images/puma-connection-flow.png +0 -0
  8. data/docs/images/puma-general-arch.png +0 -0
  9. data/docs/plugins.md +28 -0
  10. data/docs/restart.md +39 -0
  11. data/docs/signals.md +56 -3
  12. data/docs/systemd.md +124 -22
  13. data/ext/puma_http11/extconf.rb +2 -0
  14. data/ext/puma_http11/http11_parser.c +85 -84
  15. data/ext/puma_http11/http11_parser.h +1 -0
  16. data/ext/puma_http11/http11_parser.rl +10 -9
  17. data/ext/puma_http11/io_buffer.c +7 -7
  18. data/ext/puma_http11/mini_ssl.c +62 -6
  19. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +13 -16
  20. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +15 -2
  21. data/ext/puma_http11/puma_http11.c +1 -0
  22. data/lib/puma.rb +13 -5
  23. data/lib/puma/app/status.rb +8 -0
  24. data/lib/puma/binder.rb +21 -14
  25. data/lib/puma/cli.rb +49 -33
  26. data/lib/puma/client.rb +39 -4
  27. data/lib/puma/cluster.rb +51 -11
  28. data/lib/puma/commonlogger.rb +19 -20
  29. data/lib/puma/compat.rb +3 -7
  30. data/lib/puma/configuration.rb +133 -130
  31. data/lib/puma/const.rb +13 -37
  32. data/lib/puma/control_cli.rb +38 -35
  33. data/lib/puma/convenient.rb +3 -3
  34. data/lib/puma/detect.rb +3 -1
  35. data/lib/puma/dsl.rb +80 -58
  36. data/lib/puma/events.rb +6 -8
  37. data/lib/puma/io_buffer.rb +1 -1
  38. data/lib/puma/jruby_restart.rb +0 -1
  39. data/lib/puma/launcher.rb +52 -30
  40. data/lib/puma/minissl.rb +73 -4
  41. data/lib/puma/null_io.rb +6 -13
  42. data/lib/puma/plugin/tmp_restart.rb +1 -2
  43. data/lib/puma/rack/builder.rb +3 -0
  44. data/lib/puma/rack/urlmap.rb +9 -8
  45. data/lib/puma/reactor.rb +135 -0
  46. data/lib/puma/runner.rb +23 -1
  47. data/lib/puma/server.rb +117 -34
  48. data/lib/puma/single.rb +14 -3
  49. data/lib/puma/thread_pool.rb +67 -20
  50. data/lib/puma/util.rb +1 -5
  51. data/lib/rack/handler/puma.rb +58 -17
  52. data/tools/jungle/README.md +12 -2
  53. data/tools/jungle/init.d/README.md +9 -2
  54. data/tools/jungle/init.d/puma +32 -62
  55. data/tools/jungle/init.d/run-puma +5 -1
  56. data/tools/jungle/rc.d/README.md +74 -0
  57. data/tools/jungle/rc.d/puma +61 -0
  58. data/tools/jungle/rc.d/puma.conf +10 -0
  59. data/tools/trickletest.rb +1 -1
  60. metadata +22 -92
  61. data/Gemfile +0 -13
  62. data/Manifest.txt +0 -77
  63. data/Rakefile +0 -158
  64. data/lib/puma/rack/backports/uri/common_18.rb +0 -59
  65. data/lib/puma/rack/backports/uri/common_192.rb +0 -55
  66. data/puma.gemspec +0 -52
@@ -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
- logger = @logger || env['rack.errors']
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["REQUEST_METHOD"],
58
- env["PATH_INFO"],
59
- env["QUERY_STRING"].empty? ? "" : "?"+env["QUERY_STRING"],
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
- PATH_INFO = 'PATH_INFO'.freeze
65
- REQUEST_METHOD = 'REQUEST_METHOD'.freeze
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? ? "" : "?"+env[QUERY_STRING],
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)
@@ -6,13 +6,9 @@ class String
6
6
  end
7
7
 
8
8
  unless method_defined? :byteslice
9
- if RUBY_VERSION < '1.9'
10
- alias_method :byteslice, :[]
11
- else
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
@@ -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 LeveledOptions
16
- def initialize(default_options, user_options)
17
- @cur = user_options
18
- @set = [@cur]
19
- @defaults = default_options.dup
20
- end
21
-
22
- def initialize_copy(other)
23
- @set = @set.map { |o| o.dup }
24
- @cur = @set.last
25
- end
26
-
27
- def shift
28
- @cur = {}
29
- @set << @cur
30
- end
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
- @set.each do |o|
34
- if o.key? key
35
- return o[key]
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, val)
72
- @cur[key] = val
60
+ def []=(key, value)
61
+ user_options[key] = value
73
62
  end
74
63
 
75
- def key?(key)
76
- @set.each do |o|
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 merge!(o)
86
- o.each do |k,v|
87
- @cur[k]= v
88
- end
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
- def explain
104
- indent = ""
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
- @set.each do |o|
107
- o.keys.sort.each do |k|
108
- puts "#{indent}#{k}: #{o[k].inspect}"
109
- end
77
+ user.compact!
78
+ file.compact!
79
+ default.compact!
110
80
 
111
- indent = " #{indent}"
112
- end
81
+ user + file + default
113
82
  end
114
83
 
115
- def force_defaults
116
- @defaults.each do |k,v|
84
+ def finalize_values
85
+ @default_options.each do |k,v|
117
86
  if v.respond_to? :call
118
- @defaults[k] = v.call
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 self.from_file(path)
128
- cfg = new
129
+ def initialize(user_options={}, default_options = {}, &block)
130
+ default_options = self.puma_default_options.merge(default_options)
129
131
 
130
- DSL.new(cfg.options, cfg)._load_from path
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
- return cfg
133
- end
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(&blk)
148
- @options.shift
149
- DSL.new(@options, self)._run(&blk)
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 = nil
154
+ @conf = nil
154
155
  @cli_options = nil
155
- @options = @options.dup
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 default_options
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 => lambda { ENV['RACK_ENV'] || "development" },
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
- files = @options.all_of(:config_files)
192
+ config_files.each { |config_file| @file_dsl._load_from(config_file) }
191
193
 
192
- if files.empty?
193
- imp = %W(config/puma/#{@options[:environment]}.rb config/puma.rb).find { |f|
194
- File.exist?(f)
195
- }
194
+ @options
195
+ end
196
196
 
197
- files << imp
198
- elsif files == ["-"]
199
- files = []
200
- end
197
+ def config_files
198
+ files = @options.all_of(:config_files)
201
199
 
202
- files.each do |f|
203
- @options.shift
200
+ return [] if files == ['-']
201
+ return files if files.any?
204
202
 
205
- DSL.load @options, self, f
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.shift
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