puma 3.0.0.rc1 → 5.0.0.beta1

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 (91) hide show
  1. checksums.yaml +5 -5
  2. data/{History.txt → History.md} +703 -70
  3. data/LICENSE +23 -20
  4. data/README.md +173 -163
  5. data/docs/architecture.md +37 -0
  6. data/{DEPLOYMENT.md → docs/deployment.md} +28 -6
  7. data/docs/fork_worker.md +31 -0
  8. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  9. data/docs/images/puma-connection-flow.png +0 -0
  10. data/docs/images/puma-general-arch.png +0 -0
  11. data/docs/jungle/README.md +13 -0
  12. data/docs/jungle/rc.d/README.md +74 -0
  13. data/docs/jungle/rc.d/puma +61 -0
  14. data/docs/jungle/rc.d/puma.conf +10 -0
  15. data/{tools → docs}/jungle/upstart/README.md +0 -0
  16. data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
  17. data/{tools → docs}/jungle/upstart/puma.conf +1 -1
  18. data/docs/nginx.md +2 -2
  19. data/docs/plugins.md +38 -0
  20. data/docs/restart.md +41 -0
  21. data/docs/signals.md +57 -3
  22. data/docs/systemd.md +228 -0
  23. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  24. data/ext/puma_http11/extconf.rb +16 -0
  25. data/ext/puma_http11/http11_parser.c +287 -468
  26. data/ext/puma_http11/http11_parser.h +1 -0
  27. data/ext/puma_http11/http11_parser.java.rl +21 -37
  28. data/ext/puma_http11/http11_parser.rl +10 -9
  29. data/ext/puma_http11/http11_parser_common.rl +4 -4
  30. data/ext/puma_http11/mini_ssl.c +159 -10
  31. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  32. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +99 -132
  33. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +30 -6
  34. data/ext/puma_http11/puma_http11.c +6 -38
  35. data/lib/puma.rb +25 -5
  36. data/lib/puma/accept_nonblock.rb +7 -1
  37. data/lib/puma/app/status.rb +53 -26
  38. data/lib/puma/binder.rb +150 -119
  39. data/lib/puma/cli.rb +56 -38
  40. data/lib/puma/client.rb +277 -80
  41. data/lib/puma/cluster.rb +326 -130
  42. data/lib/puma/commonlogger.rb +21 -20
  43. data/lib/puma/configuration.rb +160 -161
  44. data/lib/puma/const.rb +50 -47
  45. data/lib/puma/control_cli.rb +104 -63
  46. data/lib/puma/detect.rb +13 -1
  47. data/lib/puma/dsl.rb +463 -114
  48. data/lib/puma/events.rb +22 -13
  49. data/lib/puma/io_buffer.rb +9 -5
  50. data/lib/puma/jruby_restart.rb +2 -59
  51. data/lib/puma/launcher.rb +195 -105
  52. data/lib/puma/minissl.rb +110 -4
  53. data/lib/puma/minissl/context_builder.rb +76 -0
  54. data/lib/puma/null_io.rb +9 -14
  55. data/lib/puma/plugin.rb +32 -12
  56. data/lib/puma/plugin/tmp_restart.rb +19 -6
  57. data/lib/puma/rack/builder.rb +7 -5
  58. data/lib/puma/rack/urlmap.rb +11 -8
  59. data/lib/puma/rack_default.rb +2 -0
  60. data/lib/puma/reactor.rb +242 -32
  61. data/lib/puma/runner.rb +41 -30
  62. data/lib/puma/server.rb +265 -183
  63. data/lib/puma/single.rb +22 -63
  64. data/lib/puma/state_file.rb +9 -2
  65. data/lib/puma/thread_pool.rb +179 -68
  66. data/lib/puma/util.rb +3 -11
  67. data/lib/rack/handler/puma.rb +60 -11
  68. data/tools/Dockerfile +16 -0
  69. data/tools/trickletest.rb +1 -2
  70. metadata +35 -99
  71. data/COPYING +0 -55
  72. data/Gemfile +0 -13
  73. data/Manifest.txt +0 -79
  74. data/Rakefile +0 -158
  75. data/docs/config.md +0 -0
  76. data/ext/puma_http11/io_buffer.c +0 -155
  77. data/lib/puma/capistrano.rb +0 -94
  78. data/lib/puma/compat.rb +0 -18
  79. data/lib/puma/convenient.rb +0 -23
  80. data/lib/puma/daemon_ext.rb +0 -31
  81. data/lib/puma/delegation.rb +0 -11
  82. data/lib/puma/java_io_buffer.rb +0 -45
  83. data/lib/puma/rack/backports/uri/common_18.rb +0 -56
  84. data/lib/puma/rack/backports/uri/common_192.rb +0 -52
  85. data/lib/puma/rack/backports/uri/common_193.rb +0 -29
  86. data/lib/puma/tcp_logger.rb +0 -32
  87. data/puma.gemspec +0 -52
  88. data/tools/jungle/README.md +0 -9
  89. data/tools/jungle/init.d/README.md +0 -54
  90. data/tools/jungle/init.d/puma +0 -394
  91. data/tools/jungle/init.d/run-puma +0 -3
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  # Rack::CommonLogger forwards every request to the given +app+, and
3
5
  # logs a line in the
