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