puma 6.0.0 → 6.6.0

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 (85) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +392 -13
  3. data/LICENSE +0 -0
  4. data/README.md +135 -29
  5. data/bin/puma-wild +0 -0
  6. data/docs/architecture.md +0 -0
  7. data/docs/compile_options.md +0 -0
  8. data/docs/deployment.md +0 -0
  9. data/docs/fork_worker.md +11 -1
  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/java_options.md +54 -0
  14. data/docs/jungle/README.md +0 -0
  15. data/docs/jungle/rc.d/README.md +0 -0
  16. data/docs/jungle/rc.d/puma.conf +0 -0
  17. data/docs/kubernetes.md +12 -0
  18. data/docs/nginx.md +1 -1
  19. data/docs/plugins.md +4 -0
  20. data/docs/rails_dev_mode.md +0 -0
  21. data/docs/restart.md +1 -0
  22. data/docs/signals.md +2 -2
  23. data/docs/stats.md +8 -3
  24. data/docs/systemd.md +13 -7
  25. data/docs/testing_benchmarks_local_files.md +0 -0
  26. data/docs/testing_test_rackup_ci_files.md +0 -0
  27. data/ext/puma_http11/PumaHttp11Service.java +0 -0
  28. data/ext/puma_http11/ext_help.h +0 -0
  29. data/ext/puma_http11/extconf.rb +21 -14
  30. data/ext/puma_http11/http11_parser.c +0 -0
  31. data/ext/puma_http11/http11_parser.h +0 -0
  32. data/ext/puma_http11/http11_parser.java.rl +0 -0
  33. data/ext/puma_http11/http11_parser.rl +0 -0
  34. data/ext/puma_http11/http11_parser_common.rl +0 -0
  35. data/ext/puma_http11/mini_ssl.c +107 -10
  36. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
  37. data/ext/puma_http11/org/jruby/puma/Http11.java +30 -7
  38. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +0 -0
  39. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +2 -1
  40. data/ext/puma_http11/puma_http11.c +4 -1
  41. data/lib/puma/app/status.rb +1 -1
  42. data/lib/puma/binder.rb +26 -15
  43. data/lib/puma/cli.rb +13 -5
  44. data/lib/puma/client.rb +113 -26
  45. data/lib/puma/cluster/worker.rb +14 -6
  46. data/lib/puma/cluster/worker_handle.rb +4 -5
  47. data/lib/puma/cluster.rb +93 -22
  48. data/lib/puma/commonlogger.rb +21 -14
  49. data/lib/puma/configuration.rb +42 -22
  50. data/lib/puma/const.rb +149 -89
  51. data/lib/puma/control_cli.rb +16 -9
  52. data/lib/puma/detect.rb +5 -4
  53. data/lib/puma/dsl.rb +432 -40
  54. data/lib/puma/error_logger.rb +6 -5
  55. data/lib/puma/events.rb +0 -0
  56. data/lib/puma/io_buffer.rb +10 -0
  57. data/lib/puma/jruby_restart.rb +0 -16
  58. data/lib/puma/json_serialization.rb +0 -0
  59. data/lib/puma/launcher/bundle_pruner.rb +0 -0
  60. data/lib/puma/launcher.rb +29 -29
  61. data/lib/puma/log_writer.rb +23 -13
  62. data/lib/puma/minissl/context_builder.rb +4 -0
  63. data/lib/puma/minissl.rb +23 -0
  64. data/lib/puma/null_io.rb +42 -2
  65. data/lib/puma/plugin/systemd.rb +90 -0
  66. data/lib/puma/plugin/tmp_restart.rb +0 -0
  67. data/lib/puma/plugin.rb +0 -0
  68. data/lib/puma/rack/builder.rb +2 -2
  69. data/lib/puma/rack/urlmap.rb +1 -1
  70. data/lib/puma/rack_default.rb +18 -3
  71. data/lib/puma/reactor.rb +17 -8
  72. data/lib/puma/request.rb +207 -126
  73. data/lib/puma/runner.rb +26 -4
  74. data/lib/puma/sd_notify.rb +146 -0
  75. data/lib/puma/server.rb +121 -49
  76. data/lib/puma/single.rb +3 -1
  77. data/lib/puma/state_file.rb +2 -2
  78. data/lib/puma/thread_pool.rb +56 -9
  79. data/lib/puma/util.rb +1 -1
  80. data/lib/puma.rb +1 -3
  81. data/lib/rack/handler/puma.rb +116 -86
  82. data/tools/Dockerfile +2 -2
  83. data/tools/trickletest.rb +0 -0
  84. metadata +12 -13
  85. data/lib/puma/systemd.rb +0 -47
