puma 2.16.0 → 3.11.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. checksums.yaml +5 -5
  2. data/{History.txt → History.md} +489 -70
  3. data/README.md +143 -174
  4. data/docs/architecture.md +36 -0
  5. data/{DEPLOYMENT.md → docs/deployment.md} +1 -1
  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/nginx.md +2 -2
  10. data/docs/plugins.md +28 -0
  11. data/docs/restart.md +39 -0
  12. data/docs/signals.md +56 -3
  13. data/docs/systemd.md +272 -0
  14. data/ext/puma_http11/extconf.rb +2 -0
  15. data/ext/puma_http11/http11_parser.c +291 -447
  16. data/ext/puma_http11/http11_parser.h +1 -0
  17. data/ext/puma_http11/http11_parser.java.rl +5 -5
  18. data/ext/puma_http11/http11_parser.rl +10 -9
  19. data/ext/puma_http11/http11_parser_common.rl +1 -1
  20. data/ext/puma_http11/io_buffer.c +8 -8
  21. data/ext/puma_http11/mini_ssl.c +64 -6
  22. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +113 -131
  23. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +9 -2
  24. data/ext/puma_http11/puma_http11.c +1 -0
  25. data/lib/puma/app/status.rb +9 -1
  26. data/lib/puma/binder.rb +90 -38
  27. data/lib/puma/cli.rb +134 -491
  28. data/lib/puma/client.rb +142 -4
  29. data/lib/puma/cluster.rb +132 -76
  30. data/lib/puma/commonlogger.rb +19 -20
  31. data/lib/puma/compat.rb +3 -7
  32. data/lib/puma/configuration.rb +206 -67
  33. data/lib/puma/const.rb +21 -31
  34. data/lib/puma/control_cli.rb +92 -103
  35. data/lib/puma/convenient.rb +23 -0
  36. data/lib/puma/daemon_ext.rb +6 -0
  37. data/lib/puma/detect.rb +10 -1
  38. data/lib/puma/dsl.rb +203 -45
  39. data/lib/puma/events.rb +22 -13
  40. data/lib/puma/io_buffer.rb +1 -1
  41. data/lib/puma/jruby_restart.rb +1 -2
  42. data/lib/puma/launcher.rb +431 -0
  43. data/lib/puma/minissl.rb +83 -4
  44. data/lib/puma/null_io.rb +19 -11
  45. data/lib/puma/plugin/tmp_restart.rb +34 -0
  46. data/lib/puma/plugin.rb +115 -0
  47. data/lib/puma/rack/backports/uri/common_193.rb +17 -13
  48. data/lib/puma/rack/builder.rb +3 -0
  49. data/lib/puma/rack/urlmap.rb +9 -8
  50. data/lib/puma/reactor.rb +18 -0
  51. data/lib/puma/runner.rb +43 -15
  52. data/lib/puma/server.rb +141 -35
  53. data/lib/puma/single.rb +16 -6
  54. data/lib/puma/state_file.rb +29 -0
  55. data/lib/puma/tcp_logger.rb +8 -1
  56. data/lib/puma/thread_pool.rb +60 -10
  57. data/lib/puma/util.rb +1 -5
  58. data/lib/puma.rb +13 -4
  59. data/lib/rack/handler/puma.rb +76 -29
  60. data/tools/jungle/README.md +12 -2
  61. data/tools/jungle/init.d/README.md +9 -2
  62. data/tools/jungle/init.d/puma +86 -59
  63. data/tools/jungle/init.d/run-puma +16 -1
  64. data/tools/jungle/rc.d/README.md +74 -0
  65. data/tools/jungle/rc.d/puma +61 -0
  66. data/tools/jungle/rc.d/puma.conf +10 -0
  67. data/tools/jungle/upstart/puma.conf +1 -1
  68. data/tools/trickletest.rb +1 -1
  69. metadata +28 -95
  70. data/COPYING +0 -55
  71. data/Gemfile +0 -13
  72. data/Manifest.txt +0 -74
  73. data/Rakefile +0 -158
  74. data/docs/config.md +0 -0
  75. data/lib/puma/capistrano.rb +0 -94
  76. data/lib/puma/rack/backports/uri/common_18.rb +0 -56
  77. data/lib/puma/rack/backports/uri/common_192.rb +0 -52
  78. data/puma.gemspec +0 -52
