puma 3.12.6 → 6.2.2

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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1775 -451
  3. data/LICENSE +23 -20
  4. data/README.md +193 -65
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +59 -21
  7. data/docs/compile_options.md +55 -0
  8. data/docs/deployment.md +69 -58
  9. data/docs/fork_worker.md +31 -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/jungle/README.md +9 -0
  14. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  15. data/{tools → docs}/jungle/rc.d/puma +2 -2
  16. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  17. data/docs/kubernetes.md +66 -0
  18. data/docs/nginx.md +2 -2
  19. data/docs/plugins.md +22 -12
  20. data/docs/rails_dev_mode.md +28 -0
  21. data/docs/restart.md +47 -22
  22. data/docs/signals.md +13 -11
  23. data/docs/stats.md +142 -0
  24. data/docs/systemd.md +94 -120
  25. data/docs/testing_benchmarks_local_files.md +150 -0
  26. data/docs/testing_test_rackup_ci_files.md +36 -0
  27. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  28. data/ext/puma_http11/ext_help.h +1 -1
  29. data/ext/puma_http11/extconf.rb +61 -3
  30. data/ext/puma_http11/http11_parser.c +103 -117
  31. data/ext/puma_http11/http11_parser.h +2 -2
  32. data/ext/puma_http11/http11_parser.java.rl +22 -38
  33. data/ext/puma_http11/http11_parser.rl +3 -3
  34. data/ext/puma_http11/http11_parser_common.rl +6 -6
  35. data/ext/puma_http11/mini_ssl.c +361 -99
  36. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  38. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
  39. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +248 -92
  40. data/ext/puma_http11/puma_http11.c +49 -57
  41. data/lib/puma/app/status.rb +71 -49
  42. data/lib/puma/binder.rb +242 -150
  43. data/lib/puma/cli.rb +38 -34
  44. data/lib/puma/client.rb +387 -244
  45. data/lib/puma/cluster/worker.rb +180 -0
  46. data/lib/puma/cluster/worker_handle.rb +97 -0
  47. data/lib/puma/cluster.rb +261 -243
  48. data/lib/puma/commonlogger.rb +21 -14
  49. data/lib/puma/configuration.rb +116 -88
  50. data/lib/puma/const.rb +101 -100
  51. data/lib/puma/control_cli.rb +115 -70
  52. data/lib/puma/detect.rb +33 -2
  53. data/lib/puma/dsl.rb +731 -134
  54. data/lib/puma/error_logger.rb +113 -0
  55. data/lib/puma/events.rb +16 -112
  56. data/lib/puma/io_buffer.rb +42 -5
  57. data/lib/puma/jruby_restart.rb +2 -59
  58. data/lib/puma/json_serialization.rb +96 -0
  59. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  60. data/lib/puma/launcher.rb +184 -133
  61. data/lib/puma/log_writer.rb +147 -0
  62. data/lib/puma/minissl/context_builder.rb +92 -0
  63. data/lib/puma/minissl.rb +246 -70
  64. data/lib/puma/null_io.rb +18 -1
  65. data/lib/puma/plugin/systemd.rb +90 -0
  66. data/lib/puma/plugin/tmp_restart.rb +3 -1
  67. data/lib/puma/plugin.rb +7 -13
  68. data/lib/puma/rack/builder.rb +7 -9
  69. data/lib/puma/rack/urlmap.rb +2 -0
  70. data/lib/puma/rack_default.rb +21 -4
  71. data/lib/puma/reactor.rb +85 -316
  72. data/lib/puma/request.rb +665 -0
  73. data/lib/puma/runner.rb +94 -69
  74. data/lib/puma/sd_notify.rb +149 -0
  75. data/lib/puma/server.rb +314 -771
  76. data/lib/puma/single.rb +20 -74
  77. data/lib/puma/state_file.rb +45 -8
  78. data/lib/puma/thread_pool.rb +142 -92
  79. data/lib/puma/util.rb +22 -10
  80. data/lib/puma.rb +60 -5
  81. data/lib/rack/handler/puma.rb +113 -91
  82. data/tools/Dockerfile +16 -0
  83. data/tools/trickletest.rb +0 -1
  84. metadata +54 -32
  85. data/ext/puma_http11/io_buffer.c +0 -155
  86. data/lib/puma/accept_nonblock.rb +0 -23
  87. data/lib/puma/compat.rb +0 -14
  88. data/lib/puma/convenient.rb +0 -25
  89. data/lib/puma/daemon_ext.rb +0 -33
  90. data/lib/puma/delegation.rb +0 -13
  91. data/lib/puma/java_io_buffer.rb +0 -47
  92. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  93. data/lib/puma/tcp_logger.rb +0 -41
  94. data/tools/jungle/README.md +0 -19
  95. data/tools/jungle/init.d/README.md +0 -61
  96. data/tools/jungle/init.d/puma +0 -421
  97. data/tools/jungle/init.d/run-puma +0 -18
  98. data/tools/jungle/upstart/README.md +0 -61
  99. data/tools/jungle/upstart/puma-manager.conf +0 -31
  100. data/tools/jungle/upstart/puma.conf +0 -69