@@ -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}[https://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: https://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,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'rack/builder'
4
3
  require_relative 'plugin'
5
4
  require_relative 'const'
6
- # note that dsl is loaded at end of file, requires ConfigDefault constants
5
+ require_relative 'dsl'
7
6
 
8
7
  module Puma
9
8
  # A class used for storing "leveled" configuration options.
@@ -131,10 +130,13 @@ module Puma
131
130
  binds: ['tcp://0.0.0.0:9292'.freeze],
132
131
  clean_thread_locals: false,
133
132
  debug: false,
133
+ enable_keep_alives: true,
134
134
  early_hints: nil,
135
135
  environment: 'development'.freeze,
136
- # Number of seconds to wait until we get the first data for the request
136
+ # Number of seconds to wait until we get the first data for the request.
137
137
  first_data_timeout: 30,
138
+ # Number of seconds to wait until the next request before shutting down.
139
+ idle_timeout: nil,
138
140
  io_selector_backend: :auto,
139
141
  log_requests: false,
140
142
  logger: STDOUT,
@@ -157,6 +159,7 @@ module Puma
157
159
  reaping_time: 1,
158
160
  remote_address: :socket,
159
161
  silence_single_worker_warning: false,
162
+ silence_fork_callback_warning: false,
160
163
  tag: File.basename(Dir.getwd),
161
164
  tcp_host: '0.0.0.0'.freeze,
162
165
  tcp_port: 9292,
@@ -167,10 +170,11 @@ module Puma
167
170
  worker_shutdown_timeout: 30,
168
171
  worker_timeout: 60,
169
172
  workers: 0,
173
+ http_content_length_limit: nil
170
174
  }
171
175
 
172
- def initialize(user_options={}, default_options = {}, &block)
173
- default_options = self.puma_default_options.merge(default_options)
176
+ def initialize(user_options={}, default_options = {}, env = ENV, &block)
177
+ default_options = self.puma_default_options(env).merge(default_options)
174
178
 
175
179
  @options = UserFileDefaultOptions.new(user_options, default_options)
176
180
  @plugins = PluginLoader.new
@@ -182,6 +186,8 @@ module Puma
182
186
  default_options[:preload_app] = (@options[:workers] > 1) && Puma.forkable?
183
187
  end
184
188
 
189
+ @puma_bundler_pruned = env.key? 'PUMA_BUNDLER_PRUNED'
190
+
185
191
  if block
186
192
  configure(&block)
187
193
  end
@@ -212,22 +218,27 @@ module Puma
212
218
  self
213
219
  end
214
220
 
215
- def puma_default_options
221
+ def puma_default_options(env = ENV)
216
222
  defaults = DEFAULTS.dup
217
- puma_options_from_env.each { |k,v| defaults[k] = v if v }
223
+ puma_options_from_env(env).each { |k,v| defaults[k] = v if v }
218
224
  defaults
219
225
  end
220
226
 
