puma 5.6.4 → 6.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +372 -6
  3. data/LICENSE +0 -0
  4. data/README.md +79 -29
  5. data/bin/puma-wild +1 -1
  6. data/docs/architecture.md +0 -0
  7. data/docs/compile_options.md +34 -0
  8. data/docs/deployment.md +0 -0
  9. data/docs/fork_worker.md +1 -3
  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 +0 -0
  14. data/docs/jungle/rc.d/README.md +0 -0
  15. data/docs/jungle/rc.d/puma.conf +0 -0
  16. data/docs/kubernetes.md +12 -0
  17. data/docs/nginx.md +1 -1
  18. data/docs/plugins.md +0 -0
  19. data/docs/rails_dev_mode.md +0 -0
  20. data/docs/restart.md +1 -0
  21. data/docs/signals.md +0 -0
  22. data/docs/stats.md +0 -0
  23. data/docs/systemd.md +3 -6
  24. data/docs/testing_benchmarks_local_files.md +150 -0
  25. data/docs/testing_test_rackup_ci_files.md +36 -0
  26. data/ext/puma_http11/PumaHttp11Service.java +0 -0
  27. data/ext/puma_http11/ext_help.h +0 -0
  28. data/ext/puma_http11/extconf.rb +22 -10
  29. data/ext/puma_http11/http11_parser.c +1 -1
  30. data/ext/puma_http11/http11_parser.h +1 -1
  31. data/ext/puma_http11/http11_parser.java.rl +2 -2
  32. data/ext/puma_http11/http11_parser.rl +2 -2
  33. data/ext/puma_http11/http11_parser_common.rl +2 -2
  34. data/ext/puma_http11/mini_ssl.c +153 -27
  35. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
  36. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  37. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +1 -1
  38. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +167 -65
  39. data/ext/puma_http11/puma_http11.c +17 -9
  40. data/lib/puma/app/status.rb +7 -4
  41. data/lib/puma/binder.rb +51 -54
  42. data/lib/puma/cli.rb +16 -18
  43. data/lib/puma/client.rb +100 -26
  44. data/lib/puma/cluster/worker.rb +18 -11
  45. data/lib/puma/cluster/worker_handle.rb +4 -1
  46. data/lib/puma/cluster.rb +102 -40
  47. data/lib/puma/commonlogger.rb +21 -14
  48. data/lib/puma/configuration.rb +77 -59
  49. data/lib/puma/const.rb +129 -92
  50. data/lib/puma/control_cli.rb +33 -23
  51. data/lib/puma/detect.rb +7 -4
  52. data/lib/puma/dsl.rb +251 -53
  53. data/lib/puma/error_logger.rb +18 -9
  54. data/lib/puma/events.rb +6 -126
  55. data/lib/puma/io_buffer.rb +39 -4
  56. data/lib/puma/jruby_restart.rb +2 -1
  57. data/lib/puma/json_serialization.rb +0 -0
  58. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  59. data/lib/puma/launcher.rb +113 -175
  60. data/lib/puma/log_writer.rb +147 -0
  61. data/lib/puma/minissl/context_builder.rb +26 -12
  62. data/lib/puma/minissl.rb +113 -15
  63. data/lib/puma/null_io.rb +21 -2
  64. data/lib/puma/plugin/systemd.rb +90 -0
  65. data/lib/puma/plugin/tmp_restart.rb +1 -1
  66. data/lib/puma/plugin.rb +0 -0
  67. data/lib/puma/rack/builder.rb +6 -6
  68. data/lib/puma/rack/urlmap.rb +1 -1
  69. data/lib/puma/rack_default.rb +19 -4
  70. data/lib/puma/reactor.rb +19 -10
  71. data/lib/puma/request.rb +365 -166
  72. data/lib/puma/runner.rb +56 -20
  73. data/lib/puma/sd_notify.rb +149 -0
  74. data/lib/puma/server.rb +137 -87
  75. data/lib/puma/single.rb +13 -11
  76. data/lib/puma/state_file.rb +4 -6
  77. data/lib/puma/thread_pool.rb +57 -19
  78. data/lib/puma/util.rb +12 -14
  79. data/lib/puma.rb +12 -11
  80. data/lib/rack/handler/puma.rb +113 -86
  81. data/tools/Dockerfile +2 -2
  82. data/tools/trickletest.rb +0 -0
  83. metadata +11 -6
  84. data/lib/puma/queue_close.rb +0 -26
  85. data/lib/puma/systemd.rb +0 -46
