puma 4.3.6 → 5.3.2

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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +1346 -518
  3. data/LICENSE +23 -20
  4. data/README.md +74 -31
  5. data/bin/puma-wild +3 -9
  6. data/docs/architecture.md +24 -20
  7. data/docs/compile_options.md +19 -0
  8. data/docs/deployment.md +15 -10
  9. data/docs/fork_worker.md +33 -0
  10. data/docs/jungle/README.md +9 -0
  11. data/{tools → docs}/jungle/rc.d/README.md +1 -1
  12. data/{tools → docs}/jungle/rc.d/puma +2 -2
  13. data/{tools → docs}/jungle/rc.d/puma.conf +0 -0
  14. data/docs/kubernetes.md +66 -0
  15. data/docs/nginx.md +1 -1
  16. data/docs/plugins.md +2 -2
  17. data/docs/rails_dev_mode.md +29 -0
  18. data/docs/restart.md +46 -23
  19. data/docs/signals.md +7 -6
  20. data/docs/stats.md +142 -0
  21. data/docs/systemd.md +27 -67
  22. data/ext/puma_http11/PumaHttp11Service.java +2 -4
  23. data/ext/puma_http11/ext_help.h +1 -1
  24. data/ext/puma_http11/extconf.rb +22 -8
  25. data/ext/puma_http11/http11_parser.c +45 -47
  26. data/ext/puma_http11/http11_parser.h +1 -1
  27. data/ext/puma_http11/http11_parser.java.rl +1 -1
  28. data/ext/puma_http11/http11_parser.rl +1 -1
  29. data/ext/puma_http11/mini_ssl.c +211 -118
  30. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  31. data/ext/puma_http11/org/jruby/puma/Http11.java +3 -3
  32. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +5 -7
  33. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +77 -18
  34. data/ext/puma_http11/puma_http11.c +31 -50
  35. data/lib/puma.rb +46 -0
  36. data/lib/puma/app/status.rb +47 -36
  37. data/lib/puma/binder.rb +177 -103
  38. data/lib/puma/cli.rb +11 -15
  39. data/lib/puma/client.rb +73 -74
  40. data/lib/puma/cluster.rb +184 -198
  41. data/lib/puma/cluster/worker.rb +183 -0
  42. data/lib/puma/cluster/worker_handle.rb +90 -0
  43. data/lib/puma/commonlogger.rb +2 -2
  44. data/lib/puma/configuration.rb +55 -49
  45. data/lib/puma/const.rb +13 -5
  46. data/lib/puma/control_cli.rb +93 -76
  47. data/lib/puma/detect.rb +24 -3
  48. data/lib/puma/dsl.rb +266 -92
  49. data/lib/puma/error_logger.rb +104 -0
  50. data/lib/puma/events.rb +55 -34
  51. data/lib/puma/io_buffer.rb +9 -2
  52. data/lib/puma/jruby_restart.rb +0 -58
  53. data/lib/puma/json.rb +96 -0
  54. data/lib/puma/launcher.rb +113 -45
  55. data/lib/puma/minissl.rb +114 -33
  56. data/lib/puma/minissl/context_builder.rb +6 -3
  57. data/lib/puma/null_io.rb +13 -1
  58. data/lib/puma/plugin.rb +1 -10
  59. data/lib/puma/queue_close.rb +26 -0
  60. data/lib/puma/rack/builder.rb +0 -4
  61. data/lib/puma/reactor.rb +85 -369
  62. data/lib/puma/request.rb +467 -0
  63. data/lib/puma/runner.rb +29 -58
  64. data/lib/puma/server.rb +267 -729
  65. data/lib/puma/single.rb +9 -65
  66. data/lib/puma/state_file.rb +8 -3
  67. data/lib/puma/systemd.rb +46 -0
  68. data/lib/puma/thread_pool.rb +119 -53
  69. data/lib/puma/util.rb +12 -0
  70. data/lib/rack/handler/puma.rb +2 -3
  71. data/tools/{docker/Dockerfile → Dockerfile} +0 -0
  72. metadata +25 -21
  73. data/docs/tcp_mode.md +0 -96
  74. data/ext/puma_http11/io_buffer.c +0 -155
  75. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +0 -72
  76. data/lib/puma/accept_nonblock.rb +0 -29
  77. data/lib/puma/tcp_logger.rb +0 -41
  78. data/tools/jungle/README.md +0 -19
  79. data/tools/jungle/init.d/README.md +0 -61
  80. data/tools/jungle/init.d/puma +0 -421
  81. data/tools/jungle/init.d/run-puma +0 -18
  82. data/tools/jungle/upstart/README.md +0 -61
  83. data/tools/jungle/upstart/puma-manager.conf +0 -31
  84. data/tools/jungle/upstart/puma.conf +0 -69
