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