puma 5.0.3 → 5.2.1

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +110 -40
  3. data/README.md +48 -18
  4. data/docs/compile_options.md +19 -0
  5. data/docs/deployment.md +1 -1
  6. data/docs/fork_worker.md +2 -0
  7. data/docs/kubernetes.md +66 -0
  8. data/docs/plugins.md +1 -1
  9. data/docs/rails_dev_mode.md +29 -0
  10. data/docs/stats.md +142 -0
  11. data/docs/systemd.md +24 -2
  12. data/ext/puma_http11/extconf.rb +18 -5
  13. data/ext/puma_http11/http11_parser.c +45 -47
  14. data/ext/puma_http11/http11_parser.java.rl +1 -1
  15. data/ext/puma_http11/http11_parser.rl +1 -1
  16. data/ext/puma_http11/mini_ssl.c +162 -84
  17. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +5 -7
  18. data/ext/puma_http11/puma_http11.c +8 -2
  19. data/lib/puma.rb +20 -10
  20. data/lib/puma/app/status.rb +4 -7
  21. data/lib/puma/binder.rb +60 -24
  22. data/lib/puma/cli.rb +4 -0
  23. data/lib/puma/client.rb +4 -9
  24. data/lib/puma/cluster.rb +13 -7
  25. data/lib/puma/cluster/worker.rb +8 -2
  26. data/lib/puma/cluster/worker_handle.rb +5 -2
  27. data/lib/puma/configuration.rb +13 -1
  28. data/lib/puma/const.rb +11 -3
  29. data/lib/puma/control_cli.rb +73 -70
  30. data/lib/puma/detect.rb +14 -10
  31. data/lib/puma/dsl.rb +100 -22
  32. data/lib/puma/error_logger.rb +10 -3
  33. data/lib/puma/events.rb +18 -3
  34. data/lib/puma/json.rb +96 -0
  35. data/lib/puma/launcher.rb +52 -6
  36. data/lib/puma/minissl.rb +48 -17
  37. data/lib/puma/minissl/context_builder.rb +6 -0
  38. data/lib/puma/null_io.rb +4 -0
  39. data/lib/puma/reactor.rb +19 -12
  40. data/lib/puma/request.rb +20 -7
  41. data/lib/puma/runner.rb +14 -7
  42. data/lib/puma/server.rb +20 -75
  43. data/lib/puma/state_file.rb +5 -3
  44. data/lib/puma/systemd.rb +46 -0
  45. data/lib/rack/handler/puma.rb +1 -0
  46. metadata +12 -6
data/lib/puma.rb CHANGED
@@ -12,12 +12,31 @@ require 'thread'
12
12
 
13
13
  require 'puma/puma_http11'
14
14
  require 'puma/detect'
15
+ require 'puma/json'
15
16
 
16
17
  module Puma
17
18
  autoload :Const, 'puma/const'
18
19
  autoload :Server, 'puma/server'
19
20
  autoload :Launcher, 'puma/launcher'
20
21
 
22
+ # at present, MiniSSL::Engine is only defined in extension code (puma_http11),
23
+ # not in minissl.rb
24
+ HAS_SSL = const_defined?(:MiniSSL, false) && MiniSSL.const_defined?(:Engine, false)
25
+
26
+ if HAS_SSL
27
+ require 'puma/minissl'
28
+ else
29
+ module MiniSSL
30
+ # this class is defined so that it exists when Puma is compiled
31
+ # without ssl support, as Server and Reactor use it in rescue statements.
32
+ class SSLError < StandardError ; end
33
+ end
34
+ end
35
+
36
+ def self.ssl?
37
+ HAS_SSL
38
+ end
39
+
21
40
  # @!attribute [rw] stats_object=
22
41
  def self.stats_object=(val)
23
42
  @get_stats = val
@@ -25,8 +44,7 @@ module Puma
25
44
 
26
45
  # @!attribute [rw] stats_object
27
46
  def self.stats
28
- require 'json'
29
- @get_stats.stats.to_json
47
+ Puma::JSON.generate @get_stats.stats
30
48
  end
31
49
 
32
50
  # @!attribute [r] stats_hash