data/lib/puma.rb CHANGED
@@ -10,16 +10,62 @@ require 'stringio'
10
10
 
11
11
  require 'thread'
12
12
 
13
+ require 'puma/puma_http11'
14
+ require 'puma/detect'
15
+ require 'puma/json'
16
+
13
17
  module Puma
14
18
  autoload :Const, 'puma/const'
15
19
  autoload :Server, 'puma/server'
16
20
  autoload :Launcher, 'puma/launcher'
17
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
+ HAS_UNIX_SOCKET = Object.const_defined? :UNIXSocket
27
+
28
+ if HAS_SSL
29
+ require 'puma/minissl'
30
+ else
31
+ module MiniSSL
32
+ # this class is defined so that it exists when Puma is compiled
33
+ # without ssl support, as Server and Reactor use it in rescue statements.
34
+ class SSLError < StandardError ; end
35
+ end
36
+ end
37
+
38
+ def self.ssl?
39
+ HAS_SSL
40
+ end
41
+
42
+ def self.abstract_unix_socket?
43
+ @abstract_unix ||=
44
+ if HAS_UNIX_SOCKET
45
+ begin
46
+ ::UNIXServer.new("\0puma.temp.unix").close
47
+ true
48
+ rescue ArgumentError # darwin
49
+ false
50
+ end
51
+ else
52
+ false
53
+ end
54
+ end
55
+
56
+ # @!attribute [rw] stats_object=
18
57
  def self.stats_object=(val)
19
58
  @get_stats = val
20
59
  end
21
60
 
61
+ # @!attribute [rw] stats_object
22
62
  def self.stats
63
+ Puma::JSON.generate @get_stats.stats
64
+ end
65
+
66
+ # @!attribute [r] stats_hash
67
+ # @version 5.0.0
68
+ def self.stats_hash
23
69
  @get_stats.stats
24
70
  end
25
71
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require 'puma/json'
2
3
 
3
4
  module Puma
4
5
  module App
@@ -7,58 +8,68 @@ module Puma
7
8
  class Status
8
9
  OK_STATUS = '{ "status": "ok" }'.freeze
9
10
 
10
- def initialize(cli, token = nil)
11
- @cli = cli
11
+ # @param launcher [::Puma::Launcher]
12
+ # @param token [String, nil] the token used for authentication
13
+ #
14
+ def initialize(launcher, token = nil)
15
+ @launcher = launcher
12
16
  @auth_token = token
13
17
  end
14
18
 
19
+ # most commands call methods in `::Puma::Launcher` based on command in
20
+ # `env['PATH_INFO']`
15
21
  def call(env)
16
22
  unless authenticate(env)
17
23
  return rack_response(403, 'Invalid auth token', 'text/plain')
18
24
  end
19
25
 
20
- if env['PATH_INFO'] =~ /\/(gc-stats|stats|thread-backtraces)$/
21
- require 'json'
22
- end
26
+ # resp_type is processed by following case statement, return
27
+ # is a number (status) or a string used as the body of a 200 response
28
+ resp_type =
29
+ case env['PATH_INFO'][/\/([^\/]+)$/, 1]
30
+ when 'stop'
31
+ @launcher.stop ; 200
23
32
 
24
- case env['PATH_INFO']
25
- when /\/stop$/
26
- @cli.stop
27
- rack_response(200, OK_STATUS)
33
+ when 'halt'
34
+ @launcher.halt ; 200
28
35
 
29
- when /\/halt$/
30
- @cli.halt
31
- rack_response(200, OK_STATUS)
36
+ when 'restart'
37
+ @launcher.restart ; 200
32
38
 
33
- when /\/restart$/
34
- @cli.restart
35
- rack_response(200, OK_STATUS)
39
+ when 'phased-restart'
40
+ @launcher.phased_restart ? 200 : 404
36
41
 