@@ -21,6 +23,13 @@ module Puma
21
23
  # %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
22
24
  FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}
23
25
 
26
+ HIJACK_FORMAT = %{%s - %s [%s] "%s %s%s %s" HIJACKED -1 %0.4f\n}
27
+
28
+ CONTENT_LENGTH = 'Content-Length'.freeze
29
+ PATH_INFO = 'PATH_INFO'.freeze
30
+ QUERY_STRING = 'QUERY_STRING'.freeze
31
+ REQUEST_METHOD = 'REQUEST_METHOD'.freeze
32
+
24
33
  def initialize(app, logger=nil)
25
34
  @app = app
26
35
  @logger = logger
@@ -42,36 +51,23 @@ module Puma
42
51
  [status, header, body]
43
52
  end
44
53
 
45
- HIJACK_FORMAT = %{%s - %s [%s] "%s %s%s %s" HIJACKED -1 %0.4f\n}
46
-
47
54
  private
48
55
 
49
56
  def log_hijacking(env, status, header, began_at)
50
57
  now = Time.now
51
58
 
52
- logger = @logger || env['rack.errors']
53
- logger.write HIJACK_FORMAT % [
59
+ msg = HIJACK_FORMAT % [
54
60
  env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
55
61
  env["REMOTE_USER"] || "-",
56
62
  now.strftime("%d/%b/%Y %H:%M:%S"),
57
- env["REQUEST_METHOD"],
58
- env["PATH_INFO"],
59
- env["QUERY_STRING"].empty? ? "" : "?"+env["QUERY_STRING"],
63
+ env[REQUEST_METHOD],
64
+ env[PATH_INFO],
65
+ env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
60
66
  env["HTTP_VERSION"],
61
67
  now - began_at ]
62
- end
63
68
 
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
-
69
+ write(msg)
70
+ end
75
71
 
76
72
  def log(env, status, header, began_at)
77
73
  now = Time.now
@@ -83,13 +79,18 @@ module Puma
83
79
  now.strftime("%d/%b/%Y:%H:%M:%S %z"),
84
80
  env[REQUEST_METHOD],
85
81
  env[PATH_INFO],
86
- env[QUERY_STRING].empty? ? "" : "?"+env[QUERY_STRING],
82
+ env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
87
83
  env["HTTP_VERSION"],
88
84
  status.to_s[0..3],
89
85
  length,
90
86
  now - began_at ]
91
87
 
88
+ write(msg)
89
+ end
90
+
91
+ def write(msg)
92
92
  logger = @logger || env['rack.errors']
93
+
93
94
  # Standard library logger doesn't support write but it supports << which actually
94
95
  # calls to write on the log device without formatting
95
96
  if logger.respond_to?(:write)
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/rack/builder'
2
4
  require 'puma/plugin'
5
+ require 'puma/const'
3
6
 
4
7
  module Puma
5
8
 
@@ -12,144 +15,153 @@ module Puma
12
15
  DefaultWorkerShutdownTimeout = 30
13
16
  end
14
17
 