221
- def puma_options_from_env
222
- min = ENV['PUMA_MIN_THREADS'] || ENV['MIN_THREADS']
223
- max = ENV['PUMA_MAX_THREADS'] || ENV['MAX_THREADS']
224
- workers = ENV['WEB_CONCURRENCY']
227
+ def puma_options_from_env(env = ENV)
228
+ min = env['PUMA_MIN_THREADS'] || env['MIN_THREADS']
229
+ max = env['PUMA_MAX_THREADS'] || env['MAX_THREADS']
230
+ workers = if env['WEB_CONCURRENCY'] == 'auto'
231
+ require_processor_counter
232
+ ::Concurrent.available_processor_count
233
+ else
234
+ env['WEB_CONCURRENCY']
235
+ end
225
236
 
226
237
  {
227
- min_threads: min && Integer(min),
228
- max_threads: max && Integer(max),
229
- workers: workers && Integer(workers),
230
- environment: ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'],
238
+ min_threads: min && min != "" && Integer(min),
239
+ max_threads: max && max != "" && Integer(max),
240
+ workers: workers && workers != "" && Integer(workers),
241
+ environment: env['APP_ENV'] || env['RACK_ENV'] || env['RAILS_ENV'],
231
242
  }
232
243
  end
233
244
 
@@ -307,6 +318,8 @@ module Puma
307
318
  # @param arg [Launcher, Int] `:on_restart` passes Launcher
308
319
  #
309
320
  def run_hooks(key, arg, log_writer, hook_data = nil)
321
+ log_writer.debug "Running #{key} hooks"
322
+
310
323
  @options.all_of(key).each do |b|
311
324
  begin
312
325
  if Array === b
@@ -335,12 +348,22 @@ module Puma
335
348
 
336
349
  private
337
350
 
351
+ def require_processor_counter
352
+ require 'concurrent/utility/processor_counter'
353
+ rescue LoadError
354
+ warn <<~MESSAGE
355
+ WEB_CONCURRENCY=auto requires the "concurrent-ruby" gem to be installed.
356
+ Please add "concurrent-ruby" to your Gemfile.
357
+ MESSAGE
358
+ raise
359
+ end
360
+
338
361
  # Load and use the normal Rack builder if we can, otherwise
339
362
  # fallback to our minimal version.
340
363
  def rack_builder
341
364
  # Load bundler now if we can so that we can pickup rack from
342
365
  # a Gemfile
343
- if ENV.key? 'PUMA_BUNDLER_PRUNED'
366
+ if @puma_bundler_pruned
344
367
  begin
345
368
  require 'bundler/setup'
346
369
  rescue LoadError
@@ -350,11 +373,10 @@ module Puma
350
373
  begin
351
374
  require 'rack'
352
375
  require 'rack/builder'
376
+ ::Rack::Builder
353
377
  rescue LoadError
354
- # ok, use builtin version
355
- return Puma::Rack::Builder
356
- else
357
- return ::Rack::Builder
378
+ require_relative 'rack/builder'
379
+ Puma::Rack::Builder
358
380
  end
359
381
  end
360
382
 
@@ -383,5 +405,3 @@ module Puma
383
405
  end
384
406
  end
385
407
  end
386
-
387
- 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.
@@ -19,6 +18,7 @@ module Puma
19
18
  100 => 'Continue',
20
19
  101 => 'Switching Protocols',
21
20
  102 => 'Processing',
21
+ 103 => 'Early Hints',
22
22
  200 => 'OK',
23
23
  201 => 'Created',
24
24
  202 => 'Accepted',
@@ -50,16 +50,16 @@ module Puma
50
50
  410 => 'Gone',
51
51
  411 => 'Length Required',
52
52
  412 => 'Precondition Failed',
53
- 413 => 'Payload Too Large',
53
+ 413 => 'Content Too Large',
54
54
  414 => 'URI Too Long',
55
55
  415 => 'Unsupported Media Type',
56
56
  416 => 'Range Not Satisfiable',
57
57
  417 => 'Expectation Failed',
58
- 418 => 'I\'m A Teapot',
59
58
  421 => 'Misdirected Request',