@@ -3,7 +3,7 @@
3
3
  module Puma
4
4
  # Rack::CommonLogger forwards every request to the given +app+, and
5
5
  # logs a line in the
6
- # {Apache common log format}[http://httpd.apache.org/docs/1.3/logs.html#common]
6
+ # {Apache common log format}[https://httpd.apache.org/docs/2.4/logs.html#common]
7
7
  # to the +logger+.
8
8
  #
9
9
  # If +logger+ is nil, CommonLogger will fall back +rack.errors+, which is
@@ -16,7 +16,7 @@ module Puma
16
16
  # (which is called without arguments in order to make the error appear for
17
17
  # sure)
18
18
  class CommonLogger
19
- # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
19
+ # Common Log Format: https://httpd.apache.org/docs/2.4/logs.html#common
20
20
  #
21
21
  # lilith.local - - [07/Aug/2006 23:58:02 -0400] "GET / HTTP/1.1" 500 -
22
22
  #
@@ -25,10 +25,17 @@ module Puma
25
25
 
26
26
  HIJACK_FORMAT = %{%s - %s [%s] "%s %s%s %s" HIJACKED -1 %0.4f\n}
27
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
28
+ LOG_TIME_FORMAT = '%d/%b/%Y:%H:%M:%S %z'
29
+
30
+ CONTENT_LENGTH = 'Content-Length' # should be lower case from app,
31
+ # Util::HeaderHash allows mixed
32
+ HTTP_VERSION = Const::HTTP_VERSION
33
+ HTTP_X_FORWARDED_FOR = Const::HTTP_X_FORWARDED_FOR
34
+ PATH_INFO = Const::PATH_INFO
35
+ QUERY_STRING = Const::QUERY_STRING
36
+ REMOTE_ADDR = Const::REMOTE_ADDR
37
+ REMOTE_USER = 'REMOTE_USER'
38
+ REQUEST_METHOD = Const::REQUEST_METHOD
32
39
 
33
40
  def initialize(app, logger=nil)
34
41
  @app = app
@@ -57,13 +64,13 @@ module Puma
57
64
  now = Time.now
58
65
 
59
66
  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"),
67
+ env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-",
68
+ env[REMOTE_USER] || "-",
69
+ now.strftime(LOG_TIME_FORMAT),
63
70
  env[REQUEST_METHOD],
64
71
  env[PATH_INFO],
65
72
  env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
66
- env["HTTP_VERSION"],
73
+ env[HTTP_VERSION],
67
74
  now - began_at ]
68
75
 
69
76
  write(msg)
@@ -74,13 +81,13 @@ module Puma
74
81
  length = extract_content_length(header)
75
82
 