@@ -1,21 +1,11 @@
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
+ require_relative 'dsl'
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
- DefaultWorkerCheckInterval = 5
15
- DefaultWorkerTimeout = 60
16
- DefaultWorkerShutdownTimeout = 30
17
- end
18
-
19
9
  # A class used for storing "leveled" configuration options.
20
10
  #
21
11
  # In this class any "user" specified options take precedence over any
@@ -136,7 +126,52 @@ module Puma
136
126
  # is done because an environment variable may have been modified while loading
137
127
  # configuration files.
138
128
  class Configuration
139
- 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
+ # Number of seconds to wait until the next request before shutting down.
139
+ idle_timeout: nil,
140
+ io_selector_backend: :auto,
141
+ log_requests: false,
142
+ logger: STDOUT,
143
+ # How many requests to attempt inline before sending a client back to
144
+ # the reactor to be subject to normal ordering. The idea here is that
145
+ # we amortize the cost of going back to the reactor for a well behaved
146
+ # but very "greedy" client across 10 requests. This prevents a not
147
+ # well behaved client from monopolizing the thread forever.
148
+ max_fast_inline: 10,
149
+ max_threads: Puma.mri? ? 5 : 16,
150
+ min_threads: 0,
151
+ mode: :http,
152
+ mutate_stdout_and_stderr_to_sync_on_write: true,
153
+ out_of_band: [],
154
+ # Number of seconds for another request within a persistent session.
155
+ persistent_timeout: 20,
156
+ queue_requests: true,
157
+ rackup: 'config.ru'.freeze,
158
+ raise_exception_on_sigterm: true,
159
+ reaping_time: 1,
160
+ remote_address: :socket,
161
+ silence_single_worker_warning: false,
162
+ silence_fork_callback_warning: false,
163
+ tag: File.basename(Dir.getwd),
164
+ tcp_host: '0.0.0.0'.freeze,
165
+ tcp_port: 9292,
166
+ wait_for_less_busy_worker: 0.005,
167
+ worker_boot_timeout: 60,
168
+ worker_check_interval: 5,
169
+ worker_culling_strategy: :youngest,
170
+ worker_shutdown_timeout: 30,
171
+ worker_timeout: 60,
172
+ workers: 0,
173
+ http_content_length_limit: nil
174
+ }
140
175
 
141
176
  def initialize(user_options={}, default_options = {}, &block)
142
177
  default_options = self.puma_default_options.merge(default_options)
@@ -181,37 +216,22 @@ module Puma
181
216
  self
182
217
  end
183
218
 
184
- # @version 5.0.0
185
- def default_max_threads
186
- Puma.mri? ? 5 : 16
219
+ def puma_default_options
220
+ defaults = DEFAULTS.dup
221
+ puma_options_from_env.each { |k,v| defaults[k] = v if v }
222
+ defaults
187
223
  end
188
224
 