60
- 422 => 'Unprocessable Entity',
59
+ 422 => 'Unprocessable Content',
61
60
  423 => 'Locked',
62
61
  424 => 'Failed Dependency',
62
+ 425 => 'Too Early',
63
63
  426 => 'Upgrade Required',
64
64
  428 => 'Precondition Required',
65
65
  429 => 'Too Many Requests',
@@ -74,7 +74,7 @@ module Puma
74
74
  506 => 'Variant Also Negotiates',
75
75
  507 => 'Insufficient Storage',
76
76
  508 => 'Loop Detected',
77
- 510 => 'Not Extended',
77
+ 510 => 'Not Extended (OBSOLETED)',
78
78
  511 => 'Network Authentication Required'
79
79
  }.freeze
80
80
 
@@ -100,10 +100,10 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "6.0.0".freeze
104
- CODE_NAME = "Sunflower".freeze
103
+ PUMA_VERSION = VERSION = "6.6.0"
104
+ CODE_NAME = "Return to Forever"
105
105
 
106
- PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
106
+ PUMA_SERVER_STRING = ["puma", PUMA_VERSION, CODE_NAME].join(" ").freeze
107
107
 
108
108
  FAST_TRACK_KA_TIMEOUT = 0.2
109
109
 
@@ -112,32 +112,32 @@ module Puma
112
112
  WRITE_TIMEOUT = 10
113
113
 
114
114
  # The original URI requested by the client.
115
- REQUEST_URI= 'REQUEST_URI'.freeze
116
- REQUEST_PATH = 'REQUEST_PATH'.freeze
117
- QUERY_STRING = 'QUERY_STRING'.freeze
118
- CONTENT_LENGTH = "CONTENT_LENGTH".freeze
115
+ REQUEST_URI= "REQUEST_URI"
116
+ REQUEST_PATH = "REQUEST_PATH"
117
+ QUERY_STRING = "QUERY_STRING"
118
+ CONTENT_LENGTH = "CONTENT_LENGTH"
119
119
 
120
- PATH_INFO = 'PATH_INFO'.freeze
120
+ PATH_INFO = "PATH_INFO"
121
121
 
122
- PUMA_TMP_BASE = "puma".freeze
122
+ PUMA_TMP_BASE = "puma"
123
123
 
124
124
  ERROR_RESPONSE = {
125
125
  # Indicate that we couldn't parse the request
126
- 400 => "HTTP/1.1 400 Bad Request\r\n\r\n".freeze,
126
+ 400 => "HTTP/1.1 400 Bad Request\r\n\r\n",
127
127
  # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
128
- 404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\nNOT FOUND".freeze,
128
+ 404 => "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n",
129
129
  # The standard empty 408 response for requests that timed out.
130
- 408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze,
130
+ 408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\n\r\n",
131
131
  # Indicate that there was an internal error, obviously.
132
- 500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze,
132
+ 500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n",
133
133
  # Incorrect or invalid header value
134
- 501 => "HTTP/1.1 501 Not Implemented\r\n\r\n".freeze,
134
+ 501 => "HTTP/1.1 501 Not Implemented\r\n\r\n",
135
135
  # A common header for indicating the server is too busy. Not used yet.
136
- 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
136
+ 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\n"
137
137
  }.freeze
138
138
 
139
139
  # The basic max request size we'll try to read.
140
- CHUNK_SIZE = 16 * 1024
140
+ CHUNK_SIZE = 64 * 1024
141
141
 
142
142
  # This is the maximum header that is allowed before a client is booted. The parser detects
143
143
  # this, but we'd also like to do this as well.
@@ -146,103 +146,163 @@ module Puma
146
146
  # Maximum request body size before it is moved out of memory and into a tempfile for reading.
147
147
  MAX_BODY = MAX_HEADER
148
148
 