@@ -1,4 +1,6 @@
1
1
  require 'puma/rack/builder'
2
+ require 'puma/plugin'
3
+ require 'puma/const'
2
4
 
3
5
  module Puma
4
6
 
@@ -11,62 +13,204 @@ module Puma
11
13
  DefaultWorkerShutdownTimeout = 30
12
14
  end
13
15
 
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
53
+
54
+ def [](key)
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)
58
+ end
59
+
60
+ def []=(key, value)
61
+ user_options[key] = value
62
+ end
63
+
64
+ def fetch(key, default_value = nil)
65
+ self[key] || default_value
66
+ end
67
+
68
+ def all_of(key)
69
+ user = user_options[key]
70
+ file = file_options[key]
71
+ default = default_options[key]
72
+
73
+ user = [user] unless user.is_a?(Array)
74
+ file = [file] unless file.is_a?(Array)
75
+ default = [default] unless default.is_a?(Array)
76
+
77
+ user.compact!
78
+ file.compact!
79
+ default.compact!
80
+
81
+ user + file + default
82
+ end
83
+
84
+ def finalize_values
85
+ @default_options.each do |k,v|
86
+ if v.respond_to? :call
87
+ @default_options[k] = v.call
88
+ end
89
+ end
90
+ end
91
+ end
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.
14
126
  class Configuration
15
127
  include ConfigDefault
16
128
 
17
- def initialize(options)
18
- @cli_options = options
129
+ def initialize(user_options={}, default_options = {}, &block)
130
+ default_options = self.puma_default_options.merge(default_options)
19
131
 
20
- @conf = {}
21
- @conf[:mode] ||= :http
22
- @conf[:binds] ||= []
23
- @conf[:on_restart] ||= []
24
- @conf[:before_fork] ||= []
25
- @conf[:before_worker_shutdown] ||= []
26
- @conf[:before_worker_boot] ||= []
27
- @conf[:before_worker_fork] ||= []
28
- @conf[:after_worker_boot] ||= []
29
- @conf[:worker_timeout] ||= DefaultWorkerTimeout
30
- @conf[:worker_boot_timeout] ||= @conf[:worker_timeout]
31
- @conf[:worker_shutdown_timeout] ||= DefaultWorkerShutdownTimeout
32
- @conf[:remote_address] ||= :socket
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)
33
137
 
34
- @options = {}
138
+ if block
139
+ configure(&block)
140
+ end
35
141
  end
36
142
 
37
- attr_reader :options
143
+ attr_reader :options, :plugins
144
+
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
151
+ end
38
152
 
39
153
  def initialize_copy(other)
40
- @conf = nil
154
+ @conf = nil
41
155
  @cli_options = nil
42
- @options = @options.dup
156
+ @options = @options.dup
157
+ end
158
+
159
+ def flatten
160
+ dup.flatten!
161
+ end
162
+
163
+ def flatten!
164
+ @options = @options.flatten
165
+ self
43
166
  end
44
167
 
45
- def default_options
168
+ def puma_default_options
46
169
  {
47
170
  :min_threads => 0,
48
171
  :max_threads => 16,
49
- :quiet => false,
172
+ :log_requests => false,
50
173
  :debug => false,
51
- :binds => [],
174
+ :binds => ["tcp://#{DefaultTCPHost}:#{DefaultTCPPort}"],
52
175
  :workers => 0,
53
176
  :daemon => false,
177
+ :mode => :http,
178
+ :worker_timeout => DefaultWorkerTimeout,
179
+ :worker_boot_timeout => DefaultWorkerTimeout,
180
+ :worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
181
+ :remote_address => :socket,
182
+ :tag => method(:infer_tag),
183
+ :environment => -> { ENV['RACK_ENV'] || "development" },
184
+ :rackup => DefaultRackup,
185
+ :logger => STDOUT,
186
+ :persistent_timeout => Const::PERSISTENT_TIMEOUT,
187
+ :first_data_timeout => Const::FIRST_DATA_TIMEOUT
54
188
  }