189
- def puma_default_options
225
+ def puma_options_from_env
226
+ min = ENV['PUMA_MIN_THREADS'] || ENV['MIN_THREADS']
227
+ max = ENV['PUMA_MAX_THREADS'] || ENV['MAX_THREADS']
228
+ workers = ENV['WEB_CONCURRENCY']
229
+
190
230
  {
191
- :min_threads => Integer(ENV['PUMA_MIN_THREADS'] || ENV['MIN_THREADS'] || 0),
192
- :max_threads => Integer(ENV['PUMA_MAX_THREADS'] || ENV['MAX_THREADS'] || default_max_threads),
193
- :log_requests => false,
194
- :debug => false,
195
- :binds => ["tcp://#{DefaultTCPHost}:#{DefaultTCPPort}"],
196
- :workers => Integer(ENV['WEB_CONCURRENCY'] || 0),
197
- :silence_single_worker_warning => false,
198
- :mode => :http,
199
- :worker_check_interval => DefaultWorkerCheckInterval,
200
- :worker_timeout => DefaultWorkerTimeout,
201
- :worker_boot_timeout => DefaultWorkerTimeout,
202
- :worker_shutdown_timeout => DefaultWorkerShutdownTimeout,
203
- :worker_culling_strategy => :youngest,
204
- :remote_address => :socket,
205
- :tag => method(:infer_tag),
206
- :environment => -> { ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development' },
207
- :rackup => DefaultRackup,
208
- :logger => STDOUT,
209
- :persistent_timeout => Const::PERSISTENT_TIMEOUT,
210
- :first_data_timeout => Const::FIRST_DATA_TIMEOUT,
211
- :raise_exception_on_sigterm => true,
212
- :max_fast_inline => Const::MAX_FAST_INLINE,
213
- :io_selector_backend => :auto,
214
- :mutate_stdout_and_stderr_to_sync_on_write => true,
231
+ min_threads: min && Integer(min),
232
+ max_threads: max && Integer(max),
233
+ workers: workers && Integer(workers),
234
+ environment: ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV'],
215
235
  }
216
236
  end
217
237
 
@@ -227,7 +247,7 @@ module Puma
227
247
  return [] if files == ['-']
228
248
  return files if files.any?
229
249
 
230
- first_default_file = %W(config/puma/#{environment_str}.rb config/puma.rb).find do |f|
250
+ first_default_file = %W(config/puma/#{@options[:environment]}.rb config/puma.rb).find do |f|
231
251
  File.exist?(f)
232
252
  end
233
253
 
@@ -270,7 +290,7 @@ module Puma
270
290
  found = options[:app] || load_rackup
271
291
 
272
292
  if @options[:log_requests]
273
- require 'puma/commonlogger'
293
+ require_relative 'commonlogger'
274
294
  logger = @options[:logger]
275
295
  found = CommonLogger.new(found, logger)
276
296
  end
@@ -283,21 +303,25 @@ module Puma
283
303
  @options[:environment]
284
304
  end
285
305
 
286
- def environment_str
287
- environment.respond_to?(:call) ? environment.call : environment
288
- end
289
-
290
306
  def load_plugin(name)
291
307
  @plugins.create name
292
308
  end
293
309
 
294
- def run_hooks(key, arg, events)
310
+ # @param key [:Symbol] hook to run
311
+ # @param arg [Launcher, Int] `:on_restart` passes Launcher
312
+ #
313
+ def run_hooks(key, arg, log_writer, hook_data = nil)
295
314
  @options.all_of(key).each do |b|
296
315
  begin
297
- b.call arg
316
+ if Array === b
317
+ hook_data[b[1]] ||= Hash.new
318
+ b[0].call arg, hook_data[b[1]]
319
+ else
320
+ b.call arg
321
+ end
298
322
  rescue => e
299
- events.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
300
- events.debug e.backtrace.join("\n")
323
+ log_writer.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
324
+ log_writer.debug e.backtrace.join("\n")
301
325
  end
302
326
  end
303
327
  end
@@ -315,10 +339,6 @@ module Puma
315
339
 
316
340
  private
317
341
 
318
- def infer_tag
319
- File.basename(Dir.getwd)
320
- end
321
-
322
342
  # Load and use the normal Rack builder if we can, otherwise
323
343
  # fallback to our minimal version.
324
344
  def rack_builder
@@ -367,5 +387,3 @@ module Puma
367
387
  end
368
388
  end
369
389
  end
370
-
371
- require 'puma/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,55 +100,40 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "5.6.4".freeze
104
- CODE_NAME = "Birdie's Version".freeze
103
+ PUMA_VERSION = VERSION = "6.4.2"
104
+ CODE_NAME = "The Eagle of Durango"
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
 
110
- # The default number of seconds for another request within a persistent
111
- # session.
112
- PERSISTENT_TIMEOUT = 20
113
-
114
- # The default number of seconds to wait until we get the first data
115
- # for the request
116
- FIRST_DATA_TIMEOUT = 30
117
-
118
110
  # How long to wait when getting some write blocking on the socket when
119
111
  # sending data back
120
112
  WRITE_TIMEOUT = 10
121
113
 
122
- # How many requests to attempt inline before sending a client back to
123
- # the reactor to be subject to normal ordering. The idea here is that
124
- # we amortize the cost of going back to the reactor for a well behaved
125
- # but very "greedy" client across 10 requests. This prevents a not
126
- # well behaved client from monopolizing the thread forever.
127
- MAX_FAST_INLINE = 10
128
-
129
114
  # The original URI requested by the client.
130
- REQUEST_URI= 'REQUEST_URI'.freeze
131
- REQUEST_PATH = 'REQUEST_PATH'.freeze
132
- QUERY_STRING = 'QUERY_STRING'.freeze
133
- 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"
134
119
 
135
- PATH_INFO = 'PATH_INFO'.freeze
120
+ PATH_INFO = "PATH_INFO"
136
121
 
137
- PUMA_TMP_BASE = "puma".freeze
122
+ PUMA_TMP_BASE = "puma"
138
123
 
139
124
  ERROR_RESPONSE = {
140
125
  # Indicate that we couldn't parse the request
141
- 400 => "HTTP/1.1 400 Bad Request\r\n\r\n".freeze,
126
+ 400 => "HTTP/1.1 400 Bad Request\r\n\r\n",
142
127
  # The standard empty 404 response for bad requests. Use Error4040Handler for custom stuff.
143
- 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",
144
129
  # The standard empty 408 response for requests that timed out.
145
- 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",
146
131
  # Indicate that there was an internal error, obviously.
147
- 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",
148
133
  # Incorrect or invalid header value
149
- 501 => "HTTP/1.1 501 Not Implemented\r\n\r\n".freeze,
134
+ 501 => "HTTP/1.1 501 Not Implemented\r\n\r\n",
150
135
  # A common header for indicating the server is too busy. Not used yet.
151
- 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze
136
+ 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\n"
152
137
  }.freeze
153
138
 
154
139
  # The basic max request size we'll try to read.
@@ -161,84 +146,136 @@ module Puma
161
146
  # Maximum request body size before it is moved out of memory and into a tempfile for reading.
162
147
  MAX_BODY = MAX_HEADER
163
148
 
164
- REQUEST_METHOD = "REQUEST_METHOD".freeze
165
- HEAD = "HEAD".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
+
166
200
  # 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
170
- HTTP_X_FORWARDED_SSL = "HTTP_X_FORWARDED_SSL".freeze
171
- HTTP_X_FORWARDED_SCHEME = "HTTP_X_FORWARDED_SCHEME".freeze
172
- 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"
173
207
 
174
- SERVER_NAME = "SERVER_NAME".freeze
175
- SERVER_PORT = "SERVER_PORT".freeze
176
- HTTP_HOST = "HTTP_HOST".freeze
177
- PORT_80 = "80".freeze
178
- PORT_443 = "443".freeze
179
- LOCALHOST = "localhost".freeze
180
- LOCALHOST_IP = "127.0.0.1".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 = "::"
181
218
 
182
- SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
183
- HTTP_11 = "HTTP/1.1".freeze
219
+ SERVER_PROTOCOL = "SERVER_PROTOCOL"
220
+ HTTP_11 = "HTTP/1.1"
184
221
 
185
- SERVER_SOFTWARE = "SERVER_SOFTWARE".freeze
186
- GATEWAY_INTERFACE = "GATEWAY_INTERFACE".freeze
187
- CGI_VER = "CGI/1.2".freeze
222
+ SERVER_SOFTWARE = "SERVER_SOFTWARE"
223
+ GATEWAY_INTERFACE = "GATEWAY_INTERFACE"
224
+ CGI_VER = "CGI/1.2"
188
225
 
189
- STOP_COMMAND = "?".freeze
190
- HALT_COMMAND = "!".freeze
191
- RESTART_COMMAND = "R".freeze
226
+ STOP_COMMAND = "?"
227
+ HALT_COMMAND = "!"
228
+ RESTART_COMMAND = "R"
192
229
 
193
- RACK_INPUT = "rack.input".freeze
194
- RACK_URL_SCHEME = "rack.url_scheme".freeze
195
- RACK_AFTER_REPLY = "rack.after_reply".freeze
196
- PUMA_SOCKET = "puma.socket".freeze
197
- PUMA_CONFIG = "puma.config".freeze
198
- 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"
199
236
 
200
- HTTP = "http".freeze
201
- HTTPS = "https".freeze
237
+ HTTP = "http"
238
+ HTTPS = "https"
202
239
 
203
- HTTPS_KEY = "HTTPS".freeze
240
+ HTTPS_KEY = "HTTPS"
204
241
 
205
- HTTP_VERSION = "HTTP_VERSION".freeze
206
- HTTP_CONNECTION = "HTTP_CONNECTION".freeze
207
- HTTP_EXPECT = "HTTP_EXPECT".freeze
208
- CONTINUE = "100-continue".freeze
242
+ HTTP_VERSION = "HTTP_VERSION"
243
+ HTTP_CONNECTION = "HTTP_CONNECTION"
244
+ HTTP_EXPECT = "HTTP_EXPECT"
245
+ CONTINUE = "100-continue"
209
246
 
210
- HTTP_11_100 = "HTTP/1.1 100 Continue\r\n\r\n".freeze
211
- HTTP_11_200 = "HTTP/1.1 200 OK\r\n".freeze
212
- 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"
213
250
 
214
- CLOSE = "close".freeze
215
- KEEP_ALIVE = "keep-alive".freeze
251
+ CLOSE = "close"
252
+ KEEP_ALIVE = "keep-alive"
216
253
 
217
- CONTENT_LENGTH2 = "content-length".freeze
218
- CONTENT_LENGTH_S = "Content-Length: ".freeze
219
- TRANSFER_ENCODING = "transfer-encoding".freeze
220
- 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"
221
258
 
222
- CONNECTION_CLOSE = "Connection: close\r\n".freeze
223
- 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"
224
261
 
225
- TRANSFER_ENCODING_CHUNKED = "Transfer-Encoding: chunked\r\n".freeze
226
- 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"
227
264
 
228
- CHUNKED = "chunked".freeze
265
+ CHUNKED = "chunked"
229
266
 
230
- COLON = ": ".freeze
267
+ COLON = ": "
231
268
 
232
- NEWLINE = "\n".freeze
269
+ NEWLINE = "\n"
233
270
 
234
- HIJACK_P = "rack.hijack?".freeze
235
- HIJACK = "rack.hijack".freeze
236
- HIJACK_IO = "rack.hijack_io".freeze
271
+ HIJACK_P = "rack.hijack?"
272
+ HIJACK = "rack.hijack"
273
+ HIJACK_IO = "rack.hijack_io"
237
274
 
238
- EARLY_HINTS = "rack.early_hints".freeze
275
+ EARLY_HINTS = "rack.early_hints"
239
276
 
240
277
  # Illegal character in the key or value of response header
241
- DQUOTE = "\"".freeze
278
+ DQUOTE = "\""
242
279
  HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
243
280
  ILLEGAL_HEADER_KEY_REGEX = /[\x00-\x20#{DQUOTE}#{HTTP_HEADER_DELIMITER}]/.freeze
244
281
  # header values can contain HTAB?
@@ -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
 
@@ -17,26 +15,27 @@ module Puma
17
15
  CMD_PATH_SIG_MAP = {
18
16
  'gc' => nil,
19
17
  'gc-stats' => nil,
20
- 'halt' => 'SIGQUIT',
21
- 'phased-restart' => 'SIGUSR1',
22
- 'refork' => 'SIGURG',
18
+ 'halt' => 'SIGQUIT',
19
+ 'info' => 'SIGINFO',
20
+ 'phased-restart' => 'SIGUSR1',
21
+ 'refork' => 'SIGURG',
23
22
  'reload-worker-directory' => nil,
24
- 'restart' => 'SIGUSR2',
23
+ 'reopen-log' => 'SIGHUP',
24
+ 'restart' => 'SIGUSR2',
25
25
  'start' => nil,
26
26
  'stats' => nil,
27
27
  'status' => '',
28
- 'stop' => 'SIGTERM',
29
- 'thread-backtraces' => nil
28
+ 'stop' => 'SIGTERM',
29
+ 'thread-backtraces' => nil,
30
+ 'worker-count-down' => 'SIGTTOU',
31
+ 'worker-count-up' => 'SIGTTIN'
30
32
  }.freeze
31
33
 
32
- # @deprecated 6.0.0
33
- COMMANDS = CMD_PATH_SIG_MAP.keys.freeze
34
-
35
34
  # commands that cannot be used in a request
36
- NO_REQ_COMMANDS = %w{refork}.freeze
35
+ NO_REQ_COMMANDS = %w[info reopen-log worker-count-down worker-count-up].freeze
37
36
 
38
37
  # @version 5.0.0
39
- PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}.freeze
38
+ PRINTABLE_COMMANDS = %w[gc-stats stats thread-backtraces].freeze
40
39
 
41
40
  def initialize(argv, stdout=STDOUT, stderr=STDERR)
42
41
  @state = nil
@@ -125,6 +124,9 @@ module Puma
125
124
  end
126
125
 
127
126
  if @config_file
127
+ require_relative 'configuration'
128
+ require_relative 'log_writer'
129
+
128
130
  config = Puma::Configuration.new({ config_files: [@config_file] }, {})
129
131
  config.load
130
132
  @state ||= config.options[:state]
@@ -148,6 +150,8 @@ module Puma
148
150
  raise "State file not found: #{@state}"
149
151
  end
150
152
 
153
+ require_relative 'state_file'
154
+
151
155
  sf = Puma::StateFile.new
152
156
  sf.load @state
153
157
 
@@ -163,30 +167,32 @@ module Puma
163
167
  def send_request
164
168
  uri = URI.parse @control_url
165
169
 
170
+ host = uri.host
171
+
166
172
  # create server object by scheme
167
173
  server =
168
174
  case uri.scheme
169
175
  when 'ssl'
170
176
  require 'openssl'
177
+ host = host[1..-2] if host&.start_with? '['
171
178
  OpenSSL::SSL::SSLSocket.new(
172
- TCPSocket.new(uri.host, uri.port),
179
+ TCPSocket.new(host, uri.port),
173
180
  OpenSSL::SSL::SSLContext.new)
174
181
  .tap { |ssl| ssl.sync_close = true } # default is false
175
182
  .tap(&:connect)
176
183
  when 'tcp'
177
- TCPSocket.new uri.host, uri.port
184
+ host = host[1..-2] if host&.start_with? '['
185
+ TCPSocket.new host, uri.port
178
186
  when 'unix'
179
187
  # check for abstract UNIXSocket
180
188
  UNIXSocket.new(@control_url.start_with?('unix://@') ?
181
- "\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}")
189
+ "\0#{host}#{uri.path}" : "#{host}#{uri.path}")
182
190
  else
183
191
  raise "Invalid scheme: #{uri.scheme}"
184
192
  end
185
193
 
186
194
  if @command == 'status'
187
195
  message 'Puma is started'
188
- elsif NO_REQ_COMMANDS.include? @command
189
- raise "Invalid request command: #{@command}"
190
196
  else
191
197
  url = "/#{@command}"
192
198
 
@@ -242,7 +248,11 @@ module Puma
242
248
  @stdout.flush unless @stdout.sync
243
249
  return
244
250
  elsif sig.start_with? 'SIG'
245
- Process.kill sig, @pid
251
+ if Signal.list.key? sig.sub(/\ASIG/, '')
252
+ Process.kill sig, @pid
253
+ else
254
+ raise "Signal '#{sig}' not available'"
255
+ end
246
256
  elsif @command == 'status'
247
257
  begin
248
258
  Process.kill 0, @pid
@@ -268,7 +278,7 @@ module Puma
268
278
  return start if @command == 'start'
269
279
  prepare_configuration
270
280
 
271
- if Puma.windows? || @control_url
281
+ if Puma.windows? || @control_url && !NO_REQ_COMMANDS.include?(@command)
272
282
  send_request
273
283
  else
274
284
  send_signal
@@ -281,7 +291,7 @@ module Puma
281
291
 
282
292
  private
283
293
  def start
284
- require 'puma/cli'
294
+ require_relative 'cli'
285
295
 
286
296
  run_args = []
287
297
 
@@ -293,13 +303,13 @@ module Puma
293
303
  run_args += ["-C", @config_file] if @config_file
294
304
  run_args += ["-e", @environment] if @environment
295
305
 
296
- events = Puma::Events.new @stdout, @stderr
306
+ log_writer = Puma::LogWriter.new(@stdout, @stderr)
297
307
 
298
308
  # replace $0 because puma use it to generate restart command
299
309
  puma_cmd = $0.gsub(/pumactl$/, 'puma')
300
310
  $0 = puma_cmd if File.exist?(puma_cmd)
301
311
 
302
- cli = Puma::CLI.new run_args, events
312
+ cli = Puma::CLI.new run_args, log_writer
303
313
  cli.run
304
314
  end
305
315
  end
data/lib/puma/detect.rb CHANGED
@@ -8,15 +8,18 @@ module Puma
8
8
  # @version 5.2.1
9
9
  HAS_FORK = ::Process.respond_to? :fork
10
10
 
11
+ HAS_NATIVE_IO_WAIT = ::IO.public_instance_methods(false).include? :wait_readable
12
+
11
13
  IS_JRUBY = Object.const_defined? :JRUBY_VERSION
12
14
 
13
- IS_OSX = RUBY_PLATFORM.include? 'darwin'
15
+ IS_OSX = RUBY_DESCRIPTION.include? 'darwin'
16
+
17
+ IS_WINDOWS = RUBY_DESCRIPTION.match?(/mswin|ming|cygwin/)
14
18
 
15
- IS_WINDOWS = !!(RUBY_PLATFORM =~ /mswin|ming|cygwin/) ||
16
- IS_JRUBY && RUBY_DESCRIPTION.include?('mswin')
19
+ IS_LINUX = !(IS_OSX || IS_WINDOWS)
17
20
 
18
21
  # @version 5.2.0
19
- IS_MRI = (RUBY_ENGINE == 'ruby' || RUBY_ENGINE.nil?)
22
+ IS_MRI = RUBY_ENGINE == 'ruby'
20
23
 
21
24
  def self.jruby?
22
25
  IS_JRUBY