puma 3.11.1 → 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 (98) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +2092 -422
  3. data/LICENSE +23 -20
  4. data/README.md +301 -69
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +59 -21
  7. data/docs/compile_options.md +55 -0
  8. data/docs/deployment.md +69 -58
  9. data/docs/fork_worker.md +41 -0
  10. data/docs/java_options.md +54 -0
  11. data/docs/jungle/README.md +9 -0
  12. data/docs/jungle/rc.d/README.md +74 -0
  13. data/docs/jungle/rc.d/puma +61 -0
  14. data/docs/jungle/rc.d/puma.conf +10 -0
  15. data/docs/kubernetes.md +78 -0
  16. data/docs/nginx.md +2 -2
  17. data/docs/plugins.md +26 -12
  18. data/docs/rails_dev_mode.md +28 -0
  19. data/docs/restart.md +48 -22
  20. data/docs/signals.md +13 -11
  21. data/docs/stats.md +147 -0
  22. data/docs/systemd.md +108 -117
  23. data/docs/testing_benchmarks_local_files.md +150 -0
  24. data/docs/testing_test_rackup_ci_files.md +36 -0
  25. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  26. data/ext/puma_http11/ext_help.h +1 -1
  27. data/ext/puma_http11/extconf.rb +68 -3
  28. data/ext/puma_http11/http11_parser.c +106 -118
  29. data/ext/puma_http11/http11_parser.h +2 -2
  30. data/ext/puma_http11/http11_parser.java.rl +22 -38
  31. data/ext/puma_http11/http11_parser.rl +6 -4
  32. data/ext/puma_http11/http11_parser_common.rl +6 -6
  33. data/ext/puma_http11/mini_ssl.c +474 -94
  34. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  35. data/ext/puma_http11/org/jruby/puma/Http11.java +136 -121
  36. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +84 -99
  37. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +251 -88
  38. data/ext/puma_http11/puma_http11.c +53 -58
  39. data/lib/puma/app/status.rb +71 -49
  40. data/lib/puma/binder.rb +257 -151
  41. data/lib/puma/cli.rb +61 -38
  42. data/lib/puma/client.rb +464 -224
  43. data/lib/puma/cluster/worker.rb +183 -0
  44. data/lib/puma/cluster/worker_handle.rb +96 -0
  45. data/lib/puma/cluster.rb +343 -239
  46. data/lib/puma/commonlogger.rb +23 -14
  47. data/lib/puma/configuration.rb +144 -96
  48. data/lib/puma/const.rb +194 -115
  49. data/lib/puma/control_cli.rb +135 -81
  50. data/lib/puma/detect.rb +34 -2
  51. data/lib/puma/dsl.rb +1092 -153
  52. data/lib/puma/error_logger.rb +113 -0
  53. data/lib/puma/events.rb +17 -111
  54. data/lib/puma/io_buffer.rb +44 -5
  55. data/lib/puma/jruby_restart.rb +2 -73
  56. data/lib/puma/json_serialization.rb +96 -0
  57. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  58. data/lib/puma/launcher.rb +205 -138
  59. data/lib/puma/log_writer.rb +147 -0
  60. data/lib/puma/minissl/context_builder.rb +96 -0
  61. data/lib/puma/minissl.rb +279 -70
  62. data/lib/puma/null_io.rb +61 -2
  63. data/lib/puma/plugin/systemd.rb +90 -0
  64. data/lib/puma/plugin/tmp_restart.rb +3 -1
  65. data/lib/puma/plugin.rb +9 -13
  66. data/lib/puma/rack/builder.rb +10 -11
  67. data/lib/puma/rack/urlmap.rb +3 -1
  68. data/lib/puma/rack_default.rb +21 -4
  69. data/lib/puma/reactor.rb +97 -185
  70. data/lib/puma/request.rb +688 -0
  71. data/lib/puma/runner.rb +114 -69
  72. data/lib/puma/sd_notify.rb +146 -0
  73. data/lib/puma/server.rb +409 -704
  74. data/lib/puma/single.rb +29 -72
  75. data/lib/puma/state_file.rb +48 -9
  76. data/lib/puma/thread_pool.rb +234 -93
  77. data/lib/puma/util.rb +23 -10
  78. data/lib/puma.rb +68 -5
  79. data/lib/rack/handler/puma.rb +119 -86
  80. data/tools/Dockerfile +16 -0
  81. data/tools/trickletest.rb +0 -1
  82. metadata +55 -33
  83. data/ext/puma_http11/io_buffer.c +0 -155
  84. data/lib/puma/accept_nonblock.rb +0 -23
  85. data/lib/puma/compat.rb +0 -14
  86. data/lib/puma/convenient.rb +0 -23
  87. data/lib/puma/daemon_ext.rb +0 -31
  88. data/lib/puma/delegation.rb +0 -11
  89. data/lib/puma/java_io_buffer.rb +0 -45
  90. data/lib/puma/rack/backports/uri/common_193.rb +0 -33
  91. data/lib/puma/tcp_logger.rb +0 -39
  92. data/tools/jungle/README.md +0 -13
  93. data/tools/jungle/init.d/README.md +0 -59
  94. data/tools/jungle/init.d/puma +0 -421
  95. data/tools/jungle/init.d/run-puma +0 -18
  96. data/tools/jungle/upstart/README.md +0 -61
  97. data/tools/jungle/upstart/puma-manager.conf +0 -31
  98. data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma/binder.rb CHANGED
