local https_server = {} https_server.__index = https_server local fmt = string.format local mock_srv_tpl_file = require "spec.fixtures.mock_webserver_tpl" local ngx = require "ngx" local pl_dir = require "pl.dir" local pl_file = require "pl.file" local pl_template = require "pl.template" local pl_path = require "pl.path" local uuid = require "resty.jit-uuid" local http_client = require "resty.http" local cjson = require "cjson" local shell = require "resty.shell" local Template = require("pl.stringx").Template -- we need this to get random UUIDs math.randomseed(os.time()) local HTTPS_SERVER_START_MAX_RETRY = 10 local tmp_root = os.getenv("TMPDIR") or "/tmp" local host_regex = [[([a-z0-9\-._~%!$&'()*+,;=]+@)?([a-z0-9\-._~%]+|\[[a-z0-9\-._~%!$&'()*+,;=:]+\])(:?[0-9]+)*]] local function create_temp_dir(copy_cert_and_key) local tmp_name = fmt("nginx_%s", uuid()) local tmp_path = fmt("%s/%s", tmp_root, tmp_name) local _, err = pl_path.mkdir(tmp_path) if err then return nil, err end local _, err = pl_path.mkdir(tmp_path .. "/logs") if err then return nil, err end if copy_cert_and_key then local status = pl_dir.copyfile("./spec/fixtures/kong_spec.crt", tmp_path) if not status then return nil, "could not copy cert" end status = pl_dir.copyfile("./spec/fixtures/kong_spec.key", tmp_path) if not status then return nil, "could not copy private key" end end return tmp_path end local function create_conf(params) local tpl, err = pl_template.compile(mock_srv_tpl_file) if err then return nil, err end local compiled_tpl = Template(tpl:render(params, { ipairs = ipairs })) local conf_filename = params.base_path .. "/nginx.conf" local conf, err = io.open (conf_filename, "w") if err then return nil, err end conf:write(compiled_tpl:substitute(params)) conf:close() return conf_filename end local function count_results(logs_dir) local results = { ["ok"] = 0, ["fail"] = 0, ["total"] = 0, ["status_ok"] = 0, ["status_fail"] = 0, ["status_total"] = 0 } local error_log_filename = logs_dir .. "/error.log" for line in io.lines(error_log_filename) do local m = ngx.re.match(line, [[^.*\[COUNT\] (.+) (\d\d\d)\,.*\, host: \"(.+)\"$]]) if m then local location = m[1] local status = m[2] local host = m[3] if host then local host_no_port = ngx.re.match(m[3], host_regex) if host_no_port then host = host_no_port[2] end else host = "nonamehost" end if results[host] == nil then results[host] = { ["ok"] = 0, ["fail"] = 0, ["status_ok"] = 0, ["status_fail"] = 0, } end if location == "slash" then if status == "200" then results.ok = results.ok + 1 results[host].ok = results[host].ok + 1 else results.fail = results.fail + 1 results[host].fail = results[host].fail + 1 end results.total = results.ok + results.fail elseif location == "status" then if status == "200" then results.status_ok = results.status_ok + 1 results[host].status_ok = results[host].status_ok + 1 else results.status_fail = results.status_fail + 1 results[host].status_fail = results[host].status_fail + 1 end results.status_total = results.status_ok + results.status_fail end end end return results end function https_server.clear_access_log(self) local client = assert(http_client.new()) local uri = string.format("%s://%s:%d/clear_log", self.protocol, self.host, self.http_port) local res = assert(client:request_uri(uri, { method = "GET" })) assert(res.body == "cleared\n") end function https_server.get_access_log(self) local client = assert(http_client.new()) local uri = string.format("%s://%s:%d/log?do_not_log", self.protocol, self.host, self.http_port) local res = assert(client:request_uri(uri, { method = "GET" })) return assert(cjson.decode(res.body)) end function https_server.start(self) if not pl_path.exists(tmp_root) or not pl_path.isdir(tmp_root) then error("could not get a temporary path", 2) end local err self.base_path, err = create_temp_dir(self.protocol == "https") if err then error(fmt("could not create temp dir: %s", err), 2) end local conf_params = { base_path = self.base_path, delay = self.delay, cert_path = "./", check_hostname = self.check_hostname, logs_dir = self.logs_dir, host = self.host, hosts = self.hosts, http_port = self.http_port, protocol = self.protocol, worker_num = self.worker_num, disable_ipv6 = self.disable_ipv6, } local file, err = create_conf(conf_params) if err then error(fmt("could not create conf: %s", err), 2) end for _ = 1, HTTPS_SERVER_START_MAX_RETRY do if shell.run("nginx -c " .. file .. " -p " .. self.base_path, nil, 0) then return end ngx.sleep(1) end error("failed starting nginx") end function https_server.shutdown(self) local pid_filename = self.base_path .. "/logs/nginx.pid" local pid_file = io.open (pid_filename, "r") if pid_file then local pid, err = pid_file:read() if err then error(fmt("could not read pid file: %s", tostring(err)), 2) end local kill_nginx_cmd = fmt("kill -s TERM %s", tostring(pid)) local status = shell.run(kill_nginx_cmd, nil, 0) if not status then error(fmt("could not kill nginx test server. %s was not removed", self.base_path), 2) end local pidfile_removed local watchdog = 0 repeat pidfile_removed = pl_file.access_time(pid_filename) == nil if not pidfile_removed then ngx.sleep(0.01) watchdog = watchdog + 1 if(watchdog > 100) then error("could not stop nginx", 2) end end until(pidfile_removed) end local count, err = count_results(self.base_path .. "/" .. self.logs_dir) if err then -- not a fatal error print(fmt("could not count results: %s", tostring(err))) end local _, err = pl_dir.rmtree(self.base_path) if err then print(fmt("could not remove %s: %s", self.base_path, tostring(err))) end return count end -- **DEPRECATED**: please use `spec.helpers.http_mock` instead. function https_server.new(port, hostname, protocol, check_hostname, workers, delay, disable_ipv6) local self = setmetatable({}, https_server) local host local hosts if type(hostname) == "table" then hosts = hostname host = "" for _, h in ipairs(hostname) do host = fmt("%s %s", host, h) end else hosts = {hostname} host = hostname end self.check_hostname = check_hostname or false self.delay = tonumber(delay) or 0 self.host = host or "localhost" self.hosts = hosts self.http_port = port self.logs_dir = "logs" self.protocol = protocol or "http" self.worker_num = workers or 2 self.disable_ipv6 = disable_ipv6 return self end return https_server