76
83
  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"),
84
+ env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR] || "-",
85
+ env[REMOTE_USER] || "-",
86
+ now.strftime(LOG_TIME_FORMAT),
80
87
  env[REQUEST_METHOD],
81
88
  env[PATH_INFO],
82
89
  env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
83
- env["HTTP_VERSION"],
90
+ env[HTTP_VERSION],
84
91
  status.to_s[0..3],
85
92
  length,
86
93
  now - began_at ]
@@ -1,26 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'puma/rack/builder'
4
- require 'puma/plugin'
5
- require 'puma/const'
3
+ require_relative 'rack/builder'
4
+ require_relative 'plugin'
5
+ require_relative 'const'
6
+ # note that dsl is loaded at end of file, requires ConfigDefault constants
6
7
 
7
8
  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
9
  # A class used for storing "leveled" configuration options.
19
10
  #
20
11
  # In this class any "user" specified options take precedence over any
21
12
  # "file" specified options, take precedence over any "default" options.
22
13
  #
23
- # User input is prefered over "defaults":
14
+ # User input is preferred over "defaults":
24
15
  # user_options = { foo: "bar" }
25
16
  # default_options = { foo: "zoo" }
26
17
  # options = UserFileDefaultOptions.new(user_options, default_options)
@@ -32,7 +23,7 @@ module Puma
32
23
  # puts options.all_of(:foo)
33
24
  # # => ["bar", "zoo"]
34
25
  #
35
- # A "file" option can be set. This config will be prefered over "default" options
26
+ # A "file" option can be set. This config will be preferred over "default" options
36
27
  # but will defer to any available "user" specified options.
37
28
  #
38
29
  # user_options = { foo: "bar" }
@@ -54,9 +45,7 @@ module Puma
54
45
  attr_reader :user_options, :file_options, :default_options
55
46
 
56
47
  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)
48
+ fetch(key)
60
49
  end
61
50
 
62
51
  def []=(key, value)
@@ -64,7 +53,11 @@ module Puma
64
53
  end
65
54
 
66
55
  def fetch(key, default_value = nil)
67
- self[key] || default_value
56
+ return user_options[key] if user_options.key?(key)
57
+ return file_options[key] if file_options.key?(key)
58
+ return default_options[key] if default_options.key?(key)
59
+
60
+ default_value
68
61
  end
69
62
 
70
63
  def all_of(key)
@@ -90,6 +83,12 @@ module Puma
90
83
  end
91
84
  end
92
85
  end
86
+
87
+ def final_options
88
+ default_options
89
+ .merge(file_options)
90
+ .merge(user_options)
91
+ end
93
92
  end
94
93
 
95
94
  # The main configuration class of Puma.
@@ -106,16 +105,17 @@ module Puma
106
105
  #
107
106
  # It also handles loading plugins.
108
107
  #
109
- # > Note: `:port` and `:host` are not valid keys. By they time they make it to the
108
+ # [Note:]
109
+ # `:port` and `:host` are not valid keys. By the time they make it to the
110
110
  # configuration options they are expected to be incorporated into a `:binds` key.
111
111
  # Under the hood the DSL maps `port` and `host` calls to `:binds`
112
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
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
119
  #
120
120
  # It is expected that `load` is called on the configuration instance after setting
121
121
  # config. This method expands any values in `config_file` and puts them into the
@@ -126,7 +126,50 @@ module Puma
126
126
  # is done because an environment variable may have been modified while loading
127
127
  # configuration files.
128
128
  class Configuration