15
- class LeveledOptions
16
- def initialize(default={})
17
- @cur = {}
18
- @set = [@cur]
19
- @defaults = default.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
18
+ # A class used for storing "leveled" configuration options.
19
+ #
20
+ # In this class any "user" specified options take precedence over any
21
+ # "file" specified options, take precedence over any "default" options.
22
+ #
23
+ # User input is preferred over "defaults":
24
+ # user_options = { foo: "bar" }
25
+ # default_options = { foo: "zoo" }
26
+ # options = UserFileDefaultOptions.new(user_options, default_options)
27
+ # puts options[:foo]
28
+ # # => "bar"
29
+ #
30
+ # All values can be accessed via `all_of`
31
+ #
32
+ # puts options.all_of(:foo)
33
+ # # => ["bar", "zoo"]
34
+ #
35
+ # A "file" option can be set. This config will be preferred over "default" options
36
+ # but will defer to any available "user" specified options.
37
+ #
38
+ # user_options = { foo: "bar" }
39
+ # default_options = { rackup: "zoo.rb" }
40
+ # options = UserFileDefaultOptions.new(user_options, default_options)
41
+ # options.file_options[:rackup] = "sup.rb"
42
+ # puts options[:rackup]
43
+ # # => "sup.rb"
44
+ #
45
+ # The "default" options can be set via procs. These are resolved during runtime
46
+ # via calls to `finalize_values`
47
+ class UserFileDefaultOptions
48
+ def initialize(user_options, default_options)
49
+ @user_options = user_options
50
+ @file_options = {}
51
+ @default_options = default_options
52
+ end
53
+
54
+ attr_reader :user_options, :file_options, :default_options
31
55
 
32
56
  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
- attr_reader :cur
48
-
49
- def all_of(key)
50
- all = []
51
-
52
- @set.each do |o|
53
- if v = o[key]
54
- if v.kind_of? Array
55
- all += v
56
- else
57
- all << v
58
- end
59
- end
60
- end
61
-
62
- all
57
+ fetch(key)
63
58
  end
64
59
 
65
- def []=(key, val)
66
- @cur[key] = val
60
+ def []=(key, value)
61
+ user_options[key] = value
67
62
  end
68
63
 
69
- def key?(key)
70
- @set.each do |o|
71
- if o.key? key
72
- return true
73
- end
74
- end
75
-
76
- @default.key? key
77
- end
64
+ def fetch(key, default_value = nil)
65
+ return user_options[key] if user_options.key?(key)
66
+ return file_options[key] if file_options.key?(key)
67
+ return default_options[key] if default_options.key?(key)
78
68
 
79
- def merge!(o)
80
- o.each do |k,v|
81
- @cur[k]= v
82
- end
69
+ default_value
83
70
  end
84
71
 
85
- def flatten
86
- options = {}
87
-
88
- @set.each do |o|
89
- o.each do |k,v|
90
- options[k] ||= v
91
- end
92
- end
93
-
94
- options
95
- end
72
+ def all_of(key)
73
+ user = user_options[key]
74
+ file = file_options[key]
75
+ default = default_options[key]
96
76
 
97
- def explain
98
- indent = ""
77
+ user = [user] unless user.is_a?(Array)
78
+ file = [file] unless file.is_a?(Array)
79
+ default = [default] unless default.is_a?(Array)
99
80
 
100
- @set.each do |o|
101
- o.keys.sort.each do |k|
102
- puts "#{indent}#{k}: #{o[k].inspect}"
103
- end
81
+ user.compact!
82
+ file.compact!
83
+ default.compact!
104
84
 
105
- indent = " #{indent}"
106
- end
85
+ user + file + default
107
86
  end
108
87
 
109
- def force_defaults
110
- @defaults.each do |k,v|
88
+ def finalize_values
89
+ @default_options.each do |k,v|
111
90
  if v.respond_to? :call
112
- @defaults[k] = v.call
91
+ @default_options[k] = v.call
113
92
  end
114
93
  end
115
94
  end
116
95
  end
117
96
 
97
+ # The main configuration class of Puma.
98
+ #
99
+ # It can be initialized with a set of "user" options and "default" options.
100
+ # Defaults will be merged with `Configuration.puma_default_options`.
101
+ #
102
+ # This class works together with 2 main other classes the `UserFileDefaultOptions`
103
+ # which stores configuration options in order so the precedence is that user
104
+ # set configuration wins over "file" based configuration wins over "default"
105
+ # configuration. These configurations are set via the `DSL` class. This
106
+ # class powers the Puma config file syntax and does double duty as a configuration
107
+ # DSL used by the `Puma::CLI` and Puma rack handler.
108
+ #
109
+ # It also handles loading plugins.
110
+ #
111
+ # > Note: `:port` and `:host` are not valid keys. By the time they make it to the
112
+ # configuration options they are expected to be incorporated into a `:binds` key.
113
+ # Under the hood the DSL maps `port` and `host` calls to `:binds`
114
+ #
115
+ # config = Configuration.new({}) do |user_config, file_config, default_config|
116
+ # user_config.port 3003
117
+ # end
118
+ # config.load
119
+ # puts config.options[:port]
120
+ # # => 3003
121
+ #
122
+ # It is expected that `load` is called on the configuration instance after setting
123
+ # config. This method expands any values in `config_file` and puts them into the
124
+ # correct configuration option hash.
125
+ #
126
+ # Once all configuration is complete it is expected that `clamp` will be called
127
+ # on the instance. This will expand any procs stored under "default" values. This
128
+ # is done because an environment variable may have been modified while loading
129
+ # configuration files.
118
130
  class Configuration