@@ -1,29 +1,41 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'uri'
2
4
  require 'socket'
3
5
 
4
- require 'puma/const'
5
- require 'puma/util'
6
+ require_relative 'const'
7
+ require_relative 'util'
8
+ require_relative 'configuration'
6
9
 
7
10
  module Puma
11
+
12
+ if HAS_SSL
13
+ require_relative 'minissl'
14
+ require_relative 'minissl/context_builder'
15
+ end
16
+
8
17
  class Binder
9
18
  include Puma::Const
10
19
 
11
- RACK_VERSION = [1,3].freeze
20
+ RACK_VERSION = [1,6].freeze
12
21
 
13
- def initialize(events)
14
- @events = events
22
+ def initialize(log_writer, conf = Configuration.new, env: ENV)
23
+ @log_writer = log_writer
24
+ @conf = conf
15
25
  @listeners = []
16
26
  @inherited_fds = {}
17
27
  @activated_sockets = {}
18
28
  @unix_paths = []
29
+ @env = env
19
30
 
20
31
  @proto_env = {
21
32
  "rack.version".freeze => RACK_VERSION,
22
- "rack.errors".freeze => events.stderr,
23
- "rack.multithread".freeze => true,
24
- "rack.multiprocess".freeze => false,
33
+ "rack.errors".freeze => log_writer.stderr,
34
+ "rack.multithread".freeze => conf.options[:max_threads] > 1,
35
+ "rack.multiprocess".freeze => conf.options[:workers] >= 1,
25
36
  "rack.run_once".freeze => false,
26
- "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
37
+ RACK_URL_SCHEME => conf.options[:rack_url_scheme],
38
+ "SCRIPT_NAME".freeze => env['SCRIPT_NAME'] || "",
27
39
 
28
40
  # I'd like to set a default CONTENT_TYPE here but some things
29
41
  # depend on their not being a default set and inferring
@@ -31,7 +43,6 @@ module Puma
31
43
  # infer properly.
32
44
 
33
45
  "QUERY_STRING".freeze => "",
34
- SERVER_PROTOCOL => HTTP_11,
35
46
  SERVER_SOFTWARE => PUMA_SERVER_STRING,
36
47
  GATEWAY_INTERFACE => CGI_VER
37
48
  }
@@ -40,7 +51,13 @@ module Puma
40
51
  @ios = []
41
52
  end
42
53
 
43
- attr_reader :listeners, :ios
54
+ attr_reader :ios
55
+
56
+ # @version 5.0.0
57
+ attr_reader :activated_sockets, :envs, :inherited_fds, :listeners, :proto_env, :unix_paths
58
+
59
+ # @version 5.0.0
60
+ attr_writer :ios, :listeners
44
61
 