129
- include ConfigDefault
129
+ DEFAULTS = {
130
+ auto_trim_time: 30,
131
+ binds: ['tcp://0.0.0.0:9292'.freeze],
132
+ clean_thread_locals: false,
133
+ debug: false,
134
+ early_hints: nil,
135
+ environment: 'development'.freeze,
136
+ # Number of seconds to wait until we get the first data for the request
137
+ first_data_timeout: 30,
138
+ io_selector_backend: :auto,
139
+ log_requests: false,
140
+ logger: STDOUT,
141
+ # How many requests to attempt inline before sending a client back to
142
+ # the reactor to be subject to normal ordering. The idea here is that
143
+ # we amortize the cost of going back to the reactor for a well behaved
144
+ # but very "greedy" client across 10 requests. This prevents a not
145
+ # well behaved client from monopolizing the thread forever.
146
+ max_fast_inline: 10,
147
+ max_threads: Puma.mri? ? 5 : 16,
148
+ min_threads: 0,
149
+ mode: :http,
150
+ mutate_stdout_and_stderr_to_sync_on_write: true,
151
+ out_of_band: [],
152
+ # Number of seconds for another request within a persistent session.
153
+ persistent_timeout: 20,
154
+ queue_requests: true,
155
+ rackup: 'config.ru'.freeze,
156
+ raise_exception_on_sigterm: true,
157
+ reaping_time: 1,
158
+ remote_address: :socket,
159
+ silence_single_worker_warning: false,
160
+ silence_fork_callback_warning: false,
161
+ tag: File.basename(Dir.getwd),
162
+ tcp_host: '0.0.0.0'.freeze,
163
+ tcp_port: 9292,
164
+ wait_for_less_busy_worker: 0.005,
165
+ worker_boot_timeout: 60,
166
+ worker_check_interval: 5,
167
+ worker_culling_strategy: :youngest,
168
+ worker_shutdown_timeout: 30,
169
+ worker_timeout: 60,
170
+ workers: 0,
171
+ http_content_length_limit: nil
172
+ }
130
173
 
131
174
  def initialize(user_options={}, default_options = {}, &block)
132
175
  default_options = self.puma_default_options.merge(default_options)
@@ -137,6 +180,10 @@ module Puma
137
180
  @file_dsl = DSL.new(@options.file_options, self)
138
181
  @default_dsl = DSL.new(@options.default_options, self)
139
182
 
183
+ if !@options[:prune_bundler]
184
+ default_options[:preload_app] = (@options[:workers] > 1) && Puma.forkable?
185
+ end
186
+
140
187
  if block
141
188
  configure(&block)
142
189
  end
@@ -168,25 +215,21 @@ module Puma
168
215
  end
169
216
 
170
217
  def puma_default_options
218
+ defaults = DEFAULTS.dup
219
+ puma_options_from_env.each { |k,v| defaults[k] = v if v }
220
+ defaults
221
+ end
222
+
223
+ def puma_options_from_env
224
+ min = ENV['PUMA_MIN_THREADS'] || ENV['MIN_THREADS']
225
+ max = ENV['PUMA_MAX_THREADS'] || ENV['MAX_THREADS']
226
+ workers = ENV['WEB_CONCURRENCY']
227
+
171
228
  {
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
229
+ min_threads: min && Integer(min),
230
+ max_threads: max && Integer(max),
231
+ workers: workers && Integer(workers),
232
+ environment: ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'],
190
233
  }
191
234
  end
192
235
 
@@ -202,7 +245,7 @@ module Puma
202
245
  return [] if files == ['-']
203
246
  return files if files.any?
204
247
 