55
189
  end
56
190
 
57
191
  def load
58
- @conf.merge! @cli_options
59
- DSL.load(@conf, @cli_options[:config_file])
192
+ config_files.each { |config_file| @file_dsl._load_from(config_file) }
60
193
 
61
- # Load the options in the right priority
62
- #
63
- @options.merge! default_options
64
- @options.merge! @conf
65
- @options.merge! @cli_options
194
+ @options
195
+ end
196
+
197
+ def config_files
198
+ files = @options.all_of(:config_files)
199
+
200
+ return [] if files == ['-']
201
+ return files if files.any?
202
+
203
+ first_default_file = %W(config/puma/#{environment_str}.rb config/puma.rb).find do |f|
204
+ File.exist?(f)
205
+ end
206
+
207
+ [first_default_file]
208
+ end
66
209
 
67
- setup_binds
68
- setup_control
69
- @options[:tag] ||= infer_tag
210
+ # Call once all configuration (included from rackup files)
211
+ # is loaded to flesh out any defaults
212
+ def clamp
213
+ @options.finalize_values
70
214
  end
71
215
 
72
216
  # Injects the Configuration object into the env
@@ -89,7 +233,7 @@ module Puma
89
233
  end
90
234
 
91
235
  def rackup
92
- @options[:rackup] || DefaultRackup
236
+ @options[:rackup]
93
237
  end
94
238
 
95
239
  # Load the specified rackup file, pull options from
@@ -101,18 +245,37 @@ module Puma
101
245
  if @options[:mode] == :tcp
102
246
  require 'puma/tcp_logger'
103
247
 
104
- logger = @options[:logger] || STDOUT
105
- return TCPLogger.new(logger, found, @options[:quiet])
248
+ logger = @options[:logger]
249
+ quiet = !@options[:log_requests]
250
+ return TCPLogger.new(logger, found, quiet)
106
251
  end
107
252
 
108
- if !@options[:quiet] and @options[:environment] == "development"
109
- logger = @options[:logger] || STDOUT
253
+ if @options[:log_requests]
254
+ require 'puma/commonlogger'
255
+ logger = @options[:logger]
110
256
  found = CommonLogger.new(found, logger)
111
257
  end
112
258
 
113
259
  ConfigMiddleware.new(self, found)
114
260
  end
115
261
 
262
+ # Return which environment we're running in
263
+ def environment
264
+ @options[:environment]
265
+ end
266
+
267
+ def environment_str
268
+ environment.respond_to?(:call) ? environment.call : environment
269
+ end
270
+
271
+ def load_plugin(name)
272
+ @plugins.create name
273
+ end
274
+
275
+ def run_hooks(key, arg)
276
+ @options.all_of(key).each { |b| b.call arg }
277
+ end
278
+
116
279
  def self.temp_path
117
280
  require 'tmpdir'
118
281
 
@@ -153,43 +316,19 @@ module Puma
153
316
  raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
154
317
 
155
318
  rack_app, rack_options = rack_builder.parse_file(rackup)
156
- @options.merge!(rack_options)
319
+ @options.file_options.merge!(rack_options)
157
320
 
158
321
  config_ru_binds = []
159
322
  rack_options.each do |k, v|
160
323
  config_ru_binds << v if k.to_s.start_with?("bind")
161
324
  end
162
- @options[:binds] = config_ru_binds unless config_ru_binds.empty?
163
325
 
164
- rack_app
165
- end
326
+ @options.file_options[:binds] = config_ru_binds unless config_ru_binds.empty?
166
327
 
167
- def setup_binds
168
- # Rakeup default option support
169
- host = @options[:Host]
170
- if host
171
- port = @options[:Port] || DefaultTCPPort
172
- @options[:binds] << "tcp://#{host}:#{port}"
173
- end
174
-
175
- if @options[:binds].empty?
176
- @options[:binds] << "tcp://#{DefaultTCPHost}:#{DefaultTCPPort}"
177
- end
178
-
179
- @options[:binds].uniq!
180
- end
181
-
182
- def setup_control
183
- if @options[:control_url] == 'auto'
184
- path = Configuration.temp_path
185
- @options[:control_url] = "unix://#{path}"
186
- @options[:control_url_temp] = path
187
- end
188
-
189
- setup_random_token unless @options[:control_auth_token]
328
+ rack_app
190
329
  end
191
330
 
192
- def setup_random_token
331
+ def self.random_token
193
332
  begin
194
333
  require 'openssl'
195
334
  rescue LoadError
@@ -206,13 +345,13 @@ module Puma
206
345
  end
207
346
 
208
347
  if bytes
209
- token = ""
348
+ token = "".dup
210
349
  bytes.each_byte { |b| token << b.to_s(16) }
211
350
  else
212
351
  token = (0..count).to_a.map { rand(255).to_s(16) }.join
213
352
  end
214
353
 
215
- @options[:control_auth_token] = token
354
+ return token
216
355
  end
217
356
  end
218
357
  end
data/lib/puma/const.rb CHANGED
@@ -1,3 +1,4 @@
1
+ #encoding: utf-8
1
2
  module Puma
2
3
  class UnsupportedOption < RuntimeError
3
4
  end
@@ -52,6 +53,8 @@ module Puma
52
53
  415 => 'Unsupported Media Type',
53
54
  416 => 'Range Not Satisfiable',
54
55
  417 => 'Expectation Failed',
56
+ 418 => 'I\'m A Teapot',
57
+ 421 => 'Misdirected Request',
55
58
  422 => 'Unprocessable Entity',
56
59
  423 => 'Locked',
57
60
  424 => 'Failed Dependency',
@@ -59,6 +62,7 @@ module Puma
59
62
  428 => 'Precondition Required',
60
63
  429 => 'Too Many Requests',
61
64
  431 => 'Request Header Fields Too Large',
65
+ 451 => 'Unavailable For Legal Reasons',
62
66
  500 => 'Internal Server Error',
63
67
  501 => 'Not Implemented',
64
68
  502 => 'Bad Gateway',
@@ -72,19 +76,14 @@ module Puma
72
76
  511 => 'Network Authentication Required'
73
77
  }
