puma 4.3.12 → 6.0.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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1591 -521
  3. data/LICENSE +23 -20
  4. data/README.md +130 -42
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +63 -26
  7. data/docs/compile_options.md +55 -0
  8. data/docs/deployment.md +60 -69
  9. data/docs/fork_worker.md +31 -0
  10. data/docs/jungle/README.md +9 -0
  11. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  12. data/{tools → docs}/jungle/rc.d/puma +2 -2
  13. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  14. data/docs/kubernetes.md +66 -0
  15. data/docs/nginx.md +1 -1
  16. data/docs/plugins.md +15 -15
  17. data/docs/rails_dev_mode.md +28 -0
  18. data/docs/restart.md +46 -23
  19. data/docs/signals.md +13 -11
  20. data/docs/stats.md +142 -0
  21. data/docs/systemd.md +85 -128
  22. data/docs/testing_benchmarks_local_files.md +150 -0
  23. data/docs/testing_test_rackup_ci_files.md +36 -0
  24. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  25. data/ext/puma_http11/ext_help.h +1 -1
  26. data/ext/puma_http11/extconf.rb +49 -12
  27. data/ext/puma_http11/http11_parser.c +46 -48
  28. data/ext/puma_http11/http11_parser.h +2 -2
  29. data/ext/puma_http11/http11_parser.java.rl +3 -3
  30. data/ext/puma_http11/http11_parser.rl +3 -3
  31. data/ext/puma_http11/http11_parser_common.rl +2 -2
  32. data/ext/puma_http11/mini_ssl.c +250 -93
  33. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  34. data/ext/puma_http11/org/jruby/puma/Http11.java +6 -6
  35. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +4 -6
  36. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +241 -96
  37. data/ext/puma_http11/puma_http11.c +46 -57
  38. data/lib/puma/app/status.rb +52 -38
  39. data/lib/puma/binder.rb +232 -119
  40. data/lib/puma/cli.rb +33 -33
  41. data/lib/puma/client.rb +125 -87
  42. data/lib/puma/cluster/worker.rb +175 -0
  43. data/lib/puma/cluster/worker_handle.rb +97 -0
  44. data/lib/puma/cluster.rb +224 -229
  45. data/lib/puma/commonlogger.rb +2 -2
  46. data/lib/puma/configuration.rb +112 -87
  47. data/lib/puma/const.rb +25 -22
  48. data/lib/puma/control_cli.rb +99 -79
  49. data/lib/puma/detect.rb +31 -2
  50. data/lib/puma/dsl.rb +423 -110
  51. data/lib/puma/error_logger.rb +112 -0
  52. data/lib/puma/events.rb +16 -115
  53. data/lib/puma/io_buffer.rb +34 -2
  54. data/lib/puma/jruby_restart.rb +2 -59
  55. data/lib/puma/json_serialization.rb +96 -0
  56. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  57. data/lib/puma/launcher.rb +170 -148
  58. data/lib/puma/log_writer.rb +137 -0
  59. data/lib/puma/minissl/context_builder.rb +35 -19
  60. data/lib/puma/minissl.rb +213 -55
  61. data/lib/puma/null_io.rb +18 -1
  62. data/lib/puma/plugin/tmp_restart.rb +1 -1
  63. data/lib/puma/plugin.rb +3 -12
  64. data/lib/puma/rack/builder.rb +5 -9
  65. data/lib/puma/rack_default.rb +1 -1
  66. data/lib/puma/reactor.rb +85 -369
  67. data/lib/puma/request.rb +607 -0
  68. data/lib/puma/runner.rb +83 -77
  69. data/lib/puma/server.rb +305 -789
  70. data/lib/puma/single.rb +18 -74
  71. data/lib/puma/state_file.rb +45 -8
  72. data/lib/puma/systemd.rb +47 -0
  73. data/lib/puma/thread_pool.rb +137 -66
  74. data/lib/puma/util.rb +21 -4
  75. data/lib/puma.rb +54 -5
  76. data/lib/rack/handler/puma.rb +11 -12
  77. data/tools/{docker/Dockerfile → Dockerfile} +1 -1
  78. metadata +31 -23
  79. data/docs/tcp_mode.md +0 -96
  80. data/ext/puma_http11/io_buffer.c +0 -155
  81. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  82. data/lib/puma/accept_nonblock.rb +0 -29
  83. data/lib/puma/tcp_logger.rb +0 -41
  84. data/tools/jungle/README.md +0 -19
  85. data/tools/jungle/init.d/README.md +0 -61
  86. data/tools/jungle/init.d/puma +0 -421
  87. data/tools/jungle/init.d/run-puma +0 -18
  88. data/tools/jungle/upstart/README.md +0 -61
  89. data/tools/jungle/upstart/puma-manager.conf +0 -31
  90. data/tools/jungle/upstart/puma.conf +0 -69