205
- first_default_file = %W(config/puma/#{environment_str}.rb config/puma.rb).find do |f|
248
+ first_default_file = %W(config/puma/#{@options[:environment]}.rb config/puma.rb).find do |f|
206
249
  File.exist?(f)
207
250
  end
208
251
 
@@ -244,16 +287,8 @@ module Puma
244
287
  def app
245
288
  found = options[:app] || load_rackup
246
289
 
247
- if @options[:mode] == :tcp
248
- require 'puma/tcp_logger'
249
-
250
- logger = @options[:logger]
251
- quiet = !@options[:log_requests]
252
- return TCPLogger.new(logger, found, quiet)
253
- end
254
-
255
290
  if @options[:log_requests]
256
- require 'puma/commonlogger'
291
+ require_relative 'commonlogger'
257
292
  logger = @options[:logger]
258
293
  found = CommonLogger.new(found, logger)
259
294
  end
@@ -266,16 +301,31 @@ module Puma
266
301
  @options[:environment]
267
302
  end
268
303
 
269
- def environment_str
270
- environment.respond_to?(:call) ? environment.call : environment
271
- end
272
-
273
304
  def load_plugin(name)
274
305
  @plugins.create name
275
306
  end
276
307
 
277
- def run_hooks(key, arg)
278
- @options.all_of(key).each { |b| b.call arg }
308
+ # @param key [:Symbol] hook to run
309
+ # @param arg [Launcher, Int] `:on_restart` passes Launcher
310
+ #
311
+ def run_hooks(key, arg, log_writer, hook_data = nil)
312
+ @options.all_of(key).each do |b|
313
+ begin
314
+ if Array === b
315
+ hook_data[b[1]] ||= Hash.new
316
+ b[0].call arg, hook_data[b[1]]
317
+ else
318
+ b.call arg
319
+ end
320
+ rescue => e
321
+ log_writer.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
322
+ log_writer.debug e.backtrace.join("\n")
323
+ end
324
+ end
325
+ end
326
+
327
+ def final_options
328
+ @options.final_options
279
329
  end
280
330
 
281
331
  def self.temp_path
@@ -287,10 +337,6 @@ module Puma
287
337
 
288
338
  private
289
339
 
290
- def infer_tag
291
- File.basename(Dir.getwd)
292
- end
293
-
294
340
  # Load and use the normal Rack builder if we can, otherwise
295
341
  # fallback to our minimal version.
296
342
  def rack_builder
@@ -318,6 +364,8 @@ module Puma
318
364
  raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
319
365
 
320
366
  rack_app, rack_options = rack_builder.parse_file(rackup)
367
+ rack_options = rack_options || {}
368
+
321
369
  @options.file_options.merge!(rack_options)
322
370
 
323
371
  config_ru_binds = []
@@ -331,31 +379,11 @@ module Puma
331
379
  end
332
380
 
333
381
  def self.random_token
334
- begin
335
- require 'openssl'
336
- rescue LoadError
337
- end
338
-
339
- count = 16
340
-
341
- bytes = nil
342
-
343
- if defined? OpenSSL::Random
344
- bytes = OpenSSL::Random.random_bytes(count)
345
- elsif File.exist?("/dev/urandom")
346
- File.open('/dev/urandom') { |f| bytes = f.read(count) }
347
- end
348
-
349
- if bytes
350
- token = "".dup
351
- bytes.each_byte { |b| token << b.to_s(16) }
352
- else
353
- token = (0..count).to_a.map { rand(255).to_s(16) }.join
354
- end
382
+ require 'securerandom' unless defined?(SecureRandom)
355
383
 
356
- return token
384
+ SecureRandom.hex(16)
357
385
  end
358
386
  end
359
387
  end
360
388
 
361
- require 'puma/dsl'
389
+ require_relative 'dsl'
data/lib/puma/const.rb CHANGED
@@ -5,7 +5,6 @@ module Puma
5
5
  class UnsupportedOption < RuntimeError
6
6
  end
7
7
 
8
-
9
8
  # Every standard HTTP code mapped to the appropriate message. These are
10
9
  # used so frequently that they are placed directly in Puma for easy
11
10
  # access rather than Puma::Const itself.
@@ -76,7 +75,7 @@ module Puma
76
75
  508 => 'Loop Detected',
77
76
  510 => 'Not Extended',
78
77
  511 => 'Network Authentication Required'
79
- }
78
+ }.freeze
80
79
 
81
80
  # For some HTTP status codes the client only expects headers.
82
81
  #
@@ -85,7 +84,7 @@ module Puma
85
84
  204 => true,
86
85
  205 => true,