149
- REQUEST_METHOD = "REQUEST_METHOD".freeze
150
- HEAD = "HEAD".freeze
151
- GET = "GET".freeze
152
- POST = "POST".freeze
153
- PUT = "PUT".freeze
154
- DELETE = "DELETE".freeze
155
- OPTIONS = "OPTIONS".freeze
156
- TRACE = "TRACE".freeze
157
- PATCH = "PATCH".freeze
158
- SUPPORTED_HTTP_METHODS = [HEAD, GET, POST, PUT, DELETE, OPTIONS, TRACE, PATCH].freeze
149
+ REQUEST_METHOD = "REQUEST_METHOD"
150
+ HEAD = "HEAD"
151
+
152
+ # based on https://www.rfc-editor.org/rfc/rfc9110.html#name-overview,
153
+ # with CONNECT removed, and PATCH added
154
+ SUPPORTED_HTTP_METHODS = %w[HEAD GET POST PUT DELETE OPTIONS TRACE PATCH].freeze
155
+
156
+ # list from https://www.iana.org/assignments/http-methods/http-methods.xhtml
157
+ # as of 04-May-23
158
+ IANA_HTTP_METHODS = %w[
159
+ ACL
160
+ BASELINE-CONTROL
161
+ BIND
162
+ CHECKIN
163
+ CHECKOUT
164
+ CONNECT
165
+ COPY
166
+ DELETE
167
+ GET
168
+ HEAD
169
+ LABEL
170
+ LINK
171
+ LOCK
172
+ MERGE
173
+ MKACTIVITY
174
+ MKCALENDAR
175
+ MKCOL
176
+ MKREDIRECTREF
177
+ MKWORKSPACE
178
+ MOVE
179
+ OPTIONS
180
+ ORDERPATCH
181
+ PATCH
182
+ POST
183
+ PRI
184
+ PROPFIND
185
+ PROPPATCH
186
+ PUT
187
+ REBIND
188
+ REPORT
189
+ SEARCH
190
+ TRACE
191
+ UNBIND
192
+ UNCHECKOUT
193
+ UNLINK
194
+ UNLOCK
195
+ UPDATE
196
+ UPDATEREDIRECTREF
197
+ VERSION-CONTROL
198
+ ].freeze
199
+
159
200
  # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
160
- LINE_END = "\r\n".freeze
161
- REMOTE_ADDR = "REMOTE_ADDR".freeze
162
- HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR".freeze
163
- HTTP_X_FORWARDED_SSL = "HTTP_X_FORWARDED_SSL".freeze
164
- HTTP_X_FORWARDED_SCHEME = "HTTP_X_FORWARDED_SCHEME".freeze
165
- HTTP_X_FORWARDED_PROTO = "HTTP_X_FORWARDED_PROTO".freeze
201
+ LINE_END = "\r\n"
202
+ REMOTE_ADDR = "REMOTE_ADDR"
203
+ HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR"
204
+ HTTP_X_FORWARDED_SSL = "HTTP_X_FORWARDED_SSL"
205
+ HTTP_X_FORWARDED_SCHEME = "HTTP_X_FORWARDED_SCHEME"
206
+ HTTP_X_FORWARDED_PROTO = "HTTP_X_FORWARDED_PROTO"
166
207
 
167
- SERVER_NAME = "SERVER_NAME".freeze
168
- SERVER_PORT = "SERVER_PORT".freeze
169
- HTTP_HOST = "HTTP_HOST".freeze
170
- PORT_80 = "80".freeze
171
- PORT_443 = "443".freeze
172
- LOCALHOST = "localhost".freeze
173
- LOCALHOST_IPV4 = "127.0.0.1".freeze
174
- LOCALHOST_IPV6 = "::1".freeze
175
- UNSPECIFIED_IPV4 = "0.0.0.0".freeze
176
- UNSPECIFIED_IPV6 = "::".freeze
208
+ SERVER_NAME = "SERVER_NAME"
209
+ SERVER_PORT = "SERVER_PORT"
210
+ HTTP_HOST = "HTTP_HOST"
211
+ PORT_80 = "80"
212
+ PORT_443 = "443"
213
+ LOCALHOST = "localhost"
214
+ LOCALHOST_IPV4 = "127.0.0.1"
215
+ LOCALHOST_IPV6 = "::1"
216
+ UNSPECIFIED_IPV4 = "0.0.0.0"
217
+ UNSPECIFIED_IPV6 = "::"
177
218
 