74
78
 
75
- SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
76
- [message.downcase.gsub(/\s|-|'/, '_').to_sym, code]
77
- }.flatten]
78
-
79
79
  # For some HTTP status codes the client only expects headers.
80
80
  #
81
81
 
82
- no_body = {}
83
- ((100..199).to_a << 204 << 205 << 304).each do |code|
84
- no_body[code] = true
85
- end
86
-
87
- STATUS_WITH_NO_ENTITY_BODY = no_body
82
+ STATUS_WITH_NO_ENTITY_BODY = {
83
+ 204 => true,
84
+ 205 => true,
85
+ 304 => true
86
+ }
88
87
 
89
88
  # Frequently used constants when constructing requests or responses. Many times
90
89
  # the constant just refers to a string with the same contents. Using these constants
@@ -99,8 +98,9 @@ module Puma
99
98
  # too taxing on performance.
100
99
  module Const
101
100
 
102
- PUMA_VERSION = VERSION = "2.16.0".freeze
103
- CODE_NAME = "Midwinter Nights Trance".freeze
101
+ PUMA_VERSION = VERSION = "3.11.4".freeze
102
+ CODE_NAME = "Love Song".freeze
103
+ PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
104
104
 
105
105
  FAST_TRACK_KA_TIMEOUT = 0.2
106
106
 
@@ -116,13 +116,10 @@ module Puma
116
116
  # sending data back
117
117
  WRITE_TIMEOUT = 10
118
118
 
119
- DATE = "Date".freeze
120
-
121
- SCRIPT_NAME = "SCRIPT_NAME".freeze
122
-
123
119
  # The original URI requested by the client.
124
120
  REQUEST_URI= 'REQUEST_URI'.freeze
125
121
  REQUEST_PATH = 'REQUEST_PATH'.freeze