87
86
  304 => true
88
- }
87
+ }.freeze
89
88
 
90
89
  # Frequently used constants when constructing requests or responses. Many times
91
90
  # the constant just refers to a string with the same contents. Using these constants
@@ -100,56 +99,41 @@ module Puma
100
99
  # too taxing on performance.
101
100
  module Const
102
101
 
103
- PUMA_VERSION = VERSION = "3.12.6".freeze
104
- CODE_NAME = "Llamas in Pajamas".freeze
105
- PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
106
-
107
- FAST_TRACK_KA_TIMEOUT = 0.2
102
+ PUMA_VERSION = VERSION = "6.2.2"
103
+ CODE_NAME = "Speaking of Now"
108
104
 
109
- # The default number of seconds for another request within a persistent
110
- # session.
111
- PERSISTENT_TIMEOUT = 20
105
+ PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
112
106
 
113
- # The default number of seconds to wait until we get the first data
114
- # for the request
115
- FIRST_DATA_TIMEOUT = 30
107
+ FAST_TRACK_KA_TIMEOUT = 0.2
116
108
 
117
109
  # How long to wait when getting some write blocking on the socket when
118
110
  # sending data back
119
111
  WRITE_TIMEOUT = 10
120
112
 
121
- # How many requests to attempt inline before sending a client back to
122
- # the reactor to be subject to normal ordering. The idea here is that
123
- # we amortize the cost of going back to the reactor for a well behaved
124
- # but very "greedy" client across 10 requests. This prevents a not
125
- # well behaved client from monopolizing the thread forever.
126
- MAX_FAST_INLINE = 10
127
-
128
113
  # The original URI requested by the client.
129
- REQUEST_URI= 'REQUEST_URI'.freeze
130
- REQUEST_PATH = 'REQUEST_PATH'.freeze
131
- QUERY_STRING = 'QUERY_STRING'.freeze
132
-
133
- PATH_INFO = 'PATH_INFO'.freeze
134
-
135
- PUMA_TMP_BASE = "puma".freeze
136
-
137
- # Indicate that we couldn't parse the request
138
- ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n".freeze
139
-
140
- # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
141
- ERROR_404_RESPONSE = "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze
142
-
143
- # The standard empty 408 response for requests that timed out.
144
- ERROR_408_RESPONSE = "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze
145
-
146
- CONTENT_LENGTH = "CONTENT_LENGTH".freeze
147
-
148
- # Indicate that there was an internal error, obviously.
149
- ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze
150
-
151
- # A common header for indicating the server is too busy. Not used yet.
152
- ERROR_503_RESPONSE = "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
114
+ REQUEST_URI= "REQUEST_URI"
115
+ REQUEST_PATH = "REQUEST_PATH"
116
+ QUERY_STRING = "QUERY_STRING"
117
+ CONTENT_LENGTH = "CONTENT_LENGTH"
118
+
119
+ PATH_INFO = "PATH_INFO"
120
+
121
+ PUMA_TMP_BASE = "puma"
122
+
123
+ ERROR_RESPONSE = {
124
+ # Indicate that we couldn't parse the request
125
+ 400 => "HTTP/1.1 400 Bad Request\r\n\r\n",
126
+ # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
127
+ 404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND",
128
+ # The standard empty 408 response for requests that timed out.
129
+ 408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n",
130
+ # Indicate that there was an internal error, obviously.
131
+ 500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n",
132
+ # Incorrect or invalid header value
133
+ 501 => "HTTP/1.1 501 Not Implemented\r\n\r\n",
134
+ # A common header for indicating the server is too busy. Not used yet.
135
+ 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY"
136
+ }.freeze
153
137
 
154
138
  # The basic max request size we'll try to read.
155
139
  CHUNK_SIZE = 16 * 1024
@@ -161,79 +145,96 @@ module Puma
161
145
  # Maximum request body size before it is moved out of memory and into a tempfile for reading.