178
- SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
179
- HTTP_11 = "HTTP/1.1".freeze
219
+ SERVER_PROTOCOL = "SERVER_PROTOCOL"
220
+ HTTP_11 = "HTTP/1.1"
180
221
 
181
- SERVER_SOFTWARE = "SERVER_SOFTWARE".freeze
182
- GATEWAY_INTERFACE = "GATEWAY_INTERFACE".freeze
183
- CGI_VER = "CGI/1.2".freeze
222
+ SERVER_SOFTWARE = "SERVER_SOFTWARE"
223
+ GATEWAY_INTERFACE = "GATEWAY_INTERFACE"
224
+ CGI_VER = "CGI/1.2"
184
225
 
185
- STOP_COMMAND = "?".freeze
186
- HALT_COMMAND = "!".freeze
187
- RESTART_COMMAND = "R".freeze
226
+ STOP_COMMAND = "?"
227
+ HALT_COMMAND = "!"
228
+ RESTART_COMMAND = "R"
188
229
 
189
- RACK_INPUT = "rack.input".freeze
190
- RACK_URL_SCHEME = "rack.url_scheme".freeze
191
- RACK_AFTER_REPLY = "rack.after_reply".freeze
192
- PUMA_SOCKET = "puma.socket".freeze
193
- PUMA_CONFIG = "puma.config".freeze
194
- PUMA_PEERCERT = "puma.peercert".freeze
230
+ RACK_INPUT = "rack.input"
231
+ RACK_URL_SCHEME = "rack.url_scheme"
232
+ RACK_AFTER_REPLY = "rack.after_reply"
233
+ PUMA_SOCKET = "puma.socket"
234
+ PUMA_CONFIG = "puma.config"
235
+ PUMA_PEERCERT = "puma.peercert"
195
236
 
196
- HTTP = "http".freeze
197
- HTTPS = "https".freeze
237
+ HTTP = "http"
238
+ HTTPS = "https"
198
239
 
199
- HTTPS_KEY = "HTTPS".freeze
240
+ HTTPS_KEY = "HTTPS"
200
241
 
201
- HTTP_VERSION = "HTTP_VERSION".freeze
202
- HTTP_CONNECTION = "HTTP_CONNECTION".freeze
203
- HTTP_EXPECT = "HTTP_EXPECT".freeze
204
- CONTINUE = "100-continue".freeze
242
+ HTTP_VERSION = "HTTP_VERSION"
243
+ HTTP_CONNECTION = "HTTP_CONNECTION"
244
+ HTTP_EXPECT = "HTTP_EXPECT"
245
+ CONTINUE = "100-continue"
205
246
 
206
- HTTP_11_100 = "HTTP/1.1 100 Continue\r\n\r\n".freeze
207
- HTTP_11_200 = "HTTP/1.1 200 OK\r\n".freeze
208
- HTTP_10_200 = "HTTP/1.0 200 OK\r\n".freeze
247
+ HTTP_11_100 = "HTTP/1.1 100 Continue\r\n\r\n"
248
+ HTTP_11_200 = "HTTP/1.1 200 OK\r\n"
249
+ HTTP_10_200 = "HTTP/1.0 200 OK\r\n"
209
250
 
210
- CLOSE = "close".freeze
211
- KEEP_ALIVE = "keep-alive".freeze
251
+ CLOSE = "close"
252
+ KEEP_ALIVE = "keep-alive"
212
253
 