@@ -1,20 +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
+ # 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
@@ -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,48 @@ 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
+ tag: File.basename(Dir.getwd),
161
+ tcp_host: '0.0.0.0'.freeze,
162
+ tcp_port: 9292,
163
+ wait_for_less_busy_worker: 0.005,
164
+ worker_boot_timeout: 60,
165
+ worker_check_interval: 5,
166
+ worker_culling_strategy: :youngest,
167
+ worker_shutdown_timeout: 30,
168
+ worker_timeout: 60,
169
+ workers: 0,
170
+ }
130
171
 
131
172
  def initialize(user_options={}, default_options = {}, &block)
132
173
  default_options = self.puma_default_options.merge(default_options)
@@ -137,6 +178,10 @@ module Puma
137
178
  @file_dsl = DSL.new(@options.file_options, self)
138
179
  @default_dsl = DSL.new(@options.default_options, self)
139
180
 
181
+ if !@options[:prune_bundler]
182
+ default_options[:preload_app] = (@options[:workers] > 1) && Puma.forkable?
183
+ end
184
+
140
185
  if block
141
186
  configure(&block)
142
187
  end
@@ -168,26 +213,21 @@ module Puma
168
213
  end
169
214
 
170
215
  def puma_default_options
216
+ defaults = DEFAULTS.dup
217
+ puma_options_from_env.each { |k,v| defaults[k] = v if v }
218
+ defaults
219
+ end
220
+
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']
225
+
171
226
  {
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
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'],
191
231
  }
192
232
  end
193
233
 
@@ -203,7 +243,7 @@ module Puma
203
243
  return [] if files == ['-']
204
244
  return files if files.any?
205
245
 
206
- first_default_file = %W(config/puma/#{environment_str}.rb config/puma.rb).find do |f|
246
+ first_default_file = %W(config/puma/#{@options[:environment]}.rb config/puma.rb).find do |f|
207
247
  File.exist?(f)
208
248
  end
209
249
 
@@ -245,16 +285,8 @@ module Puma
245
285
  def app
246
286
  found = options[:app] || load_rackup
247
287
 
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
288
  if @options[:log_requests]
257
- require 'puma/commonlogger'
289
+ require_relative 'commonlogger'
258
290
  logger = @options[:logger]
259
291
  found = CommonLogger.new(found, logger)
260
292
  end
@@ -267,16 +299,31 @@ module Puma
267
299
  @options[:environment]
268
300
  end
269
301
 
270
- def environment_str
271
- environment.respond_to?(:call) ? environment.call : environment
272
- end
273
-
274
302
  def load_plugin(name)
275
303
  @plugins.create name
276
304
  end
277
305
 
278
- def run_hooks(key, arg)
279
- @options.all_of(key).each { |b| b.call arg }
306
+ # @param key [:Symbol] hook to run
307
+ # @param arg [Launcher, Int] `:on_restart` passes Launcher
308
+ #
309
+ def run_hooks(key, arg, log_writer, hook_data = nil)
310
+ @options.all_of(key).each do |b|
311
+ begin
312
+ if Array === b
313
+ hook_data[b[1]] ||= Hash.new
314
+ b[0].call arg, hook_data[b[1]]
315
+ else
316
+ b.call arg
317
+ end
318
+ rescue => e
319
+ log_writer.log "WARNING hook #{key} failed with exception (#{e.class}) #{e.message}"
320
+ log_writer.debug e.backtrace.join("\n")
321
+ end
322
+ end
323
+ end
324
+
325
+ def final_options
326
+ @options.final_options
280
327
  end
281
328
 
282
329
  def self.temp_path
@@ -288,10 +335,6 @@ module Puma
288
335
 
289
336
  private
290
337
 
291
- def infer_tag
292
- File.basename(Dir.getwd)
293
- end
294
-
295
338
  # Load and use the normal Rack builder if we can, otherwise
296
339
  # fallback to our minimal version.
297
340
  def rack_builder
@@ -319,6 +362,8 @@ module Puma
319
362
  raise "Missing rackup file '#{rackup}'" unless File.exist?(rackup)
320
363
 
321
364
  rack_app, rack_options = rack_builder.parse_file(rackup)
365
+ rack_options = rack_options || {}
366
+
322
367
  @options.file_options.merge!(rack_options)
323
368
 
324
369
  config_ru_binds = []
@@ -332,31 +377,11 @@ module Puma
332
377
  end
333
378
 
334
379
  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
380
+ require 'securerandom' unless defined?(SecureRandom)
356
381
 
357
- return token
382
+ SecureRandom.hex(16)
358
383
  end
359
384
  end
360
385
  end
361
386
 
362
- require 'puma/dsl'
387
+ require_relative 'dsl'
data/lib/puma/const.rb CHANGED
@@ -100,31 +100,17 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "4.3.12".freeze
104
- CODE_NAME = "Mysterious Traveller".freeze
103
+ PUMA_VERSION = VERSION = "6.0.0".freeze
104
+ CODE_NAME = "Sunflower".freeze
105
+
105
106
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
106
107
 
107
108
  FAST_TRACK_KA_TIMEOUT = 0.2
108
109
 
109
- # The default number of seconds for another request within a persistent
110
- # session.
111
- PERSISTENT_TIMEOUT = 20
112
-
113
- # The default number of seconds to wait until we get the first data
114
- # for the request
115
- FIRST_DATA_TIMEOUT = 30
116
-
117
110
  # How long to wait when getting some write blocking on the socket when
118
111
  # sending data back
119
112
  WRITE_TIMEOUT = 10
120
113
 
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
114
  # The original URI requested by the client.
129
115
  REQUEST_URI= 'REQUEST_URI'.freeze
130
116
  REQUEST_PATH = 'REQUEST_PATH'.freeze
@@ -162,6 +148,14 @@ module Puma
162
148
 
163
149
  REQUEST_METHOD = "REQUEST_METHOD".freeze
164
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
165
159
  # ETag is based on the apache standard of hex mtime-size-inode (inode is 0 on win32)
166
160
  LINE_END = "\r\n".freeze
167
161
  REMOTE_ADDR = "REMOTE_ADDR".freeze
@@ -176,8 +170,10 @@ module Puma
176
170
  PORT_80 = "80".freeze
177
171
  PORT_443 = "443".freeze
178
172
  LOCALHOST = "localhost".freeze
179
- LOCALHOST_IP = "127.0.0.1".freeze
180
- LOCALHOST_ADDR = "127.0.0.1:0".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
181
177
 
182
178
  SERVER_PROTOCOL = "SERVER_PROTOCOL".freeze
183
179
  HTTP_11 = "HTTP/1.1".freeze
@@ -230,7 +226,6 @@ module Puma
230
226
  COLON = ": ".freeze
231
227
 
232
228
  NEWLINE = "\n".freeze
233
- HTTP_INJECTION_REGEX = /[\r\n]/.freeze
234
229
 
235
230
  HIJACK_P = "rack.hijack?".freeze
236
231
  HIJACK = "rack.hijack".freeze
@@ -238,8 +233,16 @@ module Puma
238
233
 
239
234
  EARLY_HINTS = "rack.early_hints".freeze
240
235
 
241
- # Mininum interval to checks worker health
242
- WORKER_CHECK_INTERVAL = 5
236
+ # Illegal character in the key or value of response header
237
+ DQUOTE = "\"".freeze
238
+ HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
239
+ ILLEGAL_HEADER_KEY_REGEX = /[\x00-\x20#{DQUOTE}#{HTTP_HEADER_DELIMITER}]/.freeze
240
+ # header values can contain HTAB?
241
+ ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/.freeze
242
+
243
+ # Banned keys of response header
244
+ BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
243
245
 
246
+ PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze
244
247
  end
245
248
  end
@@ -11,7 +11,33 @@ require 'socket'
11
11
  module Puma
12
12
  class ControlCLI
13
13
 
14
- COMMANDS = %w{halt restart phased-restart start stats status stop reload-worker-directory gc gc-stats}
14
+ # values must be string or nil
15
+ # value of `nil` means command cannot be processed via signal
16
+ # @version 5.0.3
17
+ CMD_PATH_SIG_MAP = {
18
+ 'gc' => nil,
19
+ 'gc-stats' => nil,
20
+ 'halt' => 'SIGQUIT',
21
+ 'info' => 'SIGINFO',
22
+ 'phased-restart' => 'SIGUSR1',
23
+ 'refork' => 'SIGURG',
24
+ 'reload-worker-directory' => nil,
25
+ 'reopen-log' => 'SIGHUP',
26
+ 'restart' => 'SIGUSR2',
27
+ 'start' => nil,
28
+ 'stats' => nil,
29
+ 'status' => '',
30
+ 'stop' => 'SIGTERM',
31
+ 'thread-backtraces' => nil,
32
+ 'worker-count-down' => 'SIGTTOU',
33
+ 'worker-count-up' => 'SIGTTIN'
34
+ }.freeze
35
+
36
+ # commands that cannot be used in a request
37
+ NO_REQ_COMMANDS = %w[info reopen-log worker-count-down worker-count-up].freeze
38
+
39
+ # @version 5.0.0
40
+ PRINTABLE_COMMANDS = %w[gc-stats stats thread-backtraces].freeze
15
41
 
16
42
  def initialize(argv, stdout=STDOUT, stderr=STDERR)
17
43
  @state = nil
@@ -22,7 +48,7 @@ module Puma
22
48
  @control_auth_token = nil
23
49
  @config_file = nil
24
50
  @command = nil
25
- @environment = ENV['RACK_ENV']
51
+ @environment = ENV['APP_ENV'] || ENV['RACK_ENV'] || ENV['RAILS_ENV']
26
52
 
27
53
  @argv = argv.dup
28
54
  @stdout = stdout
@@ -30,7 +56,7 @@ module Puma
30
56
  @cli_options = {}
31
57
 
32
58
  opts = OptionParser.new do |o|
33
- o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{COMMANDS.join("|")})"
59
+ o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{CMD_PATH_SIG_MAP.keys.join("|")})"
34
60
 
35
61
  o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
36
62
  @state = arg
@@ -71,7 +97,7 @@ module Puma
71
97
  end
72
98
 
73
99
  o.on_tail("-V", "--version", "Show version") do
74
- puts Const::PUMA_VERSION
100
+ @stdout.puts Const::PUMA_VERSION
75
101
  exit
76
102
  end
77
103
  end
@@ -81,6 +107,15 @@ module Puma
81
107
 
82
108
  @command = argv.shift
83
109
 
110
+ # check presence of command
111
+ unless @command
112
+ raise "Available commands: #{CMD_PATH_SIG_MAP.keys.join(", ")}"
113
+ end
114
+
115
+ unless CMD_PATH_SIG_MAP.key? @command
116
+ raise "Invalid command: #{@command}"
117
+ end
118
+
84
119
  unless @config_file == '-'
85
120
  environment = @environment || 'development'
86
121
 
@@ -99,16 +134,6 @@ module Puma
99
134
  @pidfile ||= config.options[:pidfile]
100
135
  end
101
136
  end
102
-
103
- # check present of command
104
- unless @command
105
- raise "Available commands: #{COMMANDS.join(", ")}"
106
- end
107
-
108
- unless COMMANDS.include? @command
109
- raise "Invalid command: #{@command}"
110
- end
111
-
112
137
  rescue => e
113
138
  @stdout.puts e.message
114
139
  exit 1
@@ -132,7 +157,7 @@ module Puma
132
157
  @pid = sf.pid
133
158
  elsif @pidfile
134
159
  # get pid from pid_file
135
- File.open(@pidfile) { |f| @pid = f.read.to_i }
160
+ @pid = File.read(@pidfile, mode: 'rb:UTF-8').to_i
136
161
  end
137
162
  end
138
163
 
@@ -140,23 +165,27 @@ module Puma
140
165
  uri = URI.parse @control_url
141
166
 
142
167
  # create server object by scheme
143
- server = case uri.scheme
144
- when "ssl"
145
- require 'openssl'
146
- OpenSSL::SSL::SSLSocket.new(
147
- TCPSocket.new(uri.host, uri.port),
148
- OpenSSL::SSL::SSLContext.new
149
- ).tap(&:connect)
150
- when "tcp"
151
- TCPSocket.new uri.host, uri.port
152
- when "unix"
153
- UNIXSocket.new "#{uri.host}#{uri.path}"
154
- else
155
- raise "Invalid scheme: #{uri.scheme}"
156
- end
157
-
158
- if @command == "status"
159
- message "Puma is started"
168
+ server =
169
+ case uri.scheme
170
+ when 'ssl'
171
+ require 'openssl'
172
+ OpenSSL::SSL::SSLSocket.new(
173
+ TCPSocket.new(uri.host, uri.port),
174
+ OpenSSL::SSL::SSLContext.new)
175
+ .tap { |ssl| ssl.sync_close = true } # default is false
176
+ .tap(&:connect)
177
+ when 'tcp'
178
+ TCPSocket.new uri.host, uri.port
179
+ when 'unix'
180
+ # check for abstract UNIXSocket
181
+ UNIXSocket.new(@control_url.start_with?('unix://@') ?
182
+ "\0#{uri.host}#{uri.path}" : "#{uri.host}#{uri.path}")
183
+ else
184
+ raise "Invalid scheme: #{uri.scheme}"
185
+ end
186
+
187
+ if @command == 'status'
188
+ message 'Puma is started'
160
189
  else
161
190
  url = "/#{@command}"
162
191
 
@@ -164,10 +193,10 @@ module Puma
164
193
  url = url + "?token=#{@control_auth_token}"
165
194
  end
166
195
 
167
- server << "GET #{url} HTTP/1.0\r\n\r\n"
196
+ server.syswrite "GET #{url} HTTP/1.0\r\n\r\n"
168
197
 
169
198
  unless data = server.read
170
- raise "Server closed connection before responding"
199
+ raise 'Server closed connection before responding'
171
200
  end
172
201
 
173
202
  response = data.split("\r\n")
@@ -176,67 +205,59 @@ module Puma
176
205
  raise "Server sent empty response"
177
206
  end
178
207
 
179
- (@http,@code,@message) = response.first.split(" ",3)
208
+ @http, @code, @message = response.first.split(' ',3)
180
209
 
181
- if @code == "403"
182
- raise "Unauthorized access to server (wrong auth token)"
183
- elsif @code == "404"
210
+ if @code == '403'
211
+ raise 'Unauthorized access to server (wrong auth token)'
212
+ elsif @code == '404'
184
213
  raise "Command error: #{response.last}"
185
- elsif @code != "200"
214
+ elsif @code != '200'
186
215
  raise "Bad response from server: #{@code}"
187
216
  end
188
217
 
189
218
  message "Command #{@command} sent success"
190
- message response.last if @command == "stats" || @command == "gc-stats"
219
+ message response.last if PRINTABLE_COMMANDS.include?(@command)
191
220
  end
192
221
  ensure
193
- server.close if server && !server.closed?
222
+ if server
223
+ if uri.scheme == 'ssl'
224
+ server.sysclose
225
+ else
226
+ server.close unless server.closed?
227
+ end
228
+ end
194
229
  end
195
230
 
196
231
  def send_signal
197
232
  unless @pid
198
- raise "Neither pid nor control url available"
233
+ raise 'Neither pid nor control url available'
199
234
  end
200
235
 
201
236
  begin
237
+ sig = CMD_PATH_SIG_MAP[@command]
202
238
 
203
- case @command
204
- when "restart"
205
- Process.kill "SIGUSR2", @pid
206
-
207
- when "halt"
208
- Process.kill "QUIT", @pid
209
-
210
- when "stop"
211
- Process.kill "SIGTERM", @pid
212
-
213
- when "stats"
214
- puts "Stats not available via pid only"
215
- return
216
-
217
- when "reload-worker-directory"
218
- puts "reload-worker-directory not available via pid only"
239
+ if sig.nil?
240
+ @stdout.puts "'#{@command}' not available via pid only"
241
+ @stdout.flush unless @stdout.sync
219
242
  return
220
-
221
- when "phased-restart"
222
- Process.kill "SIGUSR1", @pid
223
-
224
- when "status"
243
+ elsif sig.start_with? 'SIG'
244
+ if Signal.list.key? sig.sub(/\ASIG/, '')
245
+ Process.kill sig, @pid
246
+ else
247
+ raise "Signal '#{sig}' not available'"
248
+ end
249
+ elsif @command == 'status'
225
250
  begin
226
251
  Process.kill 0, @pid
227
- puts "Puma is started"
252
+ @stdout.puts 'Puma is started'
253
+ @stdout.flush unless @stdout.sync
228
254
  rescue Errno::ESRCH
229
- raise "Puma is not running"
255
+ raise 'Puma is not running'
230
256
  end
231
-
232
- return
233
-
234
- else
235
257
  return
236
258
  end
237
-
238
259
  rescue SystemCallError
239
- if @command == "restart"
260
+ if @command == 'restart'
240
261
  start
241
262
  else
242
263
  raise "No pid '#{@pid}' found"
@@ -247,14 +268,13 @@ module Puma
247
268
  end
248
269
 
249
270
  def run
250
- return start if @command == "start"
251
-
271
+ return start if @command == 'start'
252
272
  prepare_configuration
253
273
 
254
- if Puma.windows?
274
+ if Puma.windows? || @control_url && !NO_REQ_COMMANDS.include?(@command)
255
275
  send_request
256
276
  else
257
- @control_url ? send_request : send_signal
277
+ send_signal
258
278
  end
259
279
 
260
280
  rescue => e
@@ -262,9 +282,9 @@ module Puma
262
282
  exit 1
263
283
  end
264
284
 
265
- private
285
+ private
266
286
  def start
267
- require 'puma/cli'
287
+ require_relative 'cli'
268
288
 
269
289
  run_args = []
270
290
 
@@ -276,13 +296,13 @@ module Puma
276
296
  run_args += ["-C", @config_file] if @config_file
277
297
  run_args += ["-e", @environment] if @environment
278
298
 
279
- events = Puma::Events.new @stdout, @stderr
299
+ log_writer = Puma::LogWriter.new(@stdout, @stderr)
280
300
 
281
301
  # replace $0 because puma use it to generate restart command
282
302
  puma_cmd = $0.gsub(/pumactl$/, 'puma')
283
303
  $0 = puma_cmd if File.exist?(puma_cmd)
284
304
 
285
- cli = Puma::CLI.new run_args, events
305
+ cli = Puma::CLI.new run_args, log_writer
286
306
  cli.run
287
307
  end
288
308
  end