@@ -40,12 +58,4 @@ module Puma
40
58
  return unless Thread.current.respond_to?(:name=)
41
59
  Thread.current.name = "puma #{name}"
42
60
  end
43
-
44
- unless HAS_SSL
45
- module MiniSSL
46
- # this class is defined so that it exists when Puma is compiled
47
- # without ssl support, as Server and Reactor use it in rescue statements.
48
- class SSLError < StandardError ; end
49
- end
50
- end
51
61
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require 'puma/json'
2
3
 
3
4
  module Puma
4
5
  module App
@@ -22,10 +23,6 @@ module Puma
22
23
  return rack_response(403, 'Invalid auth token', 'text/plain')
23
24
  end
24
25
 
25
- if env['PATH_INFO'] =~ /\/(gc-stats|stats|thread-backtraces)$/
26
- require 'json'
27
- end
28
-
29
26
  # resp_type is processed by following case statement, return
30
27
  # is a number (status) or a string used as the body of a 200 response
31
28
  resp_type =
@@ -49,17 +46,17 @@ module Puma
49
46
  GC.start ; 200
50
47
 
51
48
  when 'gc-stats'
52
- GC.stat.to_json
49
+ Puma::JSON.generate GC.stat
53
50
 
54
51
  when 'stats'
55
- @launcher.stats.to_json
52
+ Puma::JSON.generate @launcher.stats
56
53
 
57
54
  when 'thread-backtraces'
58
55
  backtraces = []
59
56
  @launcher.thread_status do |name, backtrace|
60
57
  backtraces << { name: name, backtrace: backtrace }
61
58
  end
62
- backtraces.to_json
59
+ Puma::JSON.generate backtraces
63
60
 
64
61
  else
65
62
  return rack_response(404, "Unsupported action", 'text/plain')