213
- CONTENT_LENGTH2 = "content-length".freeze
214
- CONTENT_LENGTH_S = "Content-Length: ".freeze
215
- TRANSFER_ENCODING = "transfer-encoding".freeze
216
- TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING".freeze
254
+ CONTENT_LENGTH2 = "content-length"
255
+ CONTENT_LENGTH_S = "Content-Length: "
256
+ TRANSFER_ENCODING = "transfer-encoding"
257
+ TRANSFER_ENCODING2 = "HTTP_TRANSFER_ENCODING"
217
258
 
218
- CONNECTION_CLOSE = "Connection: close\r\n".freeze
219
- CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n".freeze
259
+ CONNECTION_CLOSE = "Connection: close\r\n"
260
+ CONNECTION_KEEP_ALIVE = "Connection: Keep-Alive\r\n"
220
261
 
221
- TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n".freeze
222
- CLOSE_CHUNKED = "0\r\n\r\n".freeze
262
+ TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n"
263
+ CLOSE_CHUNKED = "0\r\n\r\n"
223
264
 
224
- CHUNKED = "chunked".freeze
265
+ CHUNKED = "chunked"
225
266
 
226
- COLON = ": ".freeze
267
+ COLON = ": "
227
268
 
228
- NEWLINE = "\n".freeze
269
+ NEWLINE = "\n"
229
270
 
230
- HIJACK_P = "rack.hijack?".freeze
231
- HIJACK = "rack.hijack".freeze
232
- HIJACK_IO = "rack.hijack_io".freeze
271
+ HIJACK_P = "rack.hijack?"
272
+ HIJACK = "rack.hijack"
273
+ HIJACK_IO = "rack.hijack_io"
233
274
 
234
- EARLY_HINTS = "rack.early_hints".freeze
275
+ EARLY_HINTS = "rack.early_hints"
235
276
 
236
277
  # Illegal character in the key or value of response header
237
- DQUOTE = "\"".freeze
278
+ DQUOTE = "\""
238
279
  HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