37
- when /\/phased-restart$/
38
- if !@cli.phased_restart
39
- rack_response(404, '{ "error": "phased restart not available" }')
40
- else
41
- rack_response(200, OK_STATUS)
42
- end
42
+ when 'reload-worker-directory'
43
+ @launcher.send(:reload_worker_directory) ? 200 : 404
43
44
 
44
- when /\/reload-worker-directory$/
45
- if !@cli.send(:reload_worker_directory)
46
- rack_response(404, '{ "error": "reload_worker_directory not available" }')
47
- else
48
- rack_response(200, OK_STATUS)
49
- end
45
+ when 'gc'
46
+ GC.start ; 200
50
47
 
51
- when /\/gc$/
52
- GC.start
53
- rack_response(200, OK_STATUS)
48
+ when 'gc-stats'
49
+ Puma::JSON.generate GC.stat
54
50
 
55
- when /\/gc-stats$/
56
- rack_response(200, GC.stat.to_json)
51
+ when 'stats'
52
+ Puma::JSON.generate @launcher.stats
53
+
54
+ when 'thread-backtraces'
55
+ backtraces = []
56
+ @launcher.thread_status do |name, backtrace|
57
+ backtraces << { name: name, backtrace: backtrace }
58
+ end
59
+ Puma::JSON.generate backtraces
60
+
61
+ else
62
+ return rack_response(404, "Unsupported action", 'text/plain')
63
+ end
57
64
 
58
- when /\/stats$/
59
- rack_response(200, @cli.stats)
60
- else
61
- rack_response 404, "Unsupported action", 'text/plain'
65
+ case resp_type
66
+ when String
67
+ rack_response 200, resp_type
68
+ when 200
69
+ rack_response 200, OK_STATUS
70
+ when 404
71
+ str = env['PATH_INFO'][/\/(\S+)/, 1].tr '-', '_'
72
+ rack_response 404, "{ \"error\": \"#{str} not available\" }"
62
73
  end
63
74
  end
64
75
 
data/lib/puma/binder.rb CHANGED
@@ -5,15 +5,30 @@ require 'socket'
5
5
 
6
6
  require 'puma/const'
7
7
  require 'puma/util'
8
- require 'puma/minissl/context_builder'
8
+ require 'puma/configuration'
9
9
 
10
10
  module Puma
11
+
12
+ if HAS_SSL
13
+ require 'puma/minissl'
14
+ require 'puma/minissl/context_builder'
15
+
16
+ # Odd bug in 'pure Ruby' nio4r version 2.5.2, which installs with Ruby 2.3.
17
+ # NIO doesn't create any OpenSSL objects, but it rescues an OpenSSL error.
18
+ # The bug was that it did not require openssl.
19
+ # @todo remove when Ruby 2.3 support is dropped
20
+ #
21
+ if windows? && RbConfig::CONFIG['ruby_version'] == '2.3.0'
22
+ require 'openssl'
23
+ end
24
+ end
25
+
11
26
  class Binder
12
27
  include Puma::Const
13
28
 
14
- RACK_VERSION = [1,3].freeze
29
+ RACK_VERSION = [1,6].freeze
15
30
 
16
- def initialize(events)
31
+ def initialize(events, conf = Configuration.new)
17
32
  @events = events
18
33
  @listeners = []
19
34
  @inherited_fds = {}