162
146
  MAX_BODY = MAX_HEADER
163
147
 
164
- REQUEST_METHOD = "REQUEST_METHOD".freeze
165
- HEAD = "HEAD".freeze
148
+ REQUEST_METHOD = "REQUEST_METHOD"
149
+ HEAD = "HEAD"
150
+ SUPPORTED_HTTP_METHODS = %w[HEAD GET POST PUT DELETE OPTIONS TRACE PATCH].freeze
166
151
  # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
167
- LINE_END = "\r\n".freeze
168
- REMOTE_ADDR = "REMOTE_ADDR".freeze
169
- HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR".freeze
152
+ LINE_END = "\r\n"
153
+ REMOTE_ADDR = "REMOTE_ADDR"
154
+ HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR"
155
+ HTTP_X_FORWARDED_SSL = "HTTP_X_FORWARDED_SSL"
156
+ HTTP_X_FORWARDED_SCHEME = "HTTP_X_FORWARDED_SCHEME"
157
+ HTTP_X_FORWARDED_PROTO = "HTTP_X_FORWARDED_PROTO"
158
+
159
+ SERVER_NAME = "SERVER_NAME"
160
+ SERVER_PORT = "SERVER_PORT"
161
+ HTTP_HOST = "HTTP_HOST"
162
+ PORT_80 = "80"
163
+ PORT_443 = "443"
164
+ LOCALHOST = "localhost"
165
+ LOCALHOST_IPV4 = "127.0.0.1"
166
+ LOCALHOST_IPV6 = "::1"
167
+ UNSPECIFIED_IPV4 = "0.0.0.0"
168
+ UNSPECIFIED_IPV6 = "::"
169
+
170
+ SERVER_PROTOCOL = "SERVER_PROTOCOL"
171
+ HTTP_11 = "HTTP/1.1"
172
+
173
+ SERVER_SOFTWARE = "SERVER_SOFTWARE"
174
+ GATEWAY_INTERFACE = "GATEWAY_INTERFACE"
175
+ CGI_VER = "CGI/1.2"
170
176
 
171
- SERVER_NAME = "SERVER_NAME".freeze
172
- SERVER_PORT = "SERVER_PORT".freeze
173
- HTTP_HOST = "HTTP_HOST".freeze
174
- PORT_80 = "80".freeze
175
- PORT_443 = "443".freeze
176
- LOCALHOST = "localhost".freeze
177
- LOCALHOST_IP = "127.0.0.1".freeze
178
- LOCALHOST_ADDR = "127.0.0.1:0".freeze
177
+ STOP_COMMAND = "?"
178
+ HALT_COMMAND = "!"
179
+ RESTART_COMMAND = "R"
179
180
 
180
- SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
181
- HTTP_11 = "HTTP/1.1".freeze
181
+ RACK_INPUT = "rack.input"
182
+ RACK_URL_SCHEME = "rack.url_scheme"
183
+ RACK_AFTER_REPLY = "rack.after_reply"
184
+ PUMA_SOCKET = "puma.socket"
185
+ PUMA_CONFIG = "puma.config"
186
+ PUMA_PEERCERT = "puma.peercert"
182
187
 
183
- SERVER_SOFTWARE = "SERVER_SOFTWARE".freeze
184
- GATEWAY_INTERFACE = "GATEWAY_INTERFACE".freeze
185
- CGI_VER = "CGI/1.2".freeze
188
+ HTTP = "http"
189
+ HTTPS = "https"
186
190
 
187
- STOP_COMMAND = "?".freeze
188
- HALT_COMMAND = "!".freeze
189
- RESTART_COMMAND = "R".freeze
191
+ HTTPS_KEY = "HTTPS"
190
192
 