119
131
  include ConfigDefault
120
132
 
121
- def self.from_file(path)
122
- cfg = new
123
-
124
- DSL.new(cfg.options, cfg)._load_from path
125
-
126
- return cfg
127
- end
133
+ def initialize(user_options={}, default_options = {}, &block)
134
+ default_options = self.puma_default_options.merge(default_options)
128
135
 
129
- def initialize(options={}, &blk)
130
- @options = LeveledOptions.new(default_options)
131
- @plugins = PluginLoader.new
136
+ @options = UserFileDefaultOptions.new(user_options, default_options)
137
+ @plugins = PluginLoader.new
138
+ @user_dsl = DSL.new(@options.user_options, self)
139
+ @file_dsl = DSL.new(@options.file_options, self)
140
+ @default_dsl = DSL.new(@options.default_options, self)
132
141
 
133
- # options.each do |k,v|
134
- # @options[k] = v
135
- # end
142
+ if !@options[:prune_bundler]
143
+ default_options[:preload_app] = (@options[:workers] > 1) && Puma.forkable?
144
+ end
136
145
 
137
- if blk
138
- configure(&blk)
146
+ if block
147
+ configure(&block)
139
148
  end
140
149
  end
141
150
 
142
151
  attr_reader :options, :plugins
143
152
 
144
- def configure(&blk)
145
- @options.shift
146
- DSL.new(@options, self)._run(&blk)
153
+ def configure
154
+ yield @user_dsl, @file_dsl, @default_dsl
155
+ ensure
156
+ @user_dsl._offer_plugins
157
+ @file_dsl._offer_plugins
158
+ @default_dsl._offer_plugins
147
159
  end
148
160
 
149
161
  def initialize_copy(other)
150
- @conf = nil
162
+ @conf = nil
151
163
  @cli_options = nil
152
- @options = @options.dup
164
+ @options = @options.dup
153
165
  end
154
166
 
155
167
  def flatten
@@ -161,52 +173,56 @@ module Puma
161
173
  self
162
174
  end
163
175
 
164
- def default_options
176
+ def default_max_threads
177
+ Puma.mri? ? 5 : 16
178
+ end
179
+
180
+ def puma_default_options
165
181
  {
166
- :min_threads => 0,
167
- :max_threads => 16,
168
- :quiet => false,
182
+ :min_threads => Integer(ENV['PUMA_MIN_THREADS'] || ENV['MIN_THREADS'] || 0),
183
+ :max_threads => Integer(ENV['PUMA_MAX_THREADS'] || ENV['MAX_THREADS'] || default_max_threads),
184
+ :log_requests => false,
169
185
  :debug => false,
170
186
  :binds => ["tcp://#{DefaultTCPHost}:#{DefaultTCPPort}"],
171
- :workers => 0,
172
- :daemon => false,
187
+ :workers => Integer(ENV['WEB_CONCURRENCY'] || 0),
173
188
  :mode => :http,
174
189
  :worker_timeout => DefaultWorkerTimeout,
175
190
  :worker_boot_timeout => DefaultWorkerTimeout,
176
191
  :worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
177
192
  :remote_address => :socket,
178
193
  :tag => method(:infer_tag),
179
- :environment => lambda { ENV['RACK_ENV'] || "development" },
194
+ :environment => -> { ENV['RACK_ENV'] || ENV['RAILS_ENV'] || "development" },
180
195
  :rackup => DefaultRackup,
181
- :logger => STDOUT
196
+ :logger => STDOUT,
197
+ :persistent_timeout => Const::PERSISTENT_TIMEOUT,
198
+ :first_data_timeout => Const::FIRST_DATA_TIMEOUT,
199
+ :raise_exception_on_sigterm => true
182
200
  }