@@ -23,8 +38,8 @@ module Puma
23
38
  @proto_env = {
24
39
  "rack.version".freeze => RACK_VERSION,
25
40
  "rack.errors".freeze => events.stderr,
26
- "rack.multithread".freeze => true,
27
- "rack.multiprocess".freeze => false,
41
+ "rack.multithread".freeze => conf.options[:max_threads] > 1,
42
+ "rack.multiprocess".freeze => conf.options[:workers] >= 1,
28
43
  "rack.run_once".freeze => false,
29
44
  "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
30
45
 
@@ -45,6 +60,12 @@ module Puma
45
60
 
46
61
  attr_reader :ios
47
62
 
63
+ # @version 5.0.0
64
+ attr_reader :activated_sockets, :envs, :inherited_fds, :listeners, :proto_env, :unix_paths
65
+
66
+ # @version 5.0.0
67
+ attr_writer :ios, :listeners
68
+
48
69
  def env(sock)
49
70
  @envs.fetch(sock, @proto_env)
50
71
  end
@@ -53,40 +74,81 @@ module Puma
53
74
  @ios.each { |i| i.close }
54
75
  end
55
76
 
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'
77
+ # @!attribute [r] connected_ports
78
+ # @version 5.0.0
79
+ def connected_ports
80
+ ios.map { |io| io.addr[1] }.uniq
81
+ end
82
+
83
+ # @version 5.0.0
84
+ def create_inherited_fds(env_hash)
85
+ env_hash.select {|k,v| k =~ /PUMA_INHERIT_\d+/}.each do |_k, v|
86
+ fd, url = v.split(":", 2)
87
+ @inherited_fds[url] = fd.to_i
88
+ end.keys # pass keys back for removal
89
+ end
90
+
91
+ # systemd socket activation.
92
+ # LISTEN_FDS = number of listening sockets. e.g. 2 means accept on 2 sockets w/descriptors 3 and 4.
93
+ # LISTEN_PID = PID of the service process, aka us
94
+ # @see https://www.freedesktop.org/software/systemd/man/systemd-socket-activate.html
95
+ # @version 5.0.0
96
+ #
97
+ def create_activated_fds(env_hash)
98
+ return [] unless env_hash['LISTEN_FDS'] && env_hash['LISTEN_PID'].to_i == $$
99
+ env_hash['LISTEN_FDS'].to_i.times do |index|
100
+ sock = TCPServer.for_fd(socket_activation_fd(index))
101
+ key = begin # Try to parse as a path
102
+ [:unix, Socket.unpack_sockaddr_un(sock.getsockname)]
103
+ rescue ArgumentError # Try to parse as a port/ip
104
+ port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
105
+ addr = "[#{addr}]" if addr =~ /\:/
106
+ [:tcp, addr, port]
81
107
  end
108
+ @activated_sockets[key] = sock
109
+ @events.debug "Registered #{key.join ':'} for activation from LISTEN_FDS"
82
110
  end
111
+ ["LISTEN_FDS", "LISTEN_PID"] # Signal to remove these keys from ENV
112
+ end
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?
83
126
 
84
- remove.each do |k|
85
- ENV.delete k
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
86
148
  end
87
149
  end
88
150
 
89
- def parse(binds, logger)
151
+ def parse(binds, logger, log_msg = 'Listening')
90
152
  binds.each do |str|
91
153
  uri = URI.parse str
92
154
  case uri.scheme
@@ -98,33 +160,36 @@ module Puma
98
160
  io = inherit_tcp_listener uri.host, uri.port, sock
99
161
  logger.log "* Activated #{str}"
100
162
  else
163
+ ios_len = @ios.length
101
164
  params = Util.parse_query uri.query
102
165
 
103
- opt = params.key?('low_latency')
166
+ opt = params.key?('low_latency') && params['low_latency'] != 'false'
104
167
  bak = params.fetch('backlog', 1024).to_i
105
168
 
106
169
  io = add_tcp_listener uri.host, uri.port, opt, bak
107
170
 
108
- @ios.each do |i|
109
- next unless TCPServer === i
110
- addr = if i.local_address.ipv6?
111
- "[#{i.local_address.ip_unpack[0]}]:#{i.local_address.ip_unpack[1]}"
112
- else
113
- i.local_address.ip_unpack.join(':')
114
- end
115
-
116
- logger.log "* Listening on tcp://#{addr}"
171
+ @ios[ios_len..-1].each do |i|
172
+ addr = loc_addr_str i
173
+ logger.log "* #{log_msg} on http://#{addr}"
117
174
  end
118
175
  end
119
176
 
120
177
  @listeners << [str, io] if io
121
178
  when "unix"
122
179
  path = "#{uri.host}#{uri.path}".gsub("%20", " ")
180
+ abstract = false
181
+ if str.start_with? 'unix://@'
182
+ raise "OS does not support abstract UNIXSockets" unless Puma.abstract_unix_socket?
183
+ abstract = true
184
+ path = "@#{path}"
185
+ end
123
186
 
124
187
  if fd = @inherited_fds.delete(str)
188
+ @unix_paths << path unless abstract
125
189
  io = inherit_unix_listener path, fd
126
190
  logger.log "* Inherited #{str}"
127
191
  elsif sock = @activated_sockets.delete([ :unix, path ])
192
+ @unix_paths << path unless abstract || File.exist?(path)
128
193
  io = inherit_unix_listener path, sock
129
194
  logger.log "* Activated #{str}"
130
195
  else
@@ -148,12 +213,16 @@ module Puma
148
213
  end
149
214
  end
150
215
 
216
+ @unix_paths << path unless abstract || File.exist?(path)
151
217
  io = add_unix_listener path, umask, mode, backlog
152
- logger.log "* Listening on #{str}"
218
+ logger.log "* #{log_msg} on #{str}"
153
219
  end
154
220
 
155
221
  @listeners << [str, io]
156
222
  when "ssl"
223
+
224
+ raise "Puma compiled without SSL support" unless HAS_SSL
225
+
157
226
  params = Util.parse_query uri.query
158
227
  ctx = MiniSSL::ContextBuilder.new(params, @events).context
159
228
 
@@ -164,8 +233,13 @@ module Puma
164
233
  io = inherit_ssl_listener sock, ctx
165
234
  logger.log "* Activated #{str}"
166
235
  else
236
+ ios_len = @ios.length
167
237
  io = add_ssl_listener uri.host, uri.port, ctx
168
- logger.log "* Listening on #{str}"
238
+
239
+ @ios[ios_len..-1].each do |i|
240
+ addr = loc_addr_str i
241
+ logger.log "* #{log_msg} on ssl://#{addr}?#{uri.query}"
242
+ end
169
243
  end
170
244
 
171
245
  @listeners << [str, io] if io
@@ -193,23 +267,21 @@ module Puma
193
267
  end
194
268
 
195
269
  # Also close any unused activated sockets
196
- @activated_sockets.each do |key, sock|
197
- logger.log "* Closing unused activated socket: #{key.join ':'}"
198
- begin
199
- sock.close
200
- rescue SystemCallError
270
+ unless @activated_sockets.empty?
271
+ fds = @ios.map(&:to_i)
272
+ @activated_sockets.each do |key, sock|
273
+ next if fds.include? sock.to_i
274
+ logger.log "* Closing unused activated socket: #{key.first}://#{key[1..-1].join ':'}"
275
+ begin
276
+ sock.close
277
+ rescue SystemCallError
278
+ end
279
+ # We have to unlink a unix socket path that's not being used
280
+ File.unlink key[1] if key.first == :unix
201
281
  end
202
- # We have to unlink a unix socket path that's not being used
203
- File.unlink key[1] if key[0] == :unix
204
282
  end
205
283
  end
206
284
 
207
- def loopback_addresses
208
- Socket.ip_address_list.select do |addrinfo|
209
- addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
210
- end.map { |addrinfo| addrinfo.ip_address }.uniq
211
- end
212
-
213
285
  # Tell the server to listen on host +host+, port +port+.
214
286
  # If +optimize_for_latency+ is true (the default) then clients connecting
215
287
  # will be optimized for latency over throughput.
@@ -226,26 +298,19 @@ module Puma
226
298
  end
227
299
 
228
300
  host = host[1..-2] if host and host[0..0] == '['
229
- s = TCPServer.new(host, port)
301
+ tcp_server = TCPServer.new(host, port)
230
302
  if optimize_for_latency
231
- s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
303
+ tcp_server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
232
304
  end
233
- s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
234
- s.listen backlog
235
- @connected_port = s.addr[1]
305
+ tcp_server.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
306
+ tcp_server.listen backlog
236
307
 
237
- @ios << s
238
- s
308
+ @ios << tcp_server
309
+ tcp_server
239
310
  end
240
311
 
241
- attr_reader :connected_port
242
-
243
312
  def inherit_tcp_listener(host, port, fd)
244
- if fd.kind_of? TCPServer
245
- s = fd
246
- else
247
- s = TCPServer.for_fd(fd)
248
- end
313
+ s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
249
314
 
250
315
  @ios << s
251
316
  s
@@ -253,9 +318,8 @@ module Puma
253
318
 
254
319
  def add_ssl_listener(host, port, ctx,
255
320
  optimize_for_latency=true, backlog=1024)
256
- require 'puma/minissl'
257
321
 
258
- MiniSSL.check
322
+ raise "Puma compiled without SSL support" unless HAS_SSL
259
323
 
260
324
  if host == "localhost"
261
325
  loopback_addresses.each do |addr|
@@ -272,7 +336,6 @@ module Puma
272
336
  s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
273
337
  s.listen backlog
274
338
 
275
-
276
339
  ssl = MiniSSL::Server.new s, ctx
277
340
  env = @proto_env.dup
278
341
  env[HTTPS_KEY] = HTTPS
@@ -283,14 +346,10 @@ module Puma
283
346
  end
284
347
 
285
348
  def inherit_ssl_listener(fd, ctx)
286
- require 'puma/minissl'
287
- MiniSSL.check
349
+ raise "Puma compiled without SSL support" unless HAS_SSL
350
+
351
+ s = fd.kind_of?(::TCPServer) ? fd : ::TCPServer.for_fd(fd)
288
352
 
289
- if fd.kind_of? TCPServer
290
- s = fd
291
- else
292
- s = TCPServer.for_fd(fd)
293
- end
294
353
  ssl = MiniSSL::Server.new(s, ctx)
295
354
 
296
355
  env = @proto_env.dup
@@ -305,8 +364,6 @@ module Puma
305
364
  # Tell the server to listen on +path+ as a UNIX domain socket.
306
365
  #
307
366
  def add_unix_listener(path, umask=nil, mode=nil, backlog=1024)
308
- @unix_paths << path unless File.exist? path
309
-
310
367
  # Let anyone connect by default
311
368
  umask ||= 0
312
369
 
@@ -323,8 +380,7 @@ module Puma
323
380
  raise "There is already a server bound to: #{path}"
324
381
  end
325
382
  end
326
-
327
- s = UNIXServer.new(path)
383
+ s = UNIXServer.new path.sub(/\A@/, "\0") # check for abstract UNIXSocket
328
384
  s.listen backlog
329
385
  @ios << s
330
386
  ensure
@@ -343,13 +399,8 @@ module Puma
343
399
  end
344
400
 
345
401
  def inherit_unix_listener(path, fd)
346
- @unix_paths << path unless File.exist? path
402
+ s = fd.kind_of?(::TCPServer) ? fd : ::UNIXServer.for_fd(fd)
347
403
 
348
- if fd.kind_of? TCPServer
349
- s = fd
350
- else
351
- s = UNIXServer.for_fd fd
352
- end
353
404
  @ios << s
354
405
 
355
406
  env = @proto_env.dup
@@ -361,25 +412,48 @@ module Puma
361
412
 
362
413
  def close_listeners
363
414
  @listeners.each do |l, io|
364
- io.close
365
- uri = URI.parse(l)
415
+ io.close unless io.closed?
416
+ uri = URI.parse l
366
417
  next unless uri.scheme == 'unix'
367
418
  unix_path = "#{uri.host}#{uri.path}"
368
- File.unlink unix_path if @unix_paths.include? unix_path
419
+ File.unlink unix_path if @unix_paths.include?(unix_path) && File.exist?(unix_path)
369
420
  end
370
421
  end
371
422
 
372
- def close_unix_paths
373
- @unix_paths.each { |up| File.unlink(up) if File.exist? up }
423
+ def redirects_for_restart
424
+ redirects = @listeners.map { |a| [a[1].to_i, a[1].to_i] }.to_h
425
+ redirects[:close_others] = true
426
+ redirects
374
427
  end
375
428
 
376
- def redirects_for_restart
377
- redirects = {:close_others => true}
378
- @listeners.each_with_index do |(l, io), i|
379
- ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
380
- redirects[io.to_i] = io.to_i
429
+ # @version 5.0.0
430
+ def redirects_for_restart_env
431
+ @listeners.each_with_object({}).with_index do |(listen, memo), i|
432
+ memo["PUMA_INHERIT_#{i}"] = "#{listen[1].to_i}:#{listen[0]}"
381
433
  end
382
- redirects
434
+ end
435
+
436
+ private
437
+
438
+ # @!attribute [r] loopback_addresses
439
+ def loopback_addresses
440
+ Socket.ip_address_list.select do |addrinfo|
441
+ addrinfo.ipv6_loopback? || addrinfo.ipv4_loopback?
442
+ end.map { |addrinfo| addrinfo.ip_address }.uniq
443
+ end
444
+
445
+ def loc_addr_str(io)
446
+ loc_addr = io.to_io.local_address
447
+ if loc_addr.ipv6?
448
+ "[#{loc_addr.ip_unpack[0]}]:#{loc_addr.ip_unpack[1]}"
449
+ else
450
+ loc_addr.ip_unpack.join(':')
451
+ end
452
+ end
453
+
454
+ # @version 5.0.0
455
+ def socket_activation_fd(int)
456
+ int + 3 # 3 is the magic number you add to follow the SA protocol
383
457
  end
384
458
  end
385
459
  end