122
+ QUERY_STRING = 'QUERY_STRING'.freeze
126
123
 
127
124
  PATH_INFO = 'PATH_INFO'.freeze
128
125
 
@@ -155,26 +152,12 @@ module Puma
155
152
  # Maximum request body size before it is moved out of memory and into a tempfile for reading.
156
153
  MAX_BODY = MAX_HEADER
157
154
 
158
- # A frozen format for this is about 15% faster
159
- STATUS_FORMAT = "HTTP/1.1 %d %s\r\nConnection: close\r\n".freeze
160
-
161
- CONTENT_TYPE = "Content-Type".freeze
162
-
163
- LAST_MODIFIED = "Last-Modified".freeze
164
- ETAG = "ETag".freeze
165
- SLASH = "/".freeze
166
155
  REQUEST_METHOD = "REQUEST_METHOD".freeze
167
- GET = "GET".freeze
168
156
  HEAD = "HEAD".freeze
169
157
  # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
170
- ETAG_FORMAT = "\"%x-%x-%x\"".freeze
171
158
  LINE_END = "\r\n".freeze
172
159
  REMOTE_ADDR = "REMOTE_ADDR".freeze
173
160
  HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR".freeze
174
- HTTP_IF_MODIFIED_SINCE = "HTTP_IF_MODIFIED_SINCE".freeze
175
- HTTP_IF_NONE_MATCH = "HTTP_IF_NONE_MATCH".freeze
176
- REDIRECT = "HTTP/1.1 302 Found\r\nLocation: %s\r\nConnection: close\r\n\r\n".freeze
177
- HOST = "HOST".freeze
178
161
 
179
162
  SERVER_NAME = "SERVER_NAME".freeze
180
163
  SERVER_PORT = "SERVER_PORT".freeze
@@ -187,7 +170,6 @@ module Puma
187
170
 
188
171
  SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
189
172
  HTTP_11 = "HTTP/1.1".freeze
190
- HTTP_10 = "HTTP/1.0".freeze
191
173
 
192
174
  SERVER_SOFTWARE = "SERVER_SOFTWARE".freeze
193
175
  GATEWAY_INTERFACE = "GATEWAY_INTERFACE".freeze
@@ -211,7 +193,10 @@ module Puma
211
193
 
212
194
  HTTP_VERSION = "HTTP_VERSION".freeze
213
195
  HTTP_CONNECTION = "HTTP_CONNECTION".freeze
196
+ HTTP_EXPECT = "HTTP_EXPECT".freeze
197
+ CONTINUE = "100-continue".freeze
214
198
 
199
+ HTTP_11_100 = "HTTP/1.1 100 Continue\r\n\r\n".freeze
215
200
  HTTP_11_200 = "HTTP/1.1 200 OK\r\n".freeze
216
201
  HTTP_10_200 = "HTTP/1.0 200 OK\r\n".freeze
217
202
 
@@ -221,6 +206,7 @@ module Puma
221
206
  CONTENT_LENGTH2 = "content-length".freeze
222
207
  CONTENT_LENGTH_S = "Content-Length: ".freeze
223
208
  TRANSFER_ENCODING = "transfer-encoding".freeze
209
+ TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING".freeze
224
210
 
225
211
  CONNECTION_CLOSE = "Connection: close\r\n".freeze
226
212
  CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n".freeze
@@ -228,6 +214,8 @@ module Puma
228
214
  TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n".freeze
229
215
  CLOSE_CHUNKED = "0\r\n\r\n".freeze
230
216
 
217
+ CHUNKED = "chunked".freeze
218
+
231
219
  COLON = ": ".freeze
232
220
 
233
221
  NEWLINE = "\n".freeze
@@ -235,5 +223,7 @@ module Puma
235
223
  HIJACK_P = "rack.hijack?".freeze
236
224
  HIJACK = "rack.hijack".freeze
237
225
  HIJACK_IO = "rack.hijack_io".freeze
226
+
227
+ EARLY_HINTS = "rack.early_hints".freeze
238
228
  end
239
229
  end