183
201
  end
184
202
 
185
203
  def load
186
- files = @options.all_of(:config_files)
204
+ config_files.each { |config_file| @file_dsl._load_from(config_file) }
187
205
 
188
- if files.empty?
189
- imp = %W(config/puma/#{@options[:environment]}.rb config/puma.rb).find { |f|
190
- File.exist?(f)
191
- }
206
+ @options
207
+ end
192
208
 
193
- files << imp
194
- elsif files == ["-"]
195
- files = []
196
- end
209
+ def config_files
210
+ files = @options.all_of(:config_files)
197
211
 
198
- files.each do |f|
199
- @options.shift
212
+ return [] if files == ['-']
213
+ return files if files.any?
200
214
 
201
- DSL.load @options, self, f
215
+ first_default_file = %W(config/puma/#{environment_str}.rb config/puma.rb).find do |f|
216
+ File.exist?(f)
202
217
  end
218
+
219
+ [first_default_file]
203
220
  end
204
221
 
205
222
  # Call once all configuration (included from rackup files)
206
223
  # is loaded to flesh out any defaults
207
224
  def clamp
208
- @options.shift
209
- @options.force_defaults
225
+ @options.finalize_values
210
226
  end
211
227
 
212
228
  # Injects the Configuration object into the env
@@ -238,14 +254,8 @@ module Puma
238
254
  def app
239
255
  found = options[:app] || load_rackup
240
256
 
241
- if @options[:mode] == :tcp
242
- require 'puma/tcp_logger'
243
-
244
- logger = @options[:logger]
245
- return TCPLogger.new(logger, found, @options[:quiet])
246
- end
247
-
248
- if !@options[:quiet] and @options[:environment] == "development"
257
+ if @options[:log_requests]
258
+ require 'puma/commonlogger'
249
259
  logger = @options[:logger]
250
260
  found = CommonLogger.new(found, logger)
251
261
  end
@@ -258,12 +268,23 @@ module Puma
258
268
  @options[:environment]
259
269
  end
260
270
 
271
+ def environment_str
272
+ environment.respond_to?(:call) ? environment.call : environment
273
+ end
274
+
261
275
  def load_plugin(name)
262
276
  @plugins.create name
263
277
  end
264
278
 
265
- def run_hooks(key, arg)
266
- @options.all_of(key).each { |b| b.call arg }
279
+ def run_hooks(key, arg, events)
280
+ @options.all_of(key).each do |b|
281
+ begin
282
+ b.call arg
283
+ rescue => e
284
+ events.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
285
+ events.debug e.backtrace.join("\n")
286
+ end
287
+ end
267
288
  end
268
289
 
269
290
  def self.temp_path
@@ -305,45 +326,23 @@ module Puma
305
326
  def load_rackup
306
327
  raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
307
328
 
308
- @options.shift
309
-
310
329
  rack_app, rack_options = rack_builder.parse_file(rackup)
311
- @options.merge!(rack_options)
330
+ @options.file_options.merge!(rack_options)
312
331
 
313
332
  config_ru_binds = []
314
333
  rack_options.each do |k, v|
315
334
  config_ru_binds << v if k.to_s.start_with?("bind")
316
335
  end
317
336
 
318
- @options[:binds] = config_ru_binds unless config_ru_binds.empty?
337
+ @options.file_options[:binds] = config_ru_binds unless config_ru_binds.empty?
319
338
 
320
339
  rack_app
321
340
  end
322
341
 
323
342
  def self.random_token
324
- begin
325
- require 'openssl'
326
- rescue LoadError
327
- end
328
-
329
- count = 16
330
-
331
- bytes = nil
332
-
333
- if defined? OpenSSL::Random
334
- bytes = OpenSSL::Random.random_bytes(count)
335
- elsif File.exist?("/dev/urandom")
336
- File.open('/dev/urandom') { |f| bytes = f.read(count) }
337
- end
338
-
339
- if bytes
340
- token = ""
341
- bytes.each_byte { |b| token << b.to_s(16) }
342
- else
343
- token = (0..count).to_a.map { rand(255).to_s(16) }.join
344
- end
343
+ require 'securerandom' unless defined?(SecureRandom)
345
344
 
346
- return token
345
+ SecureRandom.hex(16)
347
346
  end
348
347
  end
349
348
  end