45
62
  def env(sock)
46
63
  @envs.fetch(sock, @proto_env)
@@ -48,76 +65,137 @@ module Puma
48
65
 
49
66
  def close
50
67
  @ios.each { |i| i.close }
51
- @unix_paths.each { |i| File.unlink i }
52
68
  end
53
69
 
54
- def import_from_env
55
- remove = []
56
-
57
- ENV.each do |k,v|
58
- if k =~ /PUMA_INHERIT_\d+/
59
- fd, url = v.split(":", 2)
60
- @inherited_fds[url] = fd.to_i
61
- remove << k
62
- elsif k == 'LISTEN_FDS' && ENV['LISTEN_PID'].to_i == $$
63
- v.to_i.times do |num|
64
- fd = num + 3
65
- sock = TCPServer.for_fd(fd)
66
- begin
67
- key = [ :unix, Socket.unpack_sockaddr_un(sock.getsockname) ]
68
- rescue ArgumentError
69
- port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
70
- if addr =~ /\:/
71
- addr = "[#{addr}]"
72
- end
73
- key = [ :tcp, addr, port ]
74
- end
75
- @activated_sockets[key] = sock
76
- @events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
77
- end
78
- remove << k << 'LISTEN_PID'
70
+ # @!attribute [r] connected_ports
71
+ # @version 5.0.0
72
+ def connected_ports
73
+ t = ios.map { |io| io.addr[1] }; t.uniq!; t
74
+ end
75
+
76
+ # @version 5.0.0
77
+ def create_inherited_fds(env_hash)
78
+ env_hash.select {|k,v| k =~ /PUMA_INHERIT_\d+/}.each do |_k, v|
79
+ fd, url = v.split(":", 2)
80
+ @inherited_fds[url] = fd.to_i
81
+ end.keys # pass keys back for removal
82
+ end
83
+
84
+ # systemd socket activation.
85
+ # LISTEN_FDS = number of listening sockets. e.g. 2 means accept on 2 sockets w/descriptors 3 and 4.
86
+ # LISTEN_PID = PID of the service process, aka us
87
+ # @see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
88
+ # @version 5.0.0
89
+ #
90
+ def create_activated_fds(env_hash)
91
+ @log_writer.debug "ENV['LISTEN_FDS'] #{@env['LISTEN_FDS'].inspect} env_hash['LISTEN_PID'] #{env_hash['LISTEN_PID'].inspect}"
92
+ return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
93
+ env_hash['LISTEN_FDS'].to_i.times do |index|
94
+ sock = TCPServer.for_fd(socket_activation_fd(index))
95
+ key = begin # Try to parse as a path
96
+ [:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
97
+ rescue ArgumentError # Try to parse as a port/ip
98
+ port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
99
+ addr = "[#{addr}]" if addr&.include? ':'
100
+ [:tcp, addr, port]
101
+ end
102
+ @activated_sockets[key] = sock
103
+ @log_writer.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
104
+ end
105
+ ["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
106
+ end
107
+
108
+ # Synthesize binds from systemd socket activation
109
+ #
110
+ # When systemd socket activation is enabled, it can be tedious to keep the
111
+ # binds in sync. This method can synthesize any binds based on the received
112
+ # activated sockets. Any existing matching binds will be respected.
113
+ #
114
+ # When only_matching is true in, all binds that do not match an activated
115
+ # socket is removed in place.
116
+ #
117
+ # It's a noop if no activated sockets were received.
118
+ def synthesize_binds_from_activated_fs(binds, only_matching)
119
+ return binds unless activated_sockets.any?
120
+
121
+ activated_binds = []
122
+
123
+ activated_sockets.keys.each do |proto, addr, port|
124
+ if port
125
+ tcp_url = "#{proto}://#{addr}:#{port}"
126
+ ssl_url = "ssl://#{addr}:#{port}"
127
+ ssl_url_prefix = "#{ssl_url}?"
128
+
129
+ existing = binds.find { |bind| bind == tcp_url || bind == ssl_url || bind.start_with?(ssl_url_prefix) }
130
+
131
+ activated_binds << (existing || tcp_url)
132
+ else
133
+ # TODO: can there be a SSL bind without a port?
134
+ activated_binds << "#{proto}://#{addr}"
79
135
  end
80
136
  end
81
137
 
82
- remove.each do |k|
83
- ENV.delete k
138
+ if only_matching
139
+ activated_binds
140
+ else
141
+ binds | activated_binds
84
142
  end
85
143
  end
86
144
 
87
- def parse(binds, logger)
145
+ def before_parse(&block)
146
+ @before_parse ||= []
147
+ @before_parse << block if block
148
+ @before_parse
149
+ end
150
+
151
+ def parse(binds, log_writer = nil, log_msg = 'Listening')
152
+ before_parse.each(&:call)
153
+ log_writer ||= @log_writer
88
154
  binds.each do |str|
89
155
  uri = URI.parse str
90
156
  case uri.scheme
91
157
  when "tcp"
92
158
  if fd = @inherited_fds.delete(str)
93
- logger.log "* Inherited #{str}"
94
159
  io = inherit_tcp_listener uri.host, uri.port, fd
160
+ log_writer.log "* Inherited #{str}"
95
161
  elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
96
- logger.log "* Activated #{str}"
97
162
  io = inherit_tcp_listener uri.host, uri.port, sock
163
+ log_writer.log "* Activated #{str}"
98
164
  else
165
+ ios_len = @ios.length
99
166
  params = Util.parse_query uri.query
100
167
 
101
- opt = params.key?('low_latency')
102
- bak = params.fetch('backlog', 1024).to_i
168
+ low_latency = params.key?('low_latency') && params['low_latency'] != 'false'
169
+ backlog = params.fetch('backlog', 1024).to_i
103
170
 
104
- logger.log "* Listening on #{str}"
105
- io = add_tcp_listener uri.host, uri.port, opt, bak
171
+ io = add_tcp_listener uri.host, uri.port, low_latency, backlog
172
+
173
+ @ios[ios_len..-1].each do |i|
174
+ addr = loc_addr_str i
175
+ log_writer.log "* #{log_msg} on http://#{addr}"
176
+ end
106
177
  end
107
178
 
108
179
  @listeners << [str, io] if io
109
180
  when "unix"
110
181
  path = "#{uri.host}#{uri.path}".gsub("%20", " ")
182
+ abstract = false
183
+ if str.start_with? 'unix://@'
184
+ raise "OS does not support abstract UNIXSockets" unless Puma.abstract_unix_socket?
185
+ abstract = true
186
+ path = "@#{path}"
187
+ end
111
188
 
112
189
  if fd = @inherited_fds.delete(str)
113
- logger.log "* Inherited #{str}"
190
+ @unix_paths << path unless abstract || File.exist?(path)
114
191
  io = inherit_unix_listener path, fd
115
- elsif sock = @activated_sockets.delete([ :unix, path ])
116
- logger.log "* Activated #{str}"
192
+ log_writer.log "* Inherited #{str}"
193
+ elsif sock = @activated_sockets.delete([ :unix, path ]) ||
194
+ !abstract && @activated_sockets.delete([ :unix, File.realdirpath(path) ])
195
+ @unix_paths << path unless abstract || File.exist?(path)
117
196
  io = inherit_unix_listener path, sock
197
+ log_writer.log "* Activated #{str}"
118
198
  else
119
- logger.log "* Listening on #{str}"
120
-
121
199
  umask = nil
122
200
  mode = nil
123
201
  backlog = 1024
@@ -138,87 +216,67 @@ module Puma
138
216
  end
139
217
  end
140
218
 
219
+ @unix_paths << path unless abstract || File.exist?(path)
141
220
  io = add_unix_listener path, umask, mode, backlog
221
+ log_writer.log "* #{log_msg} on #{str}"
142
222
  end
143
223
 
144
224
  @listeners << [str, io]
145
225
  when "ssl"
146
- params = Util.parse_query uri.query
147
- require 'puma/minissl'
148
-
149
- MiniSSL.check
150
-
151
- ctx = MiniSSL::Context.new
152
-
153
- if defined?(JRUBY_VERSION)
154
- unless params['keystore']
155
- @events.error "Please specify the Java keystore via 'keystore='"
156
- end
226
+ cert_key = %w[cert key]
157
227
 
158
- ctx.keystore = params['keystore']
228
+ raise "Puma compiled without SSL support" unless HAS_SSL
159
229
 
160
- unless params['keystore-pass']
161
- @events.error "Please specify the Java keystore password via 'keystore-pass='"
162
- end
163
-
164
- ctx.keystore_pass = params['keystore-pass']
165
- else
166
- unless params['key']
167
- @events.error "Please specify the SSL key via 'key='"
168
- end
169
-
170
- ctx.key = params['key']
171
-
172
- unless params['cert']
173
- @events.error "Please specify the SSL cert via 'cert='"
174
- end
230
+ params = Util.parse_query uri.query
175
231
 
176
- ctx.cert = params['cert']
232
+ # If key and certs are not defined and localhost gem is required.
233
+ # localhost gem will be used for self signed
234
+ # Load localhost authority if not loaded.
235
+ # Ruby 3 `values_at` accepts an array, earlier do not
236
+ if params.values_at(*cert_key).all? { |v| v.to_s.empty? }
237
+ ctx = localhost_authority && localhost_authority_context
238
+ end
177
239
 
178
- if ['peer', 'force_peer'].include?(params['verify_mode'])
179
- unless params['ca']
180
- @events.error "Please specify the SSL ca via 'ca='"
240
+ ctx ||=
241
+ begin
242
+ # Extract cert_pem and key_pem from options[:store] if present
243
+ cert_key.each do |v|
244
+ if params[v]&.start_with?('store:')
245
+ index = Integer(params.delete(v).split('store:').last)
246
+ params["#{v}_pem"] = @conf.options[:store][index]
247
+ end
181
248
  end
249
+ MiniSSL::ContextBuilder.new(params, @log_writer).context
182
250
  end
183
251
 
184
- ctx.ca = params['ca'] if params['ca']
185
- end
186
-
187
- if params['verify_mode']
188
- ctx.verify_mode = case params['verify_mode']
189
- when "peer"
190
- MiniSSL::VERIFY_PEER
191
- when "force_peer"
192
- MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
193
- when "none"
194
- MiniSSL::VERIFY_NONE
195
- else
196
- @events.error "Please specify a valid verify_mode="
197
- MiniSSL::VERIFY_NONE
198
- end
199
- end
200
-
201
252
  if fd = @inherited_fds.delete(str)
202
- logger.log "* Inherited #{str}"
253
+ log_writer.log "* Inherited #{str}"
203
254
  io = inherit_ssl_listener fd, ctx
204
255
  elsif sock = @activated_sockets.delete([ :tcp, uri.host, uri.port ])
205
- logger.log "* Activated #{str}"
206
256
  io = inherit_ssl_listener sock, ctx
257
+ log_writer.log "* Activated #{str}"
207
258
  else
208
- logger.log "* Listening on #{str}"
209
- io = add_ssl_listener uri.host, uri.port, ctx
259
+ ios_len = @ios.length
260
+ backlog = params.fetch('backlog', 1024).to_i
261
+ low_latency = params['low_latency'] != 'false'
262
+ io = add_ssl_listener uri.host, uri.port, ctx, low_latency, backlog
263
+
264
+ @ios[ios_len..-1].each do |i|
265
+ addr = loc_addr_str i
266
+ log_writer.log "* #{log_msg} on ssl://#{addr}?#{uri.query}"
267
+ end
210
268
  end
211
269
 
212
270
  @listeners << [str, io] if io
213
271
  else
214
- logger.error "Invalid URI: #{str}"
272
+ log_writer.error "Invalid URI: #{str}"
215
273
  end
216
274
  end
217
275
 
218
276
  # If we inherited fds but didn't use them (because of a
219
277
  # configuration change), then be sure to close them.
220
278
  @inherited_fds.each do |str, fd|
221
- logger.log "* Closing unused inherited connection: #{str}"
279
+ log_writer.log "* Closing unused inherited connection: #{str}"
222
280
 
223
281
  begin
224
282
  IO.for_fd(fd).close
@@ -234,21 +292,35 @@ module Puma
234
292
  end
235
293
 
236
294
  # Also close any unused activated sockets
237
- @activated_sockets.each do |key, sock|
238
- logger.log "* Closing unused activated socket: #{key.join ':'}"
239
- begin
240
- sock.close
241
- rescue SystemCallError
295
+ unless @activated_sockets.empty?
296
+ fds = @ios.map(&:to_i)
297
+ @activated_sockets.each do |key, sock|
298
+ next if fds.include? sock.to_i
299
+ log_writer.log "* Closing unused activated socket: #{key.first}://#{key[1..-1].join ':'}"
300
+ begin
301
+ sock.close
302
+ rescue SystemCallError
303
+ end
304
+ # We have to unlink a unix socket path that's not being used
305
+ File.unlink key[1] if key.first == :unix
242
306
  end
243
- # We have to unlink a unix socket path that's not being used
244
- File.unlink key[1] if key[0] == :unix
245
307
  end
246
308
  end
247
309
 
248
- def loopback_addresses
249
- Socket.ip_address_list.select do |addrinfo|
250
- addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
251
- end.map { |addrinfo| addrinfo.ip_address }.uniq
310
+ def localhost_authority
311
+ @localhost_authority ||= Localhost::Authority.fetch if defined?(Localhost::Authority) && !Puma::IS_JRUBY
312
+ end
313
+
314
+ def localhost_authority_context
315
+ return unless localhost_authority
316
+
317
+ key_path, crt_path = if [:key_path, :certificate_path].all? { |m| localhost_authority.respond_to?(m) }
318
+ [localhost_authority.key_path, localhost_authority.certificate_path]
319
+ else
320
+ local_certificates_path = File.expand_path("~/.localhost")
321
+ [File.join(local_certificates_path, "localhost.key"), File.join(local_certificates_path, "localhost.crt")]
322
+ end
323
+ MiniSSL::ContextBuilder.new({ "key" => key_path, "cert" => crt_path }, @log_writer).context
252
324
  end
253
325
 
254
326
  # Tell the server to listen on host +host+, port +port+.
@@ -266,27 +338,21 @@ module Puma
266
338
  return
267
339
  end
268
340
 
269
- host = host[1..-2] if host and host[0..0] == '['
270
- s = TCPServer.new(host, port)
341
+ host = host[1..-2] if host&.start_with? '['
342
+ tcp_server = TCPServer.new(host, port)
343
+
271
344
  if optimize_for_latency
272
- s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
345
+ tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
273
346
  end
274
- s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
275
- s.listen backlog
276
- @connected_port = s.addr[1]
347
+ tcp_server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
348
+ tcp_server.listen backlog
277
349
 
278
- @ios << s
279
- s
350
+ @ios << tcp_server
351
+ tcp_server
280
352
  end
281
353
 
282
- attr_reader :connected_port
283
-
284
354
  def inherit_tcp_listener(host, port, fd)
285
- if fd.kind_of? TCPServer
286
- s = fd
287
- else
288
- s = TCPServer.for_fd(fd)
289
- end
355
+ s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
290
356
 
291
357
  @ios << s
292
358
  s
@@ -294,9 +360,10 @@ module Puma
294
360
 
295
361
  def add_ssl_listener(host, port, ctx,
296
362
  optimize_for_latency=true, backlog=1024)
297
- require 'puma/minissl'
298
363
 
299
- MiniSSL.check
364
+ raise "Puma compiled without SSL support" unless HAS_SSL
365
+ # Puma will try to use local authority context if context is supplied nil
366
+ ctx ||= localhost_authority_context
300
367
 
301
368
  if host == "localhost"
302
369
  loopback_addresses.each do |addr|
@@ -305,7 +372,7 @@ module Puma
305
372
  return
306
373
  end
307
374
 
308
- host = host[1..-2] if host[0..0] == '['
375
+ host = host[1..-2] if host&.start_with? '['
309
376
  s = TCPServer.new(host, port)
310
377
  if optimize_for_latency
311
378
  s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
@@ -323,14 +390,12 @@ module Puma
323
390
  end
324
391
 
325
392
  def inherit_ssl_listener(fd, ctx)
326
- require 'puma/minissl'
327
- MiniSSL.check
393
+ raise "Puma compiled without SSL support" unless HAS_SSL
394
+ # Puma will try to use local authority context if context is supplied nil
395
+ ctx ||= localhost_authority_context
396
+
397
+ s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
328
398
 
329
- if fd.kind_of? TCPServer
330
- s = fd
331
- else
332
- s = TCPServer.for_fd(fd)
333
- end
334
399
  ssl = MiniSSL::Server.new(s, ctx)
335
400
 
336
401
  env = @proto_env.dup
@@ -345,8 +410,6 @@ module Puma
345
410
  # Tell the server to listen on +path+ as a UNIX domain socket.
346
411
  #
347
412
  def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
348
- @unix_paths << path
349
-
350
413
  # Let anyone connect by default
351
414
  umask ||= 0
352
415
 
@@ -363,8 +426,7 @@ module Puma
363
426
  raise "There is already a server bound to: #{path}"
364
427
  end
365
428
  end
366
-
367
- s = UNIXServer.new(path)
429
+ s = UNIXServer.new path.sub(/\A@/, "\0") # check for abstract UNIXSocket
368
430
  s.listen backlog
369
431
  @ios << s
370
432
  ensure
@@ -383,13 +445,8 @@ module Puma
383
445
  end
384
446
 
385
447
  def inherit_unix_listener(path, fd)
386
- @unix_paths << path
448
+ s = fd.kind_of?(::TCPServer) ? fd : ::UNIXServer.for_fd(fd)
387
449
 
388
- if fd.kind_of? TCPServer
389
- s = fd
390
- else
391
- s = UNIXServer.for_fd fd
392
- end
393
450
  @ios << s
394
451
 
395
452
  env = @proto_env.dup
@@ -399,5 +456,54 @@ module Puma
399
456
  s
400
457
  end
401
458
 
459
+ def close_listeners
460
+ @listeners.each do |l, io|
461
+ begin
462
+ io.close unless io.closed?
463
+ uri = URI.parse l
464
+ next unless uri.scheme == 'unix'
465
+ unix_path = "#{uri.host}#{uri.path}"
466
+ File.unlink unix_path if @unix_paths.include?(unix_path) && File.exist?(unix_path)
467
+ rescue Errno::EBADF
468
+ end
469
+ end
470
+ end
471
+
472
+ def redirects_for_restart
473
+ redirects = @listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
474
+ redirects[:close_others] = true
475
+ redirects
476
+ end
477
+
478
+ # @version 5.0.0
479
+ def redirects_for_restart_env
480
+ @listeners.each_with_object({}).with_index do |(listen, memo), i|
481
+ memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
482
+ end
483
+ end
484
+
485
+ private
486
+
487
+ # @!attribute [r] loopback_addresses
488
+ def loopback_addresses
489
+ t = Socket.ip_address_list.select do |addrinfo|
490
+ addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
491
+ end
492
+ t.map! { |addrinfo| addrinfo.ip_address }; t.uniq!; t
493
+ end
494
+
495
+ def loc_addr_str(io)
496
+ loc_addr = io.to_io.local_address
497
+ if loc_addr.ipv6?
498
+ "[#{loc_addr.ip_unpack[0]}]:#{loc_addr.ip_unpack[1]}"
499
+ else
500
+ loc_addr.ip_unpack.join(':')
501
+ end
502
+ end
503
+
504
+ # @version 5.0.0
505
+ def socket_activation_fd(int)
506
+ int + 3 # 3 is the magic number you add to follow the SA protocol
507
+ end
402
508
  end
403
509
  end