puma 2.2.0 → 8.0.2

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