data/lib/puma/binder.rb CHANGED
@@ -111,6 +111,43 @@ module Puma
111
111
  ["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
112
112
  end
113
113
 
114
+ # Synthesize binds from systemd socket activation
115
+ #
116
+ # When systemd socket activation is enabled, it can be tedious to keep the
117
+ # binds in sync. This method can synthesize any binds based on the received
118
+ # activated sockets. Any existing matching binds will be respected.
119
+ #
120
+ # When only_matching is true in, all binds that do not match an activated
121
+ # socket is removed in place.
122
+ #
123
+ # It's a noop if no activated sockets were received.
124
+ def synthesize_binds_from_activated_fs(binds, only_matching)
125
+ return binds unless activated_sockets.any?
126
+
127
+ activated_binds = []
128
+
129
+ activated_sockets.keys.each do |proto, addr, port|
130
+ if port
131
+ tcp_url = "#{proto}://#{addr}:#{port}"
132
+ ssl_url = "ssl://#{addr}:#{port}"
133
+ ssl_url_prefix = "#{ssl_url}?"
134
+
135
+ existing = binds.find { |bind| bind == tcp_url || bind == ssl_url || bind.start_with?(ssl_url_prefix) }
136
+
137
+ activated_binds << (existing || tcp_url)
138
+ else
139
+ # TODO: can there be a SSL bind without a port?
140
+ activated_binds << "#{proto}://#{addr}"
141
+ end
142
+ end
143
+
144
+ if only_matching
145
+ activated_binds
146
+ else
147
+ binds | activated_binds
148
+ end
149
+ end
150
+
114
151
  def parse(binds, logger, log_msg = 'Listening')
115
152
  binds.each do |str|
116
153
  uri = URI.parse str
@@ -123,6 +160,7 @@ module Puma
123
160
  io = inherit_tcp_listener uri.host, uri.port, sock
124
161
  logger.log "* Activated #{str}"
125
162
  else
163
+ ios_len = @ios.length
126
164
  params = Util.parse_query uri.query
127
165
 
128
166
  opt = params.key?('low_latency')
@@ -130,14 +168,8 @@ module Puma
130
168
 
131
169
  io = add_tcp_listener uri.host, uri.port, opt, bak
132
170
 
133
- @ios.each do |i|
134
- next unless TCPServer === i
135
- addr = if i.local_address.ipv6?
136
- "[#{i.local_address.ip_unpack[0]}]:#{i.local_address.ip_unpack[1]}"
137
- else
138
- i.local_address.ip_unpack.join(':')
139
- end
140
-
171
+ @ios[ios_len..-1].each do |i|
172
+ addr = loc_addr_str i
141
173
  logger.log "* #{log_msg} on http://#{addr}"
142
174
  end
143
175
  end
@@ -192,8 +224,13 @@ module Puma
192
224
  io = inherit_ssl_listener sock, ctx
193
225
  logger.log "* Activated #{str}"
194
226
  else
227
+ ios_len = @ios.length
195
228
  io = add_ssl_listener uri.host, uri.port, ctx
196
- logger.log "* Listening on #{str}"
229
+
230
+ @ios[ios_len..-1].each do |i|
231
+ addr = loc_addr_str i
232
+ logger.log "* #{log_msg} on ssl://#{addr}?#{uri.query}"
233
+ end
197
234
  end
198
235
 
199
236
  @listeners << [str, io] if io
@@ -260,11 +297,7 @@ module Puma
260
297
  end
261
298
 
262
299
  def inherit_tcp_listener(host, port, fd)
263
- if fd.kind_of? TCPServer
264
- s = fd
265
- else
266
- s = TCPServer.for_fd(fd)
267
- end
300
+ s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
268
301
 
269
302
  @ios << s
270
303
  s
@@ -302,11 +335,8 @@ module Puma
302
335
  def inherit_ssl_listener(fd, ctx)
303
336
  raise "Puma compiled without SSL support" unless HAS_SSL
304
337
 
305
- if fd.kind_of? TCPServer
306
- s = fd
307
- else
308
- s = TCPServer.for_fd(fd)
309
- end
338
+ s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
339
+
310
340
  ssl = MiniSSL::Server.new(s, ctx)
311
341
 
312
342
  env = @proto_env.dup
@@ -361,11 +391,8 @@ module Puma
361
391
  def inherit_unix_listener(path, fd)
362
392
  @unix_paths << path unless File.exist? path
363
393
 
364
- if fd.kind_of? TCPServer
365
- s = fd
366
- else
367
- s = UNIXServer.for_fd fd
368
- end
394
+ s = fd.kind_of?(::TCPServer) ? fd : ::UNIXServer.for_fd(fd)
395
+
369
396
  @ios << s
370
397
 
371
398
  env = @proto_env.dup
@@ -407,6 +434,15 @@ module Puma
407
434
  end.map { |addrinfo| addrinfo.ip_address }.uniq
408
435
  end
409
436
 
437
+ def loc_addr_str(io)
438
+ loc_addr = io.to_io.local_address
439
+ if loc_addr.ipv6?
440
+ "[#{loc_addr.ip_unpack[0]}]:#{loc_addr.ip_unpack[1]}"
441
+ else
442
+ loc_addr.ip_unpack.join(':')
443
+ end
444
+ end
445
+
410
446
  # @version 5.0.0
411
447
  def socket_activation_fd(int)
412
448
  int + 3 # 3 is the magic number you add to follow the SA protocol
data/lib/puma/cli.rb CHANGED
@@ -104,6 +104,10 @@ module Puma
104
104
  user_config.bind arg
105
105
  end
106
106
 
107
+ o.on "--bind-to-activated-sockets [only]", "Bind to all activated sockets" do |arg|
108
+ user_config.bind_to_activated_sockets(arg || true)
109
+ end
110
+
107
111
  o.on "-C", "--config PATH", "Load PATH as a config file" do |arg|
108
112
  file_config.load arg
109
113
  end
data/lib/puma/client.rb CHANGED
@@ -239,13 +239,8 @@ module Puma
239
239
  # @version 5.0.0
240
240
  #
241
241
  def can_close?
242
- # Allow connection to close if it's received at least one full request
243
- # and hasn't received any data for a future request.
244
- #
245
- # From RFC 2616 section 8.1.4:
246
- # Servers SHOULD always respond to at least one request per connection,
247
- # if at all possible.
248
- @requests_served > 0 && @parsed_bytes == 0
242
+ # Allow connection to close if we're not in the middle of parsing a request.
243
+ @parsed_bytes == 0
249
244
  end
250
245
 
251
246
  private
@@ -379,7 +374,7 @@ module Puma
379
374
  end
380
375
 
381
376
  if decode_chunk(chunk)
382
- @env[CONTENT_LENGTH] = @chunked_content_length
377
+ @env[CONTENT_LENGTH] = @chunked_content_length.to_s
383
378
  return true
384
379
  end
385
380
  end
@@ -396,7 +391,7 @@ module Puma
396
391
  @chunked_content_length = 0
397
392
 
398
393
  if decode_chunk(body)
399
- @env[CONTENT_LENGTH] = @chunked_content_length
394
+ @env[CONTENT_LENGTH] = @chunked_content_length.to_s
400
395
  return true
401
396
  end
402
397
  end
data/lib/puma/cluster.rb CHANGED
@@ -114,7 +114,7 @@ module Puma
114
114
  debug "Workers to cull: #{workers_to_cull.inspect}"
115
115
 
116
116
  workers_to_cull.each do |worker|
117
- log "- Worker #{worker.index} (pid: #{worker.pid}) terminating"
117
+ log "- Worker #{worker.index} (PID: #{worker.pid}) terminating"
118
118
  worker.term
119
119
  end
120
120
  end
@@ -186,10 +186,12 @@ module Puma
186
186
  pipes[:wakeup] = @wakeup
187
187
  end
188
188
 
189
+ server = start_server if preload?
189
190
  new_worker = Worker.new index: index,
190
191
  master: master,
191
192
  launcher: @launcher,
192
- pipes: pipes
193
+ pipes: pipes,
194
+ server: server
193
195
  new_worker.run
194
196
  end
195
197
 
@@ -327,15 +329,19 @@ module Puma
327
329
 
328
330
  output_header "cluster"
329
331
 
330
- log "* Process workers: #{@options[:workers]}"
332
+ # This is aligned with the output from Runner, see Runner#output_header
333
+ log "* Workers: #{@options[:workers]}"
331
334
 
332
- before = Thread.list
335
+ # Threads explicitly marked as fork safe will be ignored.
336
+ # Used in Rails, but may be used by anyone.
337
+ before = Thread.list.reject { |t| t.thread_variable_get(:fork_safe) }
333
338
 
334
339
  if preload?
340
+ log "* Restarts: (\u2714) hot (\u2716) phased"
335
341
  log "* Preloading application"
336
342
  load_and_bind
337
343
 
338
- after = Thread.list
344
+ after = Thread.list.reject { |t| t.thread_variable_get(:fork_safe) }
339
345
 
340
346
  if after.size > before.size
341
347
  threads = (after - before)
@@ -349,7 +355,7 @@ module Puma
349
355
  end
350
356
  end
351
357
  else
352
- log "* Phased restart available"
358
+ log "* Restarts: (\u2714) hot (\u2714) phased"
353
359
 
354
360
  unless @launcher.config.app_configured?
355
361
  error "No application configured, nothing to run"
@@ -428,7 +434,7 @@ module Puma
428
434
  case req
429
435
  when "b"
430
436
  w.boot!
431
- log "- Worker #{w.index} (pid: #{pid}) booted, phase: #{w.phase}"
437
+ log "- Worker #{w.index} (PID: #{pid}) booted, phase: #{w.phase}"
432
438
  @next_check = Time.now
433
439
  when "e"
434
440
  # external term, see worker method, Signal.trap "SIGTERM"
@@ -108,11 +108,17 @@ module Puma
108
108
  server_thread = server.run
109
109
  stat_thread ||= Thread.new(@worker_write) do |io|
110
110
  Puma.set_thread_name "stat payload"
111
+ base_payload = "p#{Process.pid}"
111
112
 
112
113
  while true
113
114
  begin
114
- require 'json'
115
- io << "p#{Process.pid}#{server.stats.to_json}\n"
115
+ b = server.backlog || 0
116
+ r = server.running || 0
117
+ t = server.pool_capacity || 0
118
+ m = server.max_threads || 0
119
+ rc = server.requests_count || 0
120
+ payload = %Q!#{base_payload}{ "backlog":#{b}, "running":#{r}, "pool_capacity":#{t}, "max_threads": #{m}, "requests_count": #{rc} }\n!
121
+ io << payload
116
122
  rescue IOError
117
123
  Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
118
124
  break
@@ -42,8 +42,11 @@ module Puma
42
42
 
43
43
  def ping!(status)
44
44
  @last_checkin = Time.now
45
- require 'json'
46
- @last_status = JSON.parse(status, symbolize_names: true)
45
+ captures = status.match(/{ "backlog":(?<backlog>\d*), "running":(?<running>\d*), "pool_capacity":(?<pool_capacity>\d*), "max_threads": (?<max_threads>\d*), "requests_count": (?<requests_count>\d*) }/)
46
+ @last_status = captures.names.inject({}) do |hash, key|
47
+ hash[key.to_sym] = captures[key].to_i
48
+ hash
49
+ end
47
50
  end
48
51
 
49
52
  # @see Puma::Cluster#check_workers
@@ -92,6 +92,12 @@ module Puma
92
92
  end
93
93
  end
94
94
  end
95
+
96
+ def final_options
97
+ default_options
98
+ .merge(file_options)
99
+ .merge(user_options)
100
+ end
95
101
  end
96
102
 
97
103
  # The main configuration class of Puma.
@@ -198,7 +204,9 @@ module Puma
198
204
  :logger => STDOUT,
199
205
  :persistent_timeout => Const::PERSISTENT_TIMEOUT,
200
206
  :first_data_timeout => Const::FIRST_DATA_TIMEOUT,
201
- :raise_exception_on_sigterm => true
207
+ :raise_exception_on_sigterm => true,
208
+ :max_fast_inline => Const::MAX_FAST_INLINE,
209
+ :io_selector_backend => :auto
202
210
  }