191
- RACK_INPUT = "rack.input".freeze
192
- RACK_URL_SCHEME = "rack.url_scheme".freeze
193
- RACK_AFTER_REPLY = "rack.after_reply".freeze
194
- PUMA_SOCKET = "puma.socket".freeze
195
- PUMA_CONFIG = "puma.config".freeze
196
- PUMA_PEERCERT = "puma.peercert".freeze
193
+ HTTP_VERSION = "HTTP_VERSION"
194
+ HTTP_CONNECTION = "HTTP_CONNECTION"
195
+ HTTP_EXPECT = "HTTP_EXPECT"
196
+ CONTINUE = "100-continue"
197
197
 
198
- HTTP = "http".freeze
199
- HTTPS = "https".freeze
198
+ HTTP_11_100 = "HTTP/1.1 100 Continue\r\n\r\n"
199
+ HTTP_11_200 = "HTTP/1.1 200 OK\r\n"
200
+ HTTP_10_200 = "HTTP/1.0 200 OK\r\n"
200
201
 
201
- HTTPS_KEY = "HTTPS".freeze
202
+ CLOSE = "close"
203
+ KEEP_ALIVE = "keep-alive"
202
204
 
203
- HTTP_VERSION = "HTTP_VERSION".freeze
204
- HTTP_CONNECTION = "HTTP_CONNECTION".freeze
205
- HTTP_EXPECT = "HTTP_EXPECT".freeze
206
- CONTINUE = "100-continue".freeze
205
+ CONTENT_LENGTH2 = "content-length"
206
+ CONTENT_LENGTH_S = "Content-Length: "
207
+ TRANSFER_ENCODING = "transfer-encoding"
208
+ TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING"
207
209
 
208
- HTTP_11_100 = "HTTP/1.1 100 Continue\r\n\r\n".freeze
209
- HTTP_11_200 = "HTTP/1.1 200 OK\r\n".freeze
210
- HTTP_10_200 = "HTTP/1.0 200 OK\r\n".freeze
210
+ CONNECTION_CLOSE = "Connection: close\r\n"
211
+ CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n"
211
212
 
212
- CLOSE = "close".freeze
213
- KEEP_ALIVE = "keep-alive".freeze
213
+ TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n"
214
+ CLOSE_CHUNKED = "0\r\n\r\n"
214
215
 
215
- CONTENT_LENGTH2 = "content-length".freeze
216
- CONTENT_LENGTH_S = "Content-Length: ".freeze
217
- TRANSFER_ENCODING = "transfer-encoding".freeze
218
- TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING".freeze
216
+ CHUNKED = "chunked"
219
217
 
220
- CONNECTION_CLOSE = "Connection: close\r\n".freeze
221
- CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n".freeze
218
+ COLON = ": "
222
219
 
223
- TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n".freeze
224
- CLOSE_CHUNKED = "0\r\n\r\n".freeze
220
+ NEWLINE = "\n"
225
221
 
226
- CHUNKED = "chunked".freeze
222
+ HIJACK_P = "rack.hijack?"
223
+ HIJACK = "rack.hijack"
224
+ HIJACK_IO = "rack.hijack_io"
227
225
 
228
- COLON = ": ".freeze
226
+ EARLY_HINTS = "rack.early_hints"
229
227
 
230
- NEWLINE = "\n".freeze
231
- HTTP_INJECTION_REGEX = /[\r\n]/.freeze
228
+ # Illegal character in the key or value of response header
229
+ DQUOTE = "\""
230
+ HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
231
+ ILLEGAL_HEADER_KEY_REGEX = /[\x00-\x20#{DQUOTE}#{HTTP_HEADER_DELIMITER}]/.freeze
232
+ # header values can contain HTAB?
233
+ ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/.freeze
232
234
 
233
- HIJACK_P = "rack.hijack?".freeze
234
- HIJACK = "rack.hijack".freeze
235
- HIJACK_IO = "rack.hijack_io".freeze
235
+ # Banned keys of response header
236
+ BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
236
237
 
237
- EARLY_HINTS = "rack.early_hints".freeze
238
+ PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
238
239
  end
239
240
  end