puma 3.12.1 → 5.6.4

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