puma 4.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.

Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +1532 -0
  3. data/LICENSE +26 -0
  4. data/README.md +291 -0
  5. data/bin/puma +10 -0
  6. data/bin/puma-wild +31 -0
  7. data/bin/pumactl +12 -0
  8. data/docs/architecture.md +37 -0
  9. data/docs/deployment.md +111 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/nginx.md +80 -0
  14. data/docs/plugins.md +38 -0
  15. data/docs/restart.md +41 -0
  16. data/docs/signals.md +96 -0
  17. data/docs/systemd.md +290 -0
  18. data/docs/tcp_mode.md +96 -0
  19. data/ext/puma_http11/PumaHttp11Service.java +19 -0
  20. data/ext/puma_http11/ext_help.h +15 -0
  21. data/ext/puma_http11/extconf.rb +28 -0
  22. data/ext/puma_http11/http11_parser.c +1044 -0
  23. data/ext/puma_http11/http11_parser.h +65 -0
  24. data/ext/puma_http11/http11_parser.java.rl +145 -0
  25. data/ext/puma_http11/http11_parser.rl +147 -0
  26. data/ext/puma_http11/http11_parser_common.rl +54 -0
  27. data/ext/puma_http11/io_buffer.c +155 -0
  28. data/ext/puma_http11/mini_ssl.c +553 -0
  29. data/ext/puma_http11/org/jruby/puma/Http11.java +226 -0
  30. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +455 -0
  31. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
  32. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +363 -0
  33. data/ext/puma_http11/puma_http11.c +502 -0
  34. data/lib/puma.rb +31 -0
  35. data/lib/puma/accept_nonblock.rb +29 -0
  36. data/lib/puma/app/status.rb +80 -0
  37. data/lib/puma/binder.rb +385 -0
  38. data/lib/puma/cli.rb +239 -0
  39. data/lib/puma/client.rb +494 -0
  40. data/lib/puma/cluster.rb +554 -0
  41. data/lib/puma/commonlogger.rb +108 -0
  42. data/lib/puma/configuration.rb +362 -0
  43. data/lib/puma/const.rb +235 -0
  44. data/lib/puma/control_cli.rb +289 -0
  45. data/lib/puma/detect.rb +15 -0
  46. data/lib/puma/dsl.rb +740 -0
  47. data/lib/puma/events.rb +156 -0
  48. data/lib/puma/io_buffer.rb +4 -0
  49. data/lib/puma/jruby_restart.rb +84 -0
  50. data/lib/puma/launcher.rb +475 -0
  51. data/lib/puma/minissl.rb +278 -0
  52. data/lib/puma/minissl/context_builder.rb +76 -0
  53. data/lib/puma/null_io.rb +44 -0
  54. data/lib/puma/plugin.rb +120 -0
  55. data/lib/puma/plugin/tmp_restart.rb +36 -0
  56. data/lib/puma/rack/builder.rb +301 -0
  57. data/lib/puma/rack/urlmap.rb +93 -0
  58. data/lib/puma/rack_default.rb +9 -0
  59. data/lib/puma/reactor.rb +400 -0
  60. data/lib/puma/runner.rb +192 -0
  61. data/lib/puma/server.rb +1030 -0
  62. data/lib/puma/single.rb +123 -0
  63. data/lib/puma/state_file.rb +31 -0
  64. data/lib/puma/tcp_logger.rb +41 -0
  65. data/lib/puma/thread_pool.rb +328 -0
  66. data/lib/puma/util.rb +124 -0
  67. data/lib/rack/handler/puma.rb +115 -0
  68. data/tools/docker/Dockerfile +16 -0
  69. data/tools/jungle/README.md +19 -0
  70. data/tools/jungle/init.d/README.md +61 -0
  71. data/tools/jungle/init.d/puma +421 -0
  72. data/tools/jungle/init.d/run-puma +18 -0
  73. data/tools/jungle/rc.d/README.md +74 -0
  74. data/tools/jungle/rc.d/puma +61 -0
  75. data/tools/jungle/rc.d/puma.conf +10 -0
  76. data/tools/jungle/upstart/README.md +61 -0
  77. data/tools/jungle/upstart/puma-manager.conf +31 -0
  78. data/tools/jungle/upstart/puma.conf +69 -0
  79. data/tools/trickletest.rb +44 -0
  80. metadata +144 -0
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma
4
+ # Rack::CommonLogger forwards every request to the given +app+, and
5
+ # logs a line in the
6
+ # {Apache common log format}[http://httpd.apache.org/docs/1.3/logs.html#common]
7
+ # to the +logger+.
8
+ #
9
+ # If +logger+ is nil, CommonLogger will fall back +rack.errors+, which is
10
+ # an instance of Rack::NullLogger.
11
+ #
12
+ # +logger+ can be any class, including the standard library Logger, and is
13
+ # expected to have either +write+ or +<<+ method, which accepts the CommonLogger::FORMAT.
14
+ # According to the SPEC, the error stream must also respond to +puts+
15
+ # (which takes a single argument that responds to +to_s+), and +flush+
16
+ # (which is called without arguments in order to make the error appear for
17
+ # sure)
18
+ class CommonLogger
19
+ # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
20
+ #
21
+ # lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 -
22
+ #
23
+ # %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
24
+ FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}
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
+
33
+ def initialize(app, logger=nil)
34
+ @app = app
35
+ @logger = logger
36
+ end
37
+
38
+ def call(env)
39
+ began_at = Time.now
40
+ status, header, body = @app.call(env)
41
+ header = Util::HeaderHash.new(header)
42
+
43
+ # If we've been hijacked, then output a special line
44
+ if env['rack.hijack_io']
45
+ log_hijacking(env, 'HIJACK', header, began_at)
46
+ else
47
+ ary = env['rack.after_reply']
48
+ ary << lambda { log(env, status, header, began_at) }
49
+ end
50
+
51
+ [status, header, body]
52
+ end
53
+
54
+ private
55
+
56
+ def log_hijacking(env, status, header, began_at)
57
+ now = Time.now
58
+
59
+ msg = HIJACK_FORMAT % [
60
+ env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
61
+ env["REMOTE_USER"] || "-",
62
+ now.strftime("%d/%b/%Y %H:%M:%S"),
63
+ env[REQUEST_METHOD],
64
+ env[PATH_INFO],
65
+ env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
66
+ env["HTTP_VERSION"],
67
+ now - began_at ]
68
+
69
+ write(msg)
70
+ end
71
+
72
+ def log(env, status, header, began_at)
73
+ now = Time.now
74
+ length = extract_content_length(header)
75
+
76
+ msg = FORMAT % [
77
+ env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
78
+ env["REMOTE_USER"] || "-",
79
+ now.strftime("%d/%b/%Y:%H:%M:%S %z"),
80
+ env[REQUEST_METHOD],
81
+ env[PATH_INFO],
82
+ env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
83
+ env["HTTP_VERSION"],
84
+ status.to_s[0..3],
85
+ length,
86
+ now - began_at ]
87
+
88
+ write(msg)
89
+ end
90
+
91
+ def write(msg)
92
+ logger = @logger || env['rack.errors']
93
+
94
+ # Standard library logger doesn't support write but it supports << which actually
95
+ # calls to write on the log device without formatting
96
+ if logger.respond_to?(:write)
97
+ logger.write(msg)
98
+ else
99
+ logger << msg
100
+ end
101
+ end
102
+
103
+ def extract_content_length(headers)
104
+ value = headers[CONTENT_LENGTH] or return '-'
105
+ value.to_s == '0' ? '-' : value
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,362 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma/rack/builder'
4
+ require 'puma/plugin'
5
+ require 'puma/const'
6
+
7
+ 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
+ # 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
55
+
56
+ def [](key)
57
+ return user_options[key] if user_options.key?(key)
58
+ return file_options[key] if file_options.key?(key)
59
+ return default_options[key] if default_options.key?(key)
60
+ end
61
+
62
+ def []=(key, value)
63
+ user_options[key] = value
64
+ end
65
+
66
+ def fetch(key, default_value = nil)
67
+ self[key] || default_value
68
+ end
69
+
70
+ def all_of(key)
71
+ user = user_options[key]
72
+ file = file_options[key]
73
+ default = default_options[key]
74
+
75
+ user = [user] unless user.is_a?(Array)
76
+ file = [file] unless file.is_a?(Array)
77
+ default = [default] unless default.is_a?(Array)
78
+
79
+ user.compact!
80
+ file.compact!
81
+ default.compact!
82
+
83
+ user + file + default
84
+ end
85
+
86
+ def finalize_values
87
+ @default_options.each do |k,v|
88
+ if v.respond_to? :call
89
+ @default_options[k] = v.call
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ # The main configuration class of Puma.
96
+ #
97
+ # It can be initialized with a set of "user" options and "default" options.
98
+ # Defaults will be merged with `Configuration.puma_default_options`.
99
+ #
100
+ # This class works together with 2 main other classes the `UserFileDefaultOptions`
101
+ # which stores configuration options in order so the precedence is that user
102
+ # set configuration wins over "file" based configuration wins over "default"
103
+ # configuration. These configurations are set via the `DSL` class. This
104
+ # class powers the Puma config file syntax and does double duty as a configuration
105
+ # DSL used by the `Puma::CLI` and Puma rack handler.
106
+ #
107
+ # It also handles loading plugins.
108
+ #
109
+ # > Note: `:port` and `:host` are not valid keys. By they time they make it to the
110
+ # configuration options they are expected to be incorporated into a `:binds` key.
111
+ # Under the hood the DSL maps `port` and `host` calls to `:binds`
112
+ #
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
+ #
120
+ # It is expected that `load` is called on the configuration instance after setting
121
+ # config. This method expands any values in `config_file` and puts them into the
122
+ # correct configuration option hash.
123
+ #
124
+ # Once all configuration is complete it is expected that `clamp` will be called
125
+ # on the instance. This will expand any procs stored under "default" values. This
126
+ # is done because an environment variable may have been modified while loading
127
+ # configuration files.
128
+ class Configuration
129
+ include ConfigDefault
130
+
131
+ def initialize(user_options={}, default_options = {}, &block)
132
+ default_options = self.puma_default_options.merge(default_options)
133
+
134
+ @options = UserFileDefaultOptions.new(user_options, default_options)
135
+ @plugins = PluginLoader.new
136
+ @user_dsl = DSL.new(@options.user_options, self)
137
+ @file_dsl = DSL.new(@options.file_options, self)
138
+ @default_dsl = DSL.new(@options.default_options, self)
139
+
140
+ if block
141
+ configure(&block)
142
+ end
143
+ end
144
+
145
+ attr_reader :options, :plugins
146
+
147
+ def configure
148
+ yield @user_dsl, @file_dsl, @default_dsl
149
+ ensure
150
+ @user_dsl._offer_plugins
151
+ @file_dsl._offer_plugins
152
+ @default_dsl._offer_plugins
153
+ end
154
+
155
+ def initialize_copy(other)
156
+ @conf = nil
157
+ @cli_options = nil
158
+ @options = @options.dup
159
+ end
160
+
161
+ def flatten
162
+ dup.flatten!
163
+ end
164
+
165
+ def flatten!
166
+ @options = @options.flatten
167
+ self
168
+ end
169
+
170
+ def puma_default_options
171
+ {
172
+ :min_threads => 0,
173
+ :max_threads => 16,
174
+ :log_requests => false,
175
+ :debug => false,
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,
190
+ :raise_exception_on_sigterm => true
191
+ }
192
+ end
193
+
194
+ def load
195
+ config_files.each { |config_file| @file_dsl._load_from(config_file) }
196
+
197
+ @options
198
+ end
199
+
200
+ def config_files
201
+ files = @options.all_of(:config_files)
202
+
203
+ return [] if files == ['-']
204
+ return files if files.any?
205
+
206
+ first_default_file = %W(config/puma/#{environment_str}.rb config/puma.rb).find do |f|
207
+ File.exist?(f)
208
+ end
209
+
210
+ [first_default_file]
211
+ end
212
+
213
+ # Call once all configuration (included from rackup files)
214
+ # is loaded to flesh out any defaults
215
+ def clamp
216
+ @options.finalize_values
217
+ end
218
+
219
+ # Injects the Configuration object into the env
220
+ class ConfigMiddleware
221
+ def initialize(config, app)
222
+ @config = config
223
+ @app = app
224
+ end
225
+
226
+ def call(env)
227
+ env[Const::PUMA_CONFIG] = @config
228
+ @app.call(env)
229
+ end
230
+ end
231
+
232
+ # Indicate if there is a properly configured app
233
+ #
234
+ def app_configured?
235
+ @options[:app] || File.exist?(rackup)
236
+ end
237
+
238
+ def rackup
239
+ @options[:rackup]
240
+ end
241
+
242
+ # Load the specified rackup file, pull options from
243
+ # the rackup file, and set @app.
244
+ #
245
+ def app
246
+ found = options[:app] || load_rackup
247
+
248
+ if @options[:mode] == :tcp
249
+ require 'puma/tcp_logger'
250
+
251
+ logger = @options[:logger]
252
+ quiet = !@options[:log_requests]
253
+ return TCPLogger.new(logger, found, quiet)
254
+ end
255
+
256
+ if @options[:log_requests]
257
+ require 'puma/commonlogger'
258
+ logger = @options[:logger]
259
+ found = CommonLogger.new(found, logger)
260
+ end
261
+
262
+ ConfigMiddleware.new(self, found)
263
+ end
264
+
265
+ # Return which environment we're running in
266
+ def environment
267
+ @options[:environment]
268
+ end
269
+
270
+ def environment_str
271
+ environment.respond_to?(:call) ? environment.call : environment
272
+ end
273
+
274
+ def load_plugin(name)
275
+ @plugins.create name
276
+ end
277
+
278
+ def run_hooks(key, arg)
279
+ @options.all_of(key).each { |b| b.call arg }
280
+ end
281
+
282
+ def self.temp_path
283
+ require 'tmpdir'
284
+
285
+ t = (Time.now.to_f * 1000).to_i
286
+ "#{Dir.tmpdir}/puma-status-#{t}-#{$$}"
287
+ end
288
+
289
+ private
290
+
291
+ def infer_tag
292
+ File.basename(Dir.getwd)
293
+ end
294
+
295
+ # Load and use the normal Rack builder if we can, otherwise
296
+ # fallback to our minimal version.
297
+ def rack_builder
298
+ # Load bundler now if we can so that we can pickup rack from
299
+ # a Gemfile
300
+ if ENV.key? 'PUMA_BUNDLER_PRUNED'
301
+ begin
302
+ require 'bundler/setup'
303
+ rescue LoadError
304
+ end
305
+ end
306
+
307
+ begin
308
+ require 'rack'
309
+ require 'rack/builder'
310
+ rescue LoadError
311
+ # ok, use builtin version
312
+ return Puma::Rack::Builder
313
+ else
314
+ return ::Rack::Builder
315
+ end
316
+ end
317
+
318
+ def load_rackup
319
+ raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
320
+
321
+ rack_app, rack_options = rack_builder.parse_file(rackup)
322
+ @options.file_options.merge!(rack_options)
323
+
324
+ config_ru_binds = []
325
+ rack_options.each do |k, v|
326
+ config_ru_binds << v if k.to_s.start_with?("bind")
327
+ end
328
+
329
+ @options.file_options[:binds] = config_ru_binds unless config_ru_binds.empty?
330
+
331
+ rack_app
332
+ end
333
+
334
+ def self.random_token
335
+ begin
336
+ require 'openssl'
337
+ rescue LoadError
338
+ end
339
+
340
+ count = 16
341
+
342
+ bytes = nil
343
+
344
+ if defined? OpenSSL::Random
345
+ bytes = OpenSSL::Random.random_bytes(count)
346
+ elsif File.exist?("/dev/urandom")
347
+ File.open('/dev/urandom') { |f| bytes = f.read(count) }
348
+ end
349
+
350
+ if bytes
351
+ token = "".dup
352
+ bytes.each_byte { |b| token << b.to_s(16) }
353
+ else
354
+ token = (0..count).to_a.map { rand(255).to_s(16) }.join
355
+ end
356
+
357
+ return token
358
+ end
359
+ end
360
+ end
361
+
362
+ require 'puma/dsl'