0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-09 21:45:25 +08:00
discourse/spec/support/nginx_test_proxy.rb
2026-08-04 08:39:19 -07:00

151 lines
3.6 KiB
Ruby
Vendored

# frozen_string_literal: true
require "fileutils"
require "net/http"
require "socket"
require "tmpdir"
class NginxTestProxy
attr_reader :port
def self.available?
executable.present?
end
def self.executable
override = ENV["NGINX_BIN"]
return override if override.present? && File.executable?(override)
ENV
.fetch("PATH", "")
.split(File::PATH_SEPARATOR)
.map { |path| File.join(path, "nginx") }
.find { |path| File.file?(path) && File.executable?(path) }
end
def initialize(upstream_port:)
@upstream_port = upstream_port
end
def start
@tmpdir = Dir.mktmpdir("nginx-spec-")
@port = available_port
FileUtils.mkdir_p(File.join(@tmpdir, "cache"))
FileUtils.mkdir_p(File.join(@tmpdir, "public"))
FileUtils.mkdir_p(File.join(@tmpdir, "outlets"))
File.write(sample_config_path, test_config)
File.write(wrapper_config_path, wrapper_config)
@pid =
Process.spawn(
self.class.executable,
"-c",
wrapper_config_path,
"-p",
@tmpdir,
out: File.join(@tmpdir, "stdout.log"),
err: File.join(@tmpdir, "stderr.log"),
)
wait_until_ready
self
rescue StandardError
stop
raise
end
def stop
if @pid
Process.kill("TERM", @pid)
Process.wait(@pid)
end
rescue Errno::ECHILD, Errno::ESRCH
nil
ensure
@pid = nil
FileUtils.remove_entry(@tmpdir) if @tmpdir && File.exist?(@tmpdir)
@tmpdir = nil
end
def url
"http://localhost:#{port}"
end
def get(path)
Net::HTTP.get_response(URI("#{url}#{path}"))
end
def access_log
path = File.join(@tmpdir, "access.log")
File.exist?(path) ? File.read(path) : ""
end
private
def test_config
File
.read(Rails.root.join("config/nginx.sample.conf"))
.gsub("server 127.0.0.1:3000;", "server 127.0.0.1:#{@upstream_port};")
.gsub("listen 80;", "listen 127.0.0.1:#{port};")
.gsub("/var/nginx/cache", File.join(@tmpdir, "cache"))
.gsub("/var/log/nginx/access.log", File.join(@tmpdir, "access.log"))
.gsub("/var/log/nginx/error.log", File.join(@tmpdir, "error.log"))
.gsub("/var/www/discourse/public", File.join(@tmpdir, "public"))
.gsub("conf.d/outlets/", File.join(@tmpdir, "outlets/"))
.lines
.map { |line| line.match?(/^\s*brotli/) ? "# #{line}" : line }
.join
end
def wrapper_config
<<~NGINX
worker_processes 1;
daemon off;
error_log #{File.join(@tmpdir, "error.log")} warn;
pid #{File.join(@tmpdir, "nginx.pid")};
events {
worker_connections 256;
}
http {
client_body_temp_path #{File.join(@tmpdir, "client_body_temp")};
proxy_temp_path #{File.join(@tmpdir, "proxy_temp")};
include #{sample_config_path};
}
NGINX
end
def wait_until_ready
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 5
loop do
TCPSocket.new("127.0.0.1", port).close
return
rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL
raise nginx_error if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
sleep 0.05
end
end
def nginx_error
logs =
%w[stderr.log stdout.log error.log].filter_map do |filename|
path = File.join(@tmpdir, filename)
"--- #{filename} ---\n#{File.read(path)}" if File.exist?(path)
end
"nginx did not start on port #{port}\n#{logs.join("\n")}"
end
def available_port
TCPServer.open("127.0.0.1", 0) { |server| server.addr[1] }
end
def sample_config_path
File.join(@tmpdir, "discourse.conf")
end
def wrapper_config_path
File.join(@tmpdir, "nginx.conf")
end
end