import { delay } from "../async/mod.ts";
const ERROR_SERVER_CLOSED = "Server closed";
const HTTP_PORT = 80;
const HTTPS_PORT = 443;
const INITIAL_ACCEPT_BACKOFF_DELAY = 5;
const MAX_ACCEPT_BACKOFF_DELAY = 1000;
export class Server {
    #port;
    #host;
    #handler;
    #closed = false;
    #listeners = new Set();
    #httpConnections = new Set();
    #onError;
    constructor(serverInit) {
        this.#port = serverInit.port;
        this.#host = serverInit.hostname;
        this.#handler = serverInit.handler;
        this.#onError = serverInit.onError ??
            function (error) {
                console.error(error);
                return new Response("Internal Server Error", { status: 500 });
            };
    }
    async serve(listener) {
        if (this.#closed) {
            throw new Deno.errors.Http(ERROR_SERVER_CLOSED);
        }
        this.#trackListener(listener);
        try {
            return await this.#accept(listener);
        }
        finally {
            this.#untrackListener(listener);
            try {
                listener.close();
            }
            catch {
            }
        }
    }
    async listenAndServe() {
        if (this.#closed) {
            throw new Deno.errors.Http(ERROR_SERVER_CLOSED);
        }
        const listener = Deno.listen({
            port: this.#port ?? HTTP_PORT,
            hostname: this.#host ?? "0.0.0.0",
            transport: "tcp",
        });
        return await this.serve(listener);
    }
    async listenAndServeTls(certFile, keyFile) {
        if (this.#closed) {
            throw new Deno.errors.Http(ERROR_SERVER_CLOSED);
        }
        const listener = Deno.listenTls({
            port: this.#port ?? HTTPS_PORT,
            hostname: this.#host ?? "0.0.0.0",
            certFile,
            keyFile,
            transport: "tcp",
        });
        return await this.serve(listener);
    }
    close() {
        if (this.#closed) {
            throw new Deno.errors.Http(ERROR_SERVER_CLOSED);
        }
        this.#closed = true;
        for (const listener of this.#listeners) {
            try {
                listener.close();
            }
            catch {
            }
        }
        this.#listeners.clear();
        for (const httpConn of this.#httpConnections) {
            this.#closeHttpConn(httpConn);
        }
        this.#httpConnections.clear();
    }
    get closed() {
        return this.#closed;
    }
    get addrs() {
        return Array.from(this.#listeners).map((listener) => listener.addr);
    }
    async #respond(requestEvent, httpConn, connInfo) {
        let response;
        try {
            response = await this.#handler(requestEvent.request, connInfo);
        }
        catch (error) {
            response = await this.#onError(error);
        }
        try {
            await requestEvent.respondWith(response);
        }
        catch {
            return this.#closeHttpConn(httpConn);
        }
    }
    async #serveHttp(httpConn, connInfo) {
        while (!this.#closed) {
            let requestEvent;
            try {
                requestEvent = await httpConn.nextRequest();
            }
            catch {
                break;
            }
            if (requestEvent === null) {
                break;
            }
            this.#respond(requestEvent, httpConn, connInfo);
        }
        this.#closeHttpConn(httpConn);
    }
    async #accept(listener) {
        let acceptBackoffDelay;
        while (!this.#closed) {
            let conn;
            try {
                conn = await listener.accept();
            }
            catch (error) {
                if (error instanceof Deno.errors.BadResource ||
                    error instanceof Deno.errors.InvalidData ||
                    error instanceof Deno.errors.UnexpectedEof ||
                    error instanceof Deno.errors.ConnectionReset ||
                    error instanceof Deno.errors.NotConnected) {
                    if (!acceptBackoffDelay) {
                        acceptBackoffDelay = INITIAL_ACCEPT_BACKOFF_DELAY;
                    }
                    else {
                        acceptBackoffDelay *= 2;
                    }
                    if (acceptBackoffDelay >= MAX_ACCEPT_BACKOFF_DELAY) {
                        acceptBackoffDelay = MAX_ACCEPT_BACKOFF_DELAY;
                    }
                    await delay(acceptBackoffDelay);
                    continue;
                }
                throw error;
            }
            acceptBackoffDelay = undefined;
            let httpConn;
            try {
                httpConn = Deno.serveHttp(conn);
            }
            catch {
                continue;
            }
            this.#trackHttpConnection(httpConn);
            const connInfo = {
                localAddr: conn.localAddr,
                remoteAddr: conn.remoteAddr,
            };
            this.#serveHttp(httpConn, connInfo);
        }
    }
    #closeHttpConn(httpConn) {
        this.#untrackHttpConnection(httpConn);
        try {
            httpConn.close();
        }
        catch {
        }
    }
    #trackListener(listener) {
        this.#listeners.add(listener);
    }
    #untrackListener(listener) {
        this.#listeners.delete(listener);
    }
    #trackHttpConnection(httpConn) {
        this.#httpConnections.add(httpConn);
    }
    #untrackHttpConnection(httpConn) {
        this.#httpConnections.delete(httpConn);
    }
}
export async function serveListener(listener, handler, options) {
    const server = new Server({ handler, onError: options?.onError });
    if (options?.signal) {
        options.signal.onabort = () => server.close();
    }
    return await server.serve(listener);
}
export async function serve(handler, options = {}) {
    const server = new Server({
        port: options.port ?? 8000,
        hostname: options.hostname ?? "0.0.0.0",
        handler,
        onError: options.onError,
    });
    if (options?.signal) {
        options.signal.onabort = () => server.close();
    }
    return await server.listenAndServe();
}
export async function serveTls(handler, options) {
    if (!options.keyFile) {
        throw new Error("TLS config is given, but 'keyFile' is missing.");
    }
    if (!options.certFile) {
        throw new Error("TLS config is given, but 'certFile' is missing.");
    }
    const server = new Server({
        port: options.port ?? 8443,
        hostname: options.hostname ?? "0.0.0.0",
        handler,
        onError: options.onError,
    });
    if (options?.signal) {
        options.signal.onabort = () => server.close();
    }
    return await server.listenAndServeTls(options.certFile, options.keyFile);
}
export async function listenAndServe(config, handler, options) {
    const server = new Server({ ...config, handler });
    if (options?.signal) {
        options.signal.onabort = () => server.close();
    }
    return await server.listenAndServe();
}
export async function listenAndServeTls(config, certFile, keyFile, handler, options) {
    const server = new Server({ ...config, handler });
    if (options?.signal) {
        options.signal.onabort = () => server.close();
    }
    return await server.listenAndServeTls(certFile, keyFile);
}
//# sourceMappingURL=data:application/json;base64,[encoded data, 37868 characters]