239
280
  ILLEGAL_HEADER_KEY_REGEX = /[\x00-\x20#{DQUOTE}#{HTTP_HEADER_DELIMITER}]/.freeze
240
281
  # header values can contain HTAB?
241
282
  ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/.freeze
242
283
 
284
+ # The keys of headers that should not be convert to underscore
285
+ # normalized versions. These headers are ignored at the request reading layer,
286
+ # but if we normalize them after reading, it's just confusing for the application.
287
+ UNMASKABLE_HEADERS = {
288
+ "HTTP_TRANSFER,ENCODING" => true,
289
+ "HTTP_CONTENT,LENGTH" => true,
290
+ }
291
+
243
292
  # Banned keys of response header
244
293
  BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
245
294
 
246
295
  PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
296
+
297
+ # All constants are prefixed with `PIPE_` to avoid name collisions.
298
+ module PipeRequest
299
+ PIPE_WAKEUP = "!"
300
+ PIPE_BOOT = "b"
301
+ PIPE_FORK = "f"
302
+ PIPE_EXTERNAL_TERM = "e"
303
+ PIPE_TERM = "t"
304
+ PIPE_PING = "p"
305
+ PIPE_IDLE = "i"
306
+ end
247
307
  end
248
308
  end
@@ -1,10 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'optparse'
4
- require_relative 'state_file'
5
4
  require_relative 'const'
6
5
  require_relative 'detect'
7
- require_relative 'configuration'
8
6
  require 'uri'
9
7
  require 'socket'
10
8
 
@@ -39,7 +37,7 @@ module Puma
39
37
  # @version 5.0.0
40
38
  PRINTABLE_COMMANDS = %w[gc-stats stats thread-backtraces].freeze
41
39
 
42
- def initialize(argv, stdout=STDOUT, stderr=STDERR)
40
+ def initialize(argv, stdout=STDOUT, stderr=STDERR, env: ENV)
43
41
  @state = nil
44
42
  @quiet = false
45
43
  @pidfile = nil
@@ -48,7 +46,7 @@ module Puma
48
46
  @control_auth_token = nil
49
47
  @config_file = nil
50
48
  @command = nil
51
- @environment = ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']
49
+ @environment = env['APP_ENV'] || env['RACK_ENV'] || env['RAILS_ENV']
52
50
 
53
51
  @argv = argv.dup
54
52
  @stdout = stdout
@@ -62,7 +60,7 @@ module Puma
62
60
  @state = arg
63
61
  end
64
62
 
65
- o.on "-Q", "--quiet", "Not display messages" do |arg|
63
+ o.on "-Q", "--quiet", "Do not display messages" do |arg|
66
64
  @quiet = true
67
65
  end
68
66
 
@@ -126,7 +124,10 @@ module Puma
126
124
  end
127
125
 
128
126
  if @config_file
129
- config = Puma::Configuration.new({ config_files: [@config_file] }, {})
127
+ require_relative 'configuration'
128
+ require_relative 'log_writer'
129
+
130
+ config = Puma::Configuration.new({ config_files: [@config_file] }, {} , env)
130
131
  config.load
131
132
  @state ||= config.options[:state]
132
133
  @control_url ||= config.options[:control_url]
@@ -149,6 +150,8 @@ module Puma
149
150
  raise "State file not found: #{@state}"
150
151
  end
151
152
 
153
+ require_relative 'state_file'
154
+
152
155
  sf = Puma::StateFile.new
153
156
  sf.load @state
154
157
 
@@ -164,22 +167,26 @@ module Puma
164
167
  def send_request
165
168
  uri = URI.parse @control_url
166
169
 
170
+ host = uri.host
171
+
167
172
  # create server object by scheme
168
173
  server =
169
174
  case uri.scheme
170
175
  when 'ssl'
171
176
  require 'openssl'
177
+ host = host[1..-2] if host&.start_with? '['
172
178
  OpenSSL::SSL::SSLSocket.new(
173
- TCPSocket.new(uri.host, uri.port),
179
+ TCPSocket.new(host, uri.port),
174
180
  OpenSSL::SSL::SSLContext.new)
175
181
  .tap { |ssl| ssl.sync_close = true } # default is false
176
182
  .tap(&:connect)
177
183
  when 'tcp'
178
- TCPSocket.new uri.host, uri.port
184
+ host = host[1..-2] if host&.start_with? '['
185
+ TCPSocket.new host, uri.port
179
186
  when 'unix'
180
187
  # check for abstract UNIXSocket
181
188
  UNIXSocket.new(@control_url.start_with?('unix://@') ?
182
- "\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}")
189
+ "\0#{host}#{uri.path}" : "#{host}#{uri.path}")
183
190
  else
184
191
  raise "Invalid scheme: #{uri.scheme}"
185
192
  end
data/lib/puma/detect.rb CHANGED
@@ -12,13 +12,14 @@ module Puma
12
12
 
13
13
  IS_JRUBY = Object.const_defined? :JRUBY_VERSION
14
14
 
15
- IS_OSX = RUBY_PLATFORM.include? 'darwin'
15
+ IS_OSX = RUBY_DESCRIPTION.include? 'darwin'
16
16
 
17
- IS_WINDOWS = !!(RUBY_PLATFORM =~ /mswin|ming|cygwin/) ||
18
- IS_JRUBY && RUBY_DESCRIPTION.include?('mswin')
17
+ IS_WINDOWS = RUBY_DESCRIPTION.match?(/mswin|ming|cygwin/)
18
+
19
+ IS_LINUX = !(IS_OSX || IS_WINDOWS)
19
20
 
20
21
  # @version 5.2.0
21
- IS_MRI = (RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?)
22
+ IS_MRI = RUBY_ENGINE == 'ruby'
22
23
 
23
24
  def self.jruby?
24
25
  IS_JRUBY