203
211
  end
204
212
 
@@ -289,6 +297,10 @@ module Puma
289
297
  end
290
298
  end
291
299
 
300
+ def final_options
301
+ @options.final_options
302
+ end
303
+
292
304
  def self.temp_path
293
305
  require 'tmpdir'
294
306
 
data/lib/puma/const.rb CHANGED
@@ -100,8 +100,8 @@ module Puma
100
100
  # too taxing on performance.
101
101
  module Const
102
102
 
103
- PUMA_VERSION = VERSION = "5.0.3".freeze
104
- CODE_NAME = "Spoony Bard".freeze
103
+ PUMA_VERSION = VERSION = "5.2.1".freeze
104
+ CODE_NAME = "Fettisdagsbulle".freeze
105
105
 
106
106
  PUMA_SERVER_STRING = ['puma', PUMA_VERSION, CODE_NAME].join(' ').freeze
107
107
 
@@ -228,7 +228,6 @@ module Puma
228
228
  COLON = ": ".freeze
229
229
 
230
230
  NEWLINE = "\n".freeze
231
- HTTP_INJECTION_REGEX = /[\r\n]/.freeze
232
231
 
233
232
  HIJACK_P = "rack.hijack?".freeze
234
233
  HIJACK = "rack.hijack".freeze
@@ -239,5 +238,14 @@ module Puma
239
238
  # Mininum interval to checks worker health
240
239
  WORKER_CHECK_INTERVAL = 5
241
240
 
241
+ # Illegal character in the key or value of response header
242
+ DQUOTE = "\"".freeze
243
+ HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}\\").freeze
244
+ ILLEGAL_HEADER_KEY_REGEX = /[\x00-\x20#{DQUOTE}#{HTTP_HEADER_DELIMITER}]/.freeze
245
+ # header values can contain HTAB?
246
+ ILLEGAL_HEADER_VALUE_REGEX = /[\x00-\x08\x0A-\x1F]/.freeze
247
+
248
+ # Banned keys of response header
249
+ BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze
242
250
  end
243
251
  end
@@ -11,10 +11,32 @@ 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 thread-backtraces refork}
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
+ 'phased-restart' => 'SIGUSR1',
22
+ 'refork' => 'SIGURG',
23
+ 'reload-worker-directory' => nil,
24
+ 'restart' => 'SIGUSR2',
25
+ 'start' => nil,
26
+ 'stats' => nil,
27
+ 'status' => '',
28
+ 'stop' => 'SIGTERM',
29
+ 'thread-backtraces' => nil
30
+ }.freeze
31
+
32
+ # @deprecated 6.0.0
33
+ COMMANDS = CMD_PATH_SIG_MAP.keys.freeze
34
+
35
+ # commands that cannot be used in a request
36
+ NO_REQ_COMMANDS = %w{refork}.freeze
15
37
 
16
38
  # @version 5.0.0
17
- PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}
39
+ PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}.freeze
18
40
 
19
41
  def initialize(argv, stdout=STDOUT, stderr=STDERR)
20
42
  @state = nil
@@ -33,7 +55,7 @@ module Puma
33
55
  @cli_options = {}
34
56
 
35
57
  opts = OptionParser.new do |o|
36
- o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{COMMANDS.join("|")})"
58
+ o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{CMD_PATH_SIG_MAP.keys.join("|")})"
37
59
 
38
60
  o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
39
61
  @state = arg
@@ -74,7 +96,7 @@ module Puma
74
96
  end
75
97
 
76
98
  o.on_tail("-V", "--version", "Show version") do
77
- puts Const::PUMA_VERSION
99
+ @stdout.puts Const::PUMA_VERSION
78
100
  exit
79
101
  end
80
102
  end
@@ -86,10 +108,10 @@ module Puma
86
108
 
87
109
  # check presence of command
88
110
  unless @command
89
- raise "Available commands: #{COMMANDS.join(", ")}"
111
+ raise "Available commands: #{CMD_PATH_SIG_MAP.keys.join(", ")}"
90
112
  end
91
113
 
92
- unless COMMANDS.include? @command
114
+ unless CMD_PATH_SIG_MAP.key? @command
93
115
  raise "Invalid command: #{@command}"
94
116
  end
95
117
 
@@ -134,7 +156,7 @@ module Puma
134
156
  @pid = sf.pid
135
157
  elsif @pidfile
136
158
  # get pid from pid_file
137
- File.open(@pidfile) { |f| @pid = f.read.to_i }
159
+ @pid = File.read(@pidfile, mode: 'rb:UTF-8').to_i
138
160
  end
139
161
  end
140
162
 
@@ -142,24 +164,27 @@ module Puma
142
164
  uri = URI.parse @control_url
143
165
 
144
166
  # create server object by scheme
145
- server = case uri.scheme
146
- when "ssl"
147
- require 'openssl'
148
- OpenSSL::SSL::SSLSocket.new(
149
- TCPSocket.new(uri.host, uri.port),
150
- OpenSSL::SSL::SSLContext.new)
151
- .tap { |ssl| ssl.sync_close = true } # default is false
152
- .tap(&:connect)
153
- when "tcp"
154
- TCPSocket.new uri.host, uri.port
155
- when "unix"
156
- UNIXSocket.new "#{uri.host}#{uri.path}"
157
- else
158
- raise "Invalid scheme: #{uri.scheme}"
159
- end
160
-
161
- if @command == "status"
162
- message "Puma is started"
167
+ server =
168
+ case uri.scheme
169
+ when 'ssl'
170
+ require 'openssl'
171
+ OpenSSL::SSL::SSLSocket.new(
172
+ TCPSocket.new(uri.host, uri.port),
173
+ OpenSSL::SSL::SSLContext.new)
174
+ .tap { |ssl| ssl.sync_close = true } # default is false
175
+ .tap(&:connect)
176
+ when 'tcp'
177
+ TCPSocket.new uri.host, uri.port
178
+ when 'unix'
179
+ UNIXSocket.new "#{uri.host}#{uri.path}"
180
+ else
181
+ raise "Invalid scheme: #{uri.scheme}"
182
+ end
183
+
184
+ if @command == 'status'
185
+ message 'Puma is started'
186
+ elsif NO_REQ_COMMANDS.include? @command
187
+ raise "Invalid request command: #{@command}"
163
188
  else
164
189
  url = "/#{@command}"
165
190
 
@@ -167,10 +192,10 @@ module Puma
167
192
  url = url + "?token=#{@control_auth_token}"
168
193
  end
169
194
 
170
- server << "GET #{url} HTTP/1.0\r\n\r\n"
195
+ server.syswrite "GET #{url} HTTP/1.0\r\n\r\n"
171
196
 
172
197
  unless data = server.read
173
- raise "Server closed connection before responding"
198
+ raise 'Server closed connection before responding'
174
199
  end
175
200
 
176
201
  response = data.split("\r\n")
@@ -179,13 +204,13 @@ module Puma
179
204
  raise "Server sent empty response"
180
205
  end
181
206
 
182
- (@http,@code,@message) = response.first.split(" ",3)
207
+ @http, @code, @message = response.first.split(' ',3)
183
208
 
184
- if @code == "403"
185
- raise "Unauthorized access to server (wrong auth token)"
186
- elsif @code == "404"
209
+ if @code == '403'
210
+ raise 'Unauthorized access to server (wrong auth token)'
211
+ elsif @code == '404'
187
212
  raise "Command error: #{response.last}"
188
- elsif @code != "200"
213
+ elsif @code != '200'
189
214
  raise "Bad response from server: #{@code}"
190
215
  end
191
216
 
@@ -194,7 +219,7 @@ module Puma
194
219
  end
195
220
  ensure
196
221
  if server
197
- if uri.scheme == "ssl"
222
+ if uri.scheme == 'ssl'
198
223
  server.sysclose
199
224
  else
200
225
  server.close unless server.closed?
@@ -204,51 +229,30 @@ module Puma
204
229
 
205
230
  def send_signal
206
231
  unless @pid
207
- raise "Neither pid nor control url available"
232
+ raise 'Neither pid nor control url available'
208
233
  end
209
234
 
210
235
  begin
236
+ sig = CMD_PATH_SIG_MAP[@command]
211
237
 
212
- case @command
213
- when "restart"
214
- Process.kill "SIGUSR2", @pid
215
-
216
- when "halt"
217
- Process.kill "QUIT", @pid
218
-
219
- when "stop"
220
- Process.kill "SIGTERM", @pid
221
-
222
- when "stats"
223
- puts "Stats not available via pid only"
224
- return
225
-
226
- when "reload-worker-directory"
227
- puts "reload-worker-directory not available via pid only"
238
+ if sig.nil?
239
+ @stdout.puts "'#{@command}' not available via pid only"
240
+ @stdout.flush unless @stdout.sync
228
241
  return
229
-
230
- when "phased-restart"
231
- Process.kill "SIGUSR1", @pid
232
-
233
- when "status"
242
+ elsif sig.start_with? 'SIG'
243
+ Process.kill sig, @pid
244
+ elsif @command == 'status'
234
245
  begin
235
246
  Process.kill 0, @pid
236
- puts "Puma is started"
247
+ @stdout.puts 'Puma is started'
248
+ @stdout.flush unless @stdout.sync
237
249
  rescue Errno::ESRCH
238
- raise "Puma is not running"
250
+ raise 'Puma is not running'
239
251
  end
240
-
241
- return
242
-
243
- when "refork"
244
- Process.kill "SIGURG", @pid
245
-
246
- else
247
252
  return
248
253
  end
249
-
250
254
  rescue SystemCallError
251
- if @command == "restart"
255
+ if @command == 'restart'
252
256
  start
253
257
  else
254
258
  raise "No pid '#{@pid}' found"
@@ -259,14 +263,13 @@ module Puma
259
263
  end
260
264
 
261
265
  def run
262
- return start if @command == "start"
263
-
266
+ return start if @command == 'start'
264
267
  prepare_configuration
265
268
 
266
- if Puma.windows?
269
+ if Puma.windows? || @control_url
267
270
  send_request
268
271
  else
269
- @control_url ? send_request : send_signal
272
+ send_signal
270
273
  end
271
274
 
272
275
  rescue => e