// Copyright 2018-2026 the Deno authors. MIT license.

// deno-lint-ignore-file no-console

import { assertIsError, assertMatch, assertRejects } from "@std/assert";
import { Buffer, type Reader } from "@std/io";
import { delay } from "@std/async/delay";
import * as http2 from "node:http2";
import { TextProtoReader } from "../testdata/run/textproto.ts";
import {
  assert,
  assertEquals,
  assertStringIncludes,
  assertThrows,
  BufReader,
  BufWriter,
  curlRequest,
  curlRequestWithStdErr,
  execCode,
  execCode3,
  fail,
  tmpUnixSocketPath,
} from "./test_util.ts";

// Since these tests may run in parallel, ensure this port is unique to this file
const servePort = 4511;

const {
  upgradeHttpRaw,
  addTrailers,
  serveHttpOnListener,
  serveHttpOnConnection,
  getCachedAbortSignal,
  resetLegacyAbortWarning,
  // @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
} = Deno[Deno.internal];

function createOnErrorCb(ac: AbortController): (err: unknown) => Response {
  return (err) => {
    console.error(err);
    ac.abort();
    return new Response("Internal server error", { status: 500 });
  };
}

function onListen(
  resolve: (value: void | PromiseLike<void>) => void,
): ({ hostname, port }: { hostname: string; port: number }) => void {
  return () => {
    resolve();
  };
}

async function makeServer(
  handler: (
    req: Request,
    info: Deno.ServeHandlerInfo,
  ) => Response | Promise<Response>,
): Promise<
  {
    finished: Promise<void>;
    abort: () => void;
    shutdown: () => Promise<void>;
    [Symbol.asyncDispose](): PromiseLike<void>;
  }
> {
  const ac = new AbortController();
  const { promise, resolve } = Promise.withResolvers<void>();

  const server = Deno.serve({
    handler,
    port: servePort,
    signal: ac.signal,
    onListen: onListen(resolve),
  });

  await promise;
  return {
    finished: server.finished,
    abort() {
      ac.abort();
    },
    async shutdown() {
      await server.shutdown();
    },
    [Symbol.asyncDispose]() {
      return server[Symbol.asyncDispose]();
    },
  };
}

Deno.test(async function httpServerShutsDownPortBeforeResolving() {
  const { finished, abort } = await makeServer((_req) => new Response("ok"));
  assertThrows(() => Deno.listen({ port: servePort }));
  abort();
  await finished;

  const listener = Deno.listen({ port: servePort });
  listener!.close();
});

// Regression test for the Request properties added in
// https://github.com/denoland/deno/issues/27763. Server-created requests are
// built via `fromInnerRequest` (the public `Request` constructor is bypassed),
// so the new accessors must still report the spec defaults for a Deno.serve()
// request without adding anything to the inner request's shape.
Deno.test(
  { permissions: { net: true } },
  async function httpServerRequestStandardProperties() {
    const { promise, resolve } = Promise.withResolvers<
      Record<string, unknown>
    >();
    await using server = await makeServer((req) => {
      resolve({
        cache: req.cache,
        credentials: req.credentials,
        integrity: req.integrity,
        keepalive: req.keepalive,
        mode: req.mode,
        referrer: req.referrer,
        referrerPolicy: req.referrerPolicy,
      });
      return new Response("ok");
    });
    const resp = await fetch(`http://localhost:${servePort}/`);
    await resp.text();
    assertEquals(await promise, {
      cache: "default",
      credentials: "same-origin",
      integrity: "",
      keepalive: false,
      mode: "cors",
      referrer: "about:client",
      referrerPolicy: "",
    });
    await server.shutdown();
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerRequestDoesNotExposeExternal() {
    let innerRequest: object | undefined;
    await using server = await makeServer((req) => {
      const requestSymbol = Object.getOwnPropertySymbols(req).find(
        (symbol) => symbol.description === "request",
      );
      if (requestSymbol !== undefined) {
        innerRequest = (req as unknown as Record<symbol, object>)[
          requestSymbol
        ];
      }
      return new Response("ok");
    });
    const resp = await fetch(`http://localhost:${servePort}/`);
    await resp.text();
    assert(innerRequest !== undefined);
    assertEquals(Reflect.has(innerRequest, "external"), false);
    await server.shutdown();
  },
);

// When shutting down abruptly, we require that all in-progress connections are aborted,
// no new connections are allowed, and no new transactions are allowed on existing connections.
Deno.test(
  { permissions: { net: true } },
  async function httpServerShutdownAbruptGuaranteeHttp11() {
    const deferredQueue: {
      input: ReturnType<typeof Promise.withResolvers<string>>;
      out: ReturnType<typeof Promise.withResolvers<void>>;
    }[] = [];
    const { finished, abort } = await makeServer((_req) => {
      const { input, out } = deferredQueue.shift()!;
      return new Response(
        new ReadableStream({
          async start(controller) {
            controller.enqueue(new Uint8Array([46]));
            out.resolve();
            controller.enqueue(encoder.encode(await input.promise));
            controller.close();
          },
        }),
      );
    });
    const encoder = new TextEncoder();
    const decoder = new TextDecoder();
    const conn = await Deno.connect({ port: servePort });
    const w = conn.writable.getWriter();
    const r = conn.readable.getReader();

    const deferred1 = {
      input: Promise.withResolvers<string>(),
      out: Promise.withResolvers<void>(),
    };
    deferredQueue.push(deferred1);
    const deferred2 = {
      input: Promise.withResolvers<string>(),
      out: Promise.withResolvers<void>(),
    };
    deferredQueue.push(deferred2);
    const deferred3 = {
      input: Promise.withResolvers<string>(),
      out: Promise.withResolvers<void>(),
    };
    deferredQueue.push(deferred3);
    deferred1.input.resolve("#");
    deferred2.input.resolve("$");
    await w.write(encoder.encode(`GET / HTTP/1.1\nConnection: keep-alive\n\n`));
    await w.write(encoder.encode(`GET / HTTP/1.1\nConnection: keep-alive\n\n`));

    // Fully read two responses
    let text = "";
    while (!text.includes("$\r\n")) {
      text += decoder.decode((await r.read()).value);
    }

    await w.write(encoder.encode(`GET / HTTP/1.1\nConnection: keep-alive\n\n`));
    await deferred3.out.promise;

    // This is half served, so wait for the chunk that has the first '.'
    text = "";
    while (!text.includes("1\r\n.\r\n")) {
      text += decoder.decode((await r.read()).value);
    }

    abort();

    // This doesn't actually write anything, but we release it after aborting
    deferred3.input.resolve("!");

    // Guarantee: can't connect to an aborted server (though this may not happen immediately)
    let failed = false;
    for (let i = 0; i < 10; i++) {
      try {
        const conn = await Deno.connect({ port: servePort });
        conn.close();
        // Give the runtime a few ticks to settle (required for Windows)
        await new Promise((r) => setTimeout(r, 2 ** i));
        continue;
      } catch (_) {
        failed = true;
        break;
      }
    }
    assert(failed, "The Deno.serve listener was not disabled promptly");

    // Guarantee: the pipeline is closed abruptly
    assert((await r.read()).done);

    try {
      conn.close();
    } catch (_) {
      // Ignore
    }
    await finished;
  },
);

// When shutting down abruptly, we require that all in-progress connections are aborted,
// no new connections are allowed, and no new transactions are allowed on existing connections.
Deno.test(
  { permissions: { net: true } },
  async function httpServerShutdownGracefulGuaranteeHttp11() {
    const deferredQueue: {
      input: ReturnType<typeof Promise.withResolvers<string>>;
      out: ReturnType<typeof Promise.withResolvers<void>>;
    }[] = [];
    const { finished, shutdown } = await makeServer((_req) => {
      const { input, out } = deferredQueue.shift()!;
      return new Response(
        new ReadableStream({
          async start(controller) {
            controller.enqueue(new Uint8Array([46]));
            out.resolve();
            controller.enqueue(encoder.encode(await input.promise));
            controller.close();
          },
        }),
      );
    });
    const encoder = new TextEncoder();
    const decoder = new TextDecoder();
    const conn = await Deno.connect({ port: servePort });
    const w = conn.writable.getWriter();
    const r = conn.readable.getReader();

    const deferred1 = {
      input: Promise.withResolvers<string>(),
      out: Promise.withResolvers<void>(),
    };
    deferredQueue.push(deferred1);
    const deferred2 = {
      input: Promise.withResolvers<string>(),
      out: Promise.withResolvers<void>(),
    };
    deferredQueue.push(deferred2);
    const deferred3 = {
      input: Promise.withResolvers<string>(),
      out: Promise.withResolvers<void>(),
    };
    deferredQueue.push(deferred3);
    deferred1.input.resolve("#");
    deferred2.input.resolve("$");
    await w.write(encoder.encode(`GET / HTTP/1.1\nConnection: keep-alive\n\n`));
    await w.write(encoder.encode(`GET / HTTP/1.1\nConnection: keep-alive\n\n`));

    // Fully read two responses
    let text = "";
    while (!text.includes("$\r\n")) {
      text += decoder.decode((await r.read()).value);
    }

    await w.write(encoder.encode(`GET / HTTP/1.1\nConnection: keep-alive\n\n`));
    await deferred3.out.promise;

    // This is half served, so wait for the chunk that has the first '.'
    text = "";
    while (!text.includes("1\r\n.\r\n")) {
      text += decoder.decode((await r.read()).value);
    }

    const shutdownPromise = shutdown();

    // Release the final response _after_ we shut down
    deferred3.input.resolve("!");

    // Guarantee: can't connect to an aborted server (though this may not happen immediately)
    let failed = false;
    for (let i = 0; i < 10; i++) {
      try {
        const conn = await Deno.connect({ port: servePort });
        conn.close();
        // Give the runtime a few ticks to settle (required for Windows)
        await new Promise((r) => setTimeout(r, 2 ** i));
        continue;
      } catch (_) {
        failed = true;
        break;
      }
    }
    assert(failed, "The Deno.serve listener was not disabled promptly");

    // Guarantee: existing connections fully drain
    while (!text.includes("!\r\n")) {
      text += decoder.decode((await r.read()).value);
    }

    await shutdownPromise;

    try {
      conn.close();
    } catch (_) {
      // Ignore
    }
    await finished;
  },
);

// Ensure that resources don't leak during a graceful shutdown
Deno.test(
  { permissions: { net: true, write: true, read: true } },
  async function httpServerShutdownGracefulResources() {
    const { promise, resolve } = Promise.withResolvers<void>();
    const { finished, shutdown } = await makeServer(async (_req) => {
      resolve();
      await new Promise((r) => setTimeout(r, 10));
      return new Response((await makeTempFile(1024 * 1024)).readable);
    });

    const f = fetch(`http://localhost:${servePort}`);
    await promise;
    assertEquals((await (await f).text()).length, 1048576);
    await shutdown();
    await finished;
  },
);

// Ensure that resources don't leak during a graceful shutdown
Deno.test(
  { permissions: { net: true, write: true, read: true } },
  async function httpServerShutdownGracefulResources2() {
    const waitForAbort = Promise.withResolvers<void>();
    const waitForRequest = Promise.withResolvers<void>();
    const { finished, shutdown } = await makeServer(async (_req) => {
      waitForRequest.resolve();
      await waitForAbort.promise;
      await new Promise((r) => setTimeout(r, 10));
      return new Response((await makeTempFile(1024 * 1024)).readable);
    });

    const f = fetch(`http://localhost:${servePort}`);
    await waitForRequest.promise;
    const s = shutdown();
    waitForAbort.resolve();
    assertEquals((await (await f).text()).length, 1048576);
    await s;
    await finished;
  },
);

Deno.test(
  { permissions: { net: true, write: true, read: true } },
  async function httpServerExplicitResourceManagement() {
    let dataPromise;

    {
      await using _server = await makeServer(async (_req) => {
        return new Response((await makeTempFile(1024 * 1024)).readable);
      });

      const resp = await fetch(`http://localhost:${servePort}`);
      dataPromise = resp.bytes();
    }

    assertEquals((await dataPromise).byteLength, 1048576);
  },
);

Deno.test(
  { permissions: { net: true, write: true, read: true } },
  async function httpServerExplicitResourceManagementManualClose() {
    await using server = await makeServer(async (_req) => {
      return new Response((await makeTempFile(1024 * 1024)).readable);
    });

    const resp = await fetch(`http://localhost:${servePort}`);

    const [_, data] = await Promise.all([
      server.shutdown(),
      resp.bytes(),
    ]);

    assertEquals(data.byteLength, 1048576);
  },
);

Deno.test(
  { permissions: { read: true, run: true } },
  async function httpServerUnref() {
    const [statusCode, _output] = await execCode(`
      async function main() {
        const server = Deno.serve({ port: ${servePort}, handler: () => null });
        server.unref();
        await server.finished; // This doesn't block the program from exiting
      }
      main();
    `);
    assertEquals(statusCode, 0);
  },
);

Deno.test(async function httpServerCanResolveHostnames() {
  const ac = new AbortController();
  const { promise, resolve } = Promise.withResolvers<void>();

  await using server = Deno.serve({
    handler: (_req) => new Response("ok"),
    hostname: "localhost",
    port: servePort,
    signal: ac.signal,
    onListen: onListen(resolve),
    onError: createOnErrorCb(ac),
  });

  await promise;
  const resp = await fetch(`http://localhost:${servePort}/`, {
    headers: { "connection": "close" },
  });
  const text = await resp.text();
  assertEquals(text, "ok");
  ac.abort();
  await server.finished;
});

Deno.test(async function httpServerRejectsOnAddrInUse() {
  const ac = new AbortController();
  const { promise, resolve } = Promise.withResolvers<void>();

  await using server = Deno.serve({
    handler: (_req) => new Response("ok"),
    hostname: "localhost",
    port: servePort,
    signal: ac.signal,
    onListen: onListen(resolve),
    onError: createOnErrorCb(ac),
  });
  await promise;

  assertThrows(
    () =>
      Deno.serve({
        handler: (_req) => new Response("ok"),
        hostname: "localhost",
        port: servePort,
        signal: ac.signal,
        onListen: onListen(resolve),
        onError: createOnErrorCb(ac),
      }),
    Deno.errors.AddrInUse,
  );
  ac.abort();
  await server.finished;
});

Deno.test({ permissions: { net: true } }, async function httpServerBasic() {
  const ac = new AbortController();
  const deferred = Promise.withResolvers<void>();
  const listeningDeferred = Promise.withResolvers<Deno.NetAddr>();

  await using server = Deno.serve({
    handler: async (request, { remoteAddr }) => {
      // FIXME(bartlomieju):
      // make sure that request can be inspected
      console.log(request);
      assertEquals(new URL(request.url).href, `http://127.0.0.1:${servePort}/`);
      assertEquals(await request.text(), "");
      assertEquals(remoteAddr.hostname, "127.0.0.1");
      deferred.resolve();
      return new Response("Hello World", { headers: { "foo": "bar" } });
    },
    port: servePort,
    signal: ac.signal,
    onListen: listeningDeferred.resolve,
    onError: createOnErrorCb(ac),
  });

  const addr = await listeningDeferred.promise;
  assertEquals(addr.hostname, server.addr.hostname);
  assertEquals(addr.port, server.addr.port);
  assertEquals(addr.transport, server.addr.transport);
  assertEquals(addr.transport, "tcp");
  const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
    headers: { "connection": "close" },
  });
  await deferred.promise;
  const clone = resp.clone();
  const text = await resp.text();
  assertEquals(text, "Hello World");
  assertEquals(resp.headers.get("foo"), "bar");
  const cloneText = await clone.text();
  assertEquals(cloneText, "Hello World");
  ac.abort();
  await server.finished;
});

// Test serving of HTTP on an arbitrary listener.
Deno.test(
  { permissions: { net: true } },
  async function httpServerOnListener() {
    const ac = new AbortController();
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers();
    const listener = Deno.listen({ port: servePort });
    await using server = serveHttpOnListener(
      listener,
      ac.signal,
      async (
        request: Request,
        { remoteAddr }: { remoteAddr: { hostname: string } },
      ) => {
        assertEquals(
          new URL(request.url).href,
          `http://127.0.0.1:${servePort}/`,
        );
        assertEquals(await request.text(), "");
        assertEquals(remoteAddr.hostname, "127.0.0.1");
        deferred.resolve();
        return new Response("Hello World", { headers: { "foo": "bar" } });
      },
      createOnErrorCb(ac),
      onListen(listeningDeferred.resolve),
    );

    await listeningDeferred.promise;
    const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
      headers: { "connection": "close" },
    });
    await listeningDeferred.promise;
    const clone = resp.clone();
    const text = await resp.text();
    assertEquals(text, "Hello World");
    assertEquals(resp.headers.get("foo"), "bar");
    const cloneText = await clone.text();
    assertEquals(cloneText, "Hello World");
    ac.abort();
    await server.finished;
  },
);

// Test serving of HTTP on an arbitrary connection.
Deno.test(
  { permissions: { net: true } },
  async function httpServerOnConnection() {
    const ac = new AbortController();
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const listener = Deno.listen({ port: servePort });
    const acceptPromise = listener.accept();
    const fetchPromise = fetch(`http://127.0.0.1:${servePort}/`, {
      headers: { "connection": "close" },
    });

    await using server = serveHttpOnConnection(
      await acceptPromise,
      ac.signal,
      async (
        request: Request,
        { remoteAddr }: { remoteAddr: { hostname: string } },
      ) => {
        assertEquals(
          new URL(request.url).href,
          `http://127.0.0.1:${servePort}/`,
        );
        assertEquals(await request.text(), "");
        assertEquals(remoteAddr.hostname, "127.0.0.1");
        deferred.resolve();
        return new Response("Hello World", { headers: { "foo": "bar" } });
      },
      createOnErrorCb(ac),
      onListen(listeningDeferred.resolve),
    );

    const resp = await fetchPromise;
    await deferred.promise;
    const clone = resp.clone();
    const text = await resp.text();
    assertEquals(text, "Hello World");
    assertEquals(resp.headers.get("foo"), "bar");
    const cloneText = await clone.text();
    assertEquals(cloneText, "Hello World");
    // Note that we don't need to abort this server -- it closes when the connection does
    // ac.abort();
    await server.finished;
    listener.close();
  },
);

Deno.test({ permissions: { net: true } }, async function httpServerOnError() {
  const ac = new AbortController();
  const { promise, resolve } = Promise.withResolvers<void>();
  let requestStash: Request | null;

  await using server = Deno.serve({
    handler: async (request: Request) => {
      requestStash = request;
      await new Promise((r) => setTimeout(r, 100));
      throw "fail";
    },
    port: servePort,
    signal: ac.signal,
    onListen: onListen(resolve),
    onError: () => {
      return new Response("failed: " + requestStash!.url, { status: 500 });
    },
  });

  await promise;
  const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
    headers: { "connection": "close" },
  });
  const text = await resp.text();
  ac.abort();
  await server.finished;

  assertEquals(text, `failed: http://127.0.0.1:${servePort}/`);
});

Deno.test(
  { permissions: { net: true } },
  async function httpServerOnErrorFails() {
    const ac = new AbortController();
    const { promise, resolve } = Promise.withResolvers<void>();
    // NOTE(bartlomieju): deno lint doesn't know that it's actually used later,
    // but TypeScript can't see that either ¯\_(ツ)_/¯
    // deno-lint-ignore no-unused-vars
    let requestStash: Request | null;

    await using server = Deno.serve({
      handler: async (request: Request) => {
        requestStash = request;
        await new Promise((r) => setTimeout(r, 100));
        throw "fail";
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(resolve),
      onError: () => {
        throw "again";
      },
    });

    await promise;
    const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
      headers: { "connection": "close" },
    });
    const text = await resp.text();
    ac.abort();
    await server.finished;

    assertEquals(text, "Internal Server Error");
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerMultipleResponseBodyConsume() {
    const ac = new AbortController();
    const { promise, resolve } = Promise.withResolvers<void>();
    const response = new Response("Hello World");
    let hadError = false;
    await using server = Deno.serve({
      handler: () => {
        return response;
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(resolve),
      onError: () => {
        hadError = true;
        return new Response("Internal Server Error", { status: 500 });
      },
    });

    await promise;
    assert(!response.bodyUsed);

    const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
      headers: { "connection": "close" },
    });
    assertEquals(resp.status, 200);
    const text = await resp.text();
    assertEquals(text, "Hello World");
    assert(response.bodyUsed);

    const resp2 = await fetch(`http://127.0.0.1:${servePort}/`, {
      headers: { "connection": "close" },
    });
    assertEquals(resp2.status, 500);
    const text2 = await resp2.text();
    assertEquals(text2, "Internal Server Error");
    assert(hadError);
    assert(response.bodyUsed);

    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerResponseHeadersMutation() {
    const ac = new AbortController();
    const { promise, resolve } = Promise.withResolvers<void>();
    const response = new Response("Hello World");
    response.headers.set("x-native-test", "mutated");

    await using server = Deno.serve({
      handler: () => response,
      port: servePort,
      signal: ac.signal,
      onListen: onListen(resolve),
      onError: createOnErrorCb(ac),
    });

    await promise;
    const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
      headers: { "connection": "close" },
    });
    assertEquals(resp.status, 200);
    assertEquals(resp.headers.get("x-native-test"), "mutated");
    assertEquals(await resp.text(), "Hello World");

    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerEmptyResponseWithHeaders() {
    const ac = new AbortController();
    const { promise, resolve } = Promise.withResolvers<void>();

    await using server = Deno.serve({
      handler: () =>
        new Response(null, {
          status: 201,
          headers: { "x-empty-native-test": "yes" },
        }),
      port: servePort,
      signal: ac.signal,
      onListen: onListen(resolve),
      onError: createOnErrorCb(ac),
    });

    await promise;
    const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
      headers: { "connection": "close" },
    });
    assertEquals(resp.status, 201);
    assertEquals(resp.headers.get("x-empty-native-test"), "yes");
    assertEquals(await resp.text(), "");

    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerReturnErrorResponse() {
    const ac = new AbortController();
    const { promise, resolve } = Promise.withResolvers<void>();
    let hadError = false;
    await using server = Deno.serve({
      handler: () => {
        return Response.error();
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(resolve),
      onError: () => {
        hadError = true;
        return new Response("Internal Server Error", { status: 500 });
      },
    });

    await promise;

    const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
      headers: { "connection": "close" },
    });
    assertEquals(resp.status, 500);
    const text = await resp.text();
    assertEquals(text, "Internal Server Error");
    assert(hadError);

    ac.abort();
    await server.finished;
  },
);

Deno.test({ permissions: { net: true } }, async function httpServerOverload1() {
  const ac = new AbortController();
  const deferred = Promise.withResolvers<void>();
  const listeningDeferred = Promise.withResolvers<void>();

  await using server = Deno.serve({
    port: servePort,
    signal: ac.signal,
    onListen: onListen(listeningDeferred.resolve),
    onError: createOnErrorCb(ac),
  }, async (request) => {
    // FIXME(bartlomieju):
    // make sure that request can be inspected
    console.log(request);
    assertEquals(new URL(request.url).href, `http://127.0.0.1:${servePort}/`);
    assertEquals(await request.text(), "");
    deferred.resolve();
    return new Response("Hello World", { headers: { "foo": "bar" } });
  });

  await listeningDeferred.promise;
  const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
    headers: { "connection": "close" },
  });
  await deferred.promise;
  const clone = resp.clone();
  const text = await resp.text();
  assertEquals(text, "Hello World");
  assertEquals(resp.headers.get("foo"), "bar");
  const cloneText = await clone.text();
  assertEquals(cloneText, "Hello World");
  ac.abort();
  await server.finished;
});

Deno.test({ permissions: { net: true } }, async function httpServerOverload2() {
  const ac = new AbortController();
  const deferred = Promise.withResolvers<void>();
  const listeningDeferred = Promise.withResolvers<void>();

  await using server = Deno.serve({
    port: servePort,
    signal: ac.signal,
    onListen: onListen(listeningDeferred.resolve),
    onError: createOnErrorCb(ac),
  }, async (request) => {
    // FIXME(bartlomieju):
    // make sure that request can be inspected
    console.log(request);
    assertEquals(new URL(request.url).href, `http://127.0.0.1:${servePort}/`);
    assertEquals(await request.text(), "");
    deferred.resolve();
    return new Response("Hello World", { headers: { "foo": "bar" } });
  });

  await listeningDeferred.promise;
  const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
    headers: { "connection": "close" },
  });
  await deferred.promise;
  const clone = resp.clone();
  const text = await resp.text();
  assertEquals(text, "Hello World");
  assertEquals(resp.headers.get("foo"), "bar");
  const cloneText = await clone.text();
  assertEquals(cloneText, "Hello World");
  ac.abort();
  await server.finished;
});

Deno.test(
  { permissions: { net: true } },
  function httpServerErrorOverloadMissingHandler() {
    // @ts-ignore - testing invalid overload
    assertThrows(() => Deno.serve(), TypeError, "handler");
    // @ts-ignore - testing invalid overload
    assertThrows(() => Deno.serve({}), TypeError, "handler");
    assertThrows(
      // @ts-ignore - testing invalid overload
      () => Deno.serve({ handler: undefined }),
      TypeError,
      "handler",
    );
    assertThrows(
      // @ts-ignore - testing invalid overload
      () => Deno.serve(undefined, { handler: () => {} }),
      TypeError,
      "handler",
    );
  },
);

Deno.test({ permissions: { net: true } }, async function httpServerPort0() {
  const ac = new AbortController();

  await using server = Deno.serve({
    handler() {
      return new Response("Hello World");
    },
    port: 0,
    signal: ac.signal,
    onListen({ port }) {
      assert(port > 0 && port < 65536);
      ac.abort();
    },
  });
  await server.finished;
});

// https://github.com/denoland/deno/issues/15107
Deno.test(
  { permissions: { net: true } },
  async function httpLazyHeadersIssue15107() {
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    let headers: Headers;
    await using server = Deno.serve({
      handler: async (request) => {
        await request.text();
        headers = request.headers;
        deferred.resolve();
        return new Response("");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    // Send GET request with a body + content-length.
    const encoder = new TextEncoder();
    const body =
      `GET / HTTP/1.1\r\nHost: 127.0.0.1:2333\r\nContent-Length: 5\r\n\r\n12345`;
    const writeResult = await conn.write(encoder.encode(body));
    assertEquals(body.length, writeResult);
    await deferred.promise;
    conn.close();
    assertEquals(headers!.get("content-length"), "5");
    ac.abort();
    await server.finished;
  },
);

Deno.test({ permissions: { net: true } }, async function validPortString() {
  await using server = Deno.serve({
    handler: (_request) => new Response(),
    port: "4501" as unknown as number,
  });
  assertEquals(server.addr.transport, "tcp");
  assertEquals(server.addr.port, 4501);
  await server.shutdown();
});

Deno.test({ permissions: { net: true } }, function invalidPortFloat() {
  assertThrows(
    () =>
      Deno.serve({
        handler: (_request) => new Response(),
        port: 45.1,
      }),
    TypeError,
    `Invalid port: 45.1`,
  );
});

Deno.test({ permissions: { net: true } }, function invalidPortNaN() {
  assertThrows(
    () =>
      Deno.serve({
        handler: (_request) => new Response(),
        port: NaN,
      }),
    TypeError,
    `Invalid port: NaN`,
  );
});

Deno.test({ permissions: { net: true } }, function invalidPortString() {
  assertThrows(
    () =>
      Deno.serve({
        handler: (_request) => new Response(),
        port: "some-non-number-string" as unknown as number,
      }),
    TypeError,
    `Invalid port: 'some-non-number-string'`,
  );
});

Deno.test({ permissions: { net: true } }, function invalidPortTooSmall() {
  assertThrows(
    () =>
      Deno.serve({
        handler: (_request) => new Response(),
        port: -111,
      }),
    RangeError,
    `Invalid port (out of range): -111`,
  );
});

Deno.test({ permissions: { net: true } }, function invalidPortTooLarge() {
  assertThrows(
    () =>
      Deno.serve({
        handler: (_request) => new Response(),
        port: 100000,
      }),
    RangeError,
    `Invalid port (out of range): 100000`,
  );
});

Deno.test({ permissions: { net: true } }, function invalidPortType() {
  assertThrows(
    () =>
      Deno.serve({
        handler: (_request) => new Response(),
        port: true as unknown as number,
      }),
    TypeError,
    `Invalid port (expected number): true`,
  );
});

function createUrlTest(
  name: string,
  methodAndPath: string,
  host: string | null,
  expected: string | number,
) {
  Deno.test(`httpServerUrl${name}`, async () => {
    const listeningDeferred = Promise.withResolvers<number>();
    const urlDeferred = Promise.withResolvers<string>();
    const ac = new AbortController();
    await using server = Deno.serve({
      handler: (request: Request) => {
        urlDeferred.resolve(request.url);
        return new Response("");
      },
      port: 0,
      signal: ac.signal,
      onListen: ({ port }: { port: number }) => {
        listeningDeferred.resolve(port);
      },
      onError: createOnErrorCb(ac),
    });

    const port = await listeningDeferred.promise;
    const conn = await Deno.connect({ port });

    const encoder = new TextEncoder();
    // `null` omits the Host header entirely; `""` sends `Host:` with an
    // empty value, which is a separate code path.
    const body = `${methodAndPath} HTTP/1.1\r\n${
      host !== null ? ("Host: " + host + "\r\n") : ""
    }Content-Length: 5\r\n\r\n12345`;
    const writeResult = await conn.write(encoder.encode(body));
    assertEquals(body.length, writeResult);

    try {
      if (typeof expected === "number") {
        const buf = new Uint8Array(128);
        const n = await conn.read(buf);
        assert(n != null);
        const response = new TextDecoder().decode(buf.subarray(0, n));
        assert(response.startsWith(`HTTP/1.1 ${expected}`));
      } else {
        const expectedResult = expected.replace("HOST", "localhost").replace(
          "PORT",
          `${port}`,
        );
        assertEquals(await urlDeferred.promise, expectedResult);
      }
    } finally {
      ac.abort();
      await server.finished;
      conn.close();
    }
  });
}

createUrlTest("WithPath", "GET /path", null, "http://HOST:PORT/path");
createUrlTest(
  "WithPathAndHost",
  "GET /path",
  "deno.land",
  "http://deno.land/path",
);
createUrlTest(
  "WithAbsolutePath",
  "GET http://localhost/path",
  null,
  "http://localhost/path",
);
createUrlTest(
  "WithAbsolutePathAndHost",
  "GET http://localhost/path",
  "deno.land",
  "http://localhost/path",
);
createUrlTest(
  "WithPortAbsolutePath",
  "GET http://localhost:1234/path",
  null,
  "http://localhost:1234/path",
);
createUrlTest(
  "WithPortAbsolutePathAndHost",
  "GET http://localhost:1234/path",
  "deno.land",
  "http://localhost:1234/path",
);
createUrlTest(
  "WithPortAbsolutePathAndHostWithPort",
  "GET http://localhost:1234/path",
  "deno.land:9999",
  "http://localhost:1234/path",
);

createUrlTest("WithAsterisk", "OPTIONS *", null, "http://localhost:PORT/*");
createUrlTest(
  "WithAuthorityForm",
  "CONNECT deno.land:80",
  null,
  "http://deno.land:80",
);

createUrlTest("WithInvalidAsterisk", "GET *", null, 400);
createUrlTest("WithInvalidNakedPath", "GET path", null, 400);
createUrlTest(
  "WithInvalidNakedAuthority",
  "GET deno.land:1234",
  null,
  400,
);
createUrlTest(
  "WithInvalidConnectAuthority",
  "CONNECT /path",
  null,
  400,
);

// Regression test for https://github.com/denoland/deno/issues/29872: an empty
// `Host:` header must fall back to the listener's authority instead of
// producing `request.url = "http:///path"` (which parses as hostname "path").
createUrlTest(
  "WithEmptyHostHeader",
  "GET /path",
  "",
  "http://HOST:PORT/path",
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerHttp2RequestUrl() {
    const url = Promise.withResolvers<string>();
    const listening = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve({
      handler: (request: Request) => {
        url.resolve(request.url);
        return new Response("");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listening.resolve),
      onError: createOnErrorCb(ac),
    });
    await listening.promise;

    const conn = await Deno.connect({ port: servePort });
    await writeHttp2ClientPreface(conn);
    await writeHttp2Headers(conn, [
      [":method", "GET"],
      [":path", "/path?query=1"],
      [":scheme", "http"],
      [":authority", "deno.land:1234"],
    ], true);

    assertEquals(await url.promise, "http://deno.land:1234/path?query=1");

    conn.close();
    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerGetRequestBody() {
    const deferred = Promise.withResolvers<void>();
    const ac = new AbortController();
    const listeningDeferred = Promise.withResolvers<void>();

    await using server = Deno.serve({
      handler: (request) => {
        assertEquals(request.body, null);
        deferred.resolve();
        return new Response("", { headers: {} });
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    // Send GET request with a body + content-length.
    const encoder = new TextEncoder();
    const body =
      `GET / HTTP/1.1\r\nHost: 127.0.0.1:${servePort}\r\nContent-Length: 5\r\n\r\n12345`;
    const writeResult = await conn.write(encoder.encode(body));
    assertEquals(body.length, writeResult);

    const resp = new Uint8Array(200);
    const readResult = await conn.read(resp);
    assert(readResult);
    assert(readResult > 0);

    conn.close();
    await deferred.promise;
    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerHttp2RequestTextBody() {
    const requestBody = Promise.withResolvers<string>();
    const listening = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve({
      handler: async (request: Request) => {
        requestBody.resolve(await request.text());
        return new Response("");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listening.resolve),
      onError: createOnErrorCb(ac),
    });
    await listening.promise;

    const conn = await Deno.connect({ port: servePort });
    const body = new TextEncoder().encode("hello from h2");
    await writeHttp2ClientPreface(conn);
    await writeHttp2Headers(conn, [
      [":method", "POST"],
      [":path", "/"],
      [":scheme", "http"],
      [":authority", `localhost:${servePort}`],
      ["content-length", `${body.length}`],
    ], false);
    await writeHttp2Data(conn, body, true);

    assertEquals(await requestBody.promise, "hello from h2");

    conn.close();
    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerAbortedRequestBody() {
    const deferred = Promise.withResolvers<void>();
    const ac = new AbortController();
    const listeningDeferred = Promise.withResolvers<void>();

    await using server = Deno.serve({
      handler: async (request) => {
        await assertRejects(async () => {
          await request.text();
        });
        deferred.resolve();
        // Not actually used
        return new Response();
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    // Send POST request with a body + content-length, but don't send it all
    const encoder = new TextEncoder();
    const body =
      `POST / HTTP/1.1\r\nHost: 127.0.0.1:${servePort}\r\nContent-Length: 10\r\n\r\n12345`;
    const writeResult = await conn.write(encoder.encode(body));
    assertEquals(body.length, writeResult);
    conn.close();
    await deferred.promise;
    ac.abort();
    await server.finished;
  },
);

function createStreamTest(count: number, delay: number, action: string) {
  function doAction(controller: ReadableStreamDefaultController, i: number) {
    if (i == count) {
      if (action == "Throw") {
        controller.error(new Error("Expected error!"));
      } else {
        controller.close();
      }
    } else {
      controller.enqueue(`a${i}`);

      if (delay == 0) {
        doAction(controller, i + 1);
      } else {
        setTimeout(() => doAction(controller, i + 1), delay);
      }
    }
  }

  function makeStream(_count: number, delay: number): ReadableStream {
    return new ReadableStream({
      start(controller) {
        if (delay == 0) {
          doAction(controller, 0);
        } else {
          setTimeout(() => doAction(controller, 0), delay);
        }
      },
    }).pipeThrough(new TextEncoderStream());
  }

  Deno.test(`httpServerStreamCount${count}Delay${delay}${action}`, async () => {
    const ac = new AbortController();
    const { promise, resolve } = Promise.withResolvers<void>();
    await using server = Deno.serve({
      handler: (_request) => {
        return new Response(makeStream(count, delay));
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(resolve),
      onError: createOnErrorCb(ac),
    });

    try {
      await promise;
      const resp = await fetch(`http://127.0.0.1:${servePort}/`);
      if (action == "Throw") {
        await assertRejects(async () => {
          await resp.text();
        });
      } else {
        const text = await resp.text();

        let expected = "";
        for (let i = 0; i < count; i++) {
          expected += `a${i}`;
        }

        assertEquals(text, expected);
      }
    } finally {
      ac.abort();
      await server.shutdown();
    }
  });
}

for (const count of [0, 1, 2, 3]) {
  for (const delay of [0, 1, 25]) {
    // Creating a stream that errors in start will throw
    if (delay > 0) {
      createStreamTest(count, delay, "Throw");
    }
    createStreamTest(count, delay, "Close");
  }
}

Deno.test(
  { permissions: { net: true } },
  async function httpServerStreamRequest() {
    const stream = new TransformStream();
    const writer = stream.writable.getWriter();
    writer.write(new TextEncoder().encode("hello "));
    writer.write(new TextEncoder().encode("world"));
    writer.close();
    const { promise, resolve } = Promise.withResolvers<void>();
    const ac = new AbortController();
    await using server = Deno.serve({
      handler: async (request) => {
        const reqBody = await request.text();
        assertEquals("hello world", reqBody);
        return new Response("yo");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(resolve),
      onError: createOnErrorCb(ac),
    });

    await promise;
    const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
      body: stream.readable,
      method: "POST",
      headers: { "connection": "close" },
    });

    assertEquals(await resp.text(), "yo");
    ac.abort();
    await server.finished;
  },
);

Deno.test({ permissions: { net: true } }, async function httpServerClose() {
  const ac = new AbortController();
  const { promise, resolve } = Promise.withResolvers<void>();
  await using server = Deno.serve({
    handler: () => new Response("ok"),
    port: servePort,
    signal: ac.signal,
    onListen: onListen(resolve),
    onError: createOnErrorCb(ac),
  });
  await promise;
  const client = await Deno.connect({ port: servePort });
  client.close();
  ac.abort();
  await server.finished;
});

// https://github.com/denoland/deno/issues/15427
Deno.test({ permissions: { net: true } }, async function httpServerCloseGet() {
  const ac = new AbortController();
  const listeningDeferred = Promise.withResolvers<void>();
  const requestDeferred = Promise.withResolvers<void>();
  const responseDeferred = Promise.withResolvers<void>();
  await using server = Deno.serve({
    handler: async () => {
      requestDeferred.resolve();
      await new Promise((r) => setTimeout(r, 500));
      responseDeferred.resolve();
      return new Response("ok");
    },
    port: servePort,
    signal: ac.signal,
    onListen: onListen(listeningDeferred.resolve),
    onError: createOnErrorCb(ac),
  });
  await listeningDeferred.promise;
  const conn = await Deno.connect({ port: servePort });
  const encoder = new TextEncoder();
  const body =
    `GET / HTTP/1.1\r\nHost: example.domain\r\nConnection: close\r\n\r\n`;
  const writeResult = await conn.write(encoder.encode(body));
  assertEquals(body.length, writeResult);
  await requestDeferred.promise;
  conn.close();
  await responseDeferred.promise;
  ac.abort();
  await server.finished;
});

Deno.test(
  { permissions: { net: true } },
  async function httpServerEmptyBlobResponse() {
    const ac = new AbortController();
    const { promise, resolve } = Promise.withResolvers<void>();
    await using server = Deno.serve({
      handler: () => new Response(new Blob([])),
      port: servePort,
      signal: ac.signal,
      onListen: onListen(resolve),
      onError: createOnErrorCb(ac),
    });

    await promise;
    const resp = await fetch(`http://127.0.0.1:${servePort}/`);
    const respBody = await resp.text();

    assertEquals("", respBody);
    ac.abort();
    await server.finished;
  },
);

// https://github.com/denoland/deno/issues/17291
Deno.test(
  { permissions: { net: true } },
  async function httpServerIncorrectChunkedResponse() {
    const ac = new AbortController();
    const listeningDeferred = Promise.withResolvers<void>();
    const errorDeferred = Promise.withResolvers<void>();
    await using server = Deno.serve({
      handler: () => {
        const body = new ReadableStream({
          start(controller) {
            // Non-encoded string is not a valid readable chunk.
            // @ts-ignore we're testing that input is invalid
            controller.enqueue("wat");
          },
          type: "bytes",
        });
        return new Response(body);
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: (err) => {
        const errResp = new Response(
          `Internal server error: ${(err as Error).message}`,
          { status: 500 },
        );
        errorDeferred.resolve();
        return errResp;
      },
    });

    await listeningDeferred.promise;
    const resp = await fetch(`http://127.0.0.1:${servePort}/`);
    // Incorrectly implemented reader ReadableStream should reject.
    assertStringIncludes(await resp.text(), "Failed to execute 'enqueue'");
    await errorDeferred.promise;
    ac.abort();
    await server.finished;
  },
);

// https://github.com/denoland/deno/issues/27223
Deno.test(
  { permissions: { net: true } },
  async function httpServerNonUint8ArrayResponseStreamDoesNotHang() {
    for (const chunk of ["wat", ""]) {
      const ac = new AbortController();
      const listeningDeferred = Promise.withResolvers<void>();
      const errorDeferred = Promise.withResolvers<unknown>();
      await using server = Deno.serve({
        handler: () => {
          const body = new ReadableStream({
            start(controller) {
              // @ts-ignore we're testing that input is invalid
              controller.enqueue(chunk);
              controller.close();
            },
          });
          return new Response(body);
        },
        port: servePort,
        signal: ac.signal,
        onListen: onListen(listeningDeferred.resolve),
        onError: (err) => {
          errorDeferred.resolve(err);
          return new Response("Internal server error", { status: 500 });
        },
      });

      await listeningDeferred.promise;
      const clientAc = new AbortController();
      const timeoutId = setTimeout(() => clientAc.abort(), 1000);
      let serverErrorTimeoutId: ReturnType<typeof setTimeout> | undefined;
      try {
        const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
          signal: clientAc.signal,
        });
        assertEquals(resp.status, 200);

        await assertRejects(() => resp.text(), TypeError);

        const serverError = await Promise.race([
          errorDeferred.promise,
          new Promise<"timeout">((resolve) => {
            serverErrorTimeoutId = setTimeout(() => resolve("timeout"), 1000);
          }),
        ]);
        assertIsError(serverError, TypeError);
        assertStringIncludes(
          serverError.message,
          "expected typed ArrayBufferView",
        );
      } finally {
        clearTimeout(timeoutId);
        clearTimeout(serverErrorTimeoutId);
        ac.abort();
        await server.finished;
      }
    }
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerCorrectLengthForUnicodeString() {
    const ac = new AbortController();
    const { promise, resolve } = Promise.withResolvers<void>();

    await using server = Deno.serve({
      handler: () => new Response("韓國".repeat(10)),
      port: servePort,
      signal: ac.signal,
      onListen: onListen(resolve),
      onError: createOnErrorCb(ac),
    });

    await promise;
    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();
    const decoder = new TextDecoder();

    const body =
      `GET / HTTP/1.1\r\nHost: example.domain\r\nConnection: close\r\n\r\n`;
    const writeResult = await conn.write(encoder.encode(body));
    assertEquals(body.length, writeResult);

    const buf = new Uint8Array(1024);
    const readResult = await conn.read(buf);
    assert(readResult);
    const msg = decoder.decode(buf.subarray(0, readResult));

    conn.close();

    ac.abort();
    await server.finished;
    assert(msg.includes("content-length: 60"));
  },
);

Deno.test({ permissions: { net: true } }, async function httpServerWebSocket() {
  const ac = new AbortController();
  const listeningDeferred = Promise.withResolvers<void>();
  const doneDeferred = Promise.withResolvers<void>();
  await using server = Deno.serve({
    handler: (request) => {
      const {
        response,
        socket,
      } = Deno.upgradeWebSocket(request);
      socket.onerror = (e) => {
        console.error(e);
        fail();
      };
      socket.onmessage = (m) => {
        socket.send(m.data);
        socket.close(1001);
      };
      socket.onclose = () => doneDeferred.resolve();
      return response;
    },
    port: servePort,
    signal: ac.signal,
    onListen: onListen(listeningDeferred.resolve),
    onError: createOnErrorCb(ac),
  });

  await listeningDeferred.promise;
  const def = Promise.withResolvers<void>();
  const ws = new WebSocket(`ws://localhost:${servePort}`);
  ws.onmessage = (m) => assertEquals(m.data, "foo");
  ws.onerror = (e) => {
    console.error(e);
    fail();
  };
  ws.onclose = () => def.resolve();
  ws.onopen = () => ws.send("foo");

  await def.promise;
  await doneDeferred.promise;
  ac.abort();
  await server.finished;
});

Deno.test(
  { permissions: { net: true } },
  async function httpServerWebSocketRaw() {
    const ac = new AbortController();
    const { promise, resolve } = Promise.withResolvers<void>();
    await using server = Deno.serve({
      handler: async (request) => {
        const { conn, response } = upgradeHttpRaw(request);
        let written;

        written = await conn.write(new TextEncoder().encode("HTTP/1.1 101 Sw"));
        assertEquals(written, 15);

        written = await conn.write(
          new TextEncoder().encode("itching Protocols\r\nConnection:"),
        );
        assertEquals(written, 30);

        written = await conn.write(
          new TextEncoder().encode("Upgrade\r\n\r\nExtra"),
        );
        assertEquals(written, 11); // note: does not include "Extra"

        written = await conn.write(new TextEncoder().encode("Extra"));
        assertEquals(written, 5);

        const buf = new Uint8Array(1024);
        let read;

        // Upgrade data
        read = await conn.read(buf);
        assertEquals(
          new TextDecoder().decode(buf.subarray(0, read!)),
          "Upgrade data",
        );

        // Read the packet to echo
        read = await conn.read(buf);
        // Echo
        await conn.write(buf.subarray(0, read!));

        conn.close();
        return response;
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(resolve),
      onError: createOnErrorCb(ac),
    });

    await promise;

    const conn = await Deno.connect({ port: servePort });
    await conn.write(
      new TextEncoder().encode(
        "GET / HTTP/1.1\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\nUpgrade data",
      ),
    );
    const buf = new Uint8Array(1024);
    let len;

    // Headers
    let headers = "";
    for (let i = 0; i < 2; i++) {
      len = await conn.read(buf);
      headers += new TextDecoder().decode(buf.subarray(0, len!));
      if (headers.endsWith("Extra")) {
        break;
      }
    }
    assertMatch(
      headers,
      /HTTP\/1\.1 101 Switching Protocols[ ,.A-Za-z:0-9\r\n]*Extra/im,
    );

    // Data to echo
    await conn.write(new TextEncoder().encode("buffer data"));

    // Echo
    len = await conn.read(buf);
    assertEquals(
      new TextDecoder().decode(buf.subarray(0, len!)),
      "buffer data",
    );

    conn.close();
    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerWebSocketUpgradeTwice() {
    const ac = new AbortController();
    const done = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    await using server = Deno.serve({
      handler: (request) => {
        const {
          response,
          socket,
        } = Deno.upgradeWebSocket(request);
        assertThrows(
          () => {
            Deno.upgradeWebSocket(request);
          },
          Deno.errors.Http,
          "Already upgraded",
        );
        socket.onerror = (e) => {
          console.error(e);
          fail();
        };
        socket.onmessage = (m) => {
          socket.send(m.data);
          socket.close(1001);
        };
        socket.onclose = () => done.resolve();
        return response;
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const def = Promise.withResolvers<void>();
    const ws = new WebSocket(`ws://localhost:${servePort}`);
    ws.onmessage = (m) => assertEquals(m.data, "foo");
    ws.onerror = (e) => {
      console.error(e);
      fail();
    };
    ws.onclose = () => def.resolve();
    ws.onopen = () => ws.send("foo");

    await def.promise;
    await done.promise;
    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerWebSocketCloseFast() {
    const ac = new AbortController();
    const done = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    await using server = Deno.serve({
      handler: (request) => {
        const {
          response,
          socket,
        } = Deno.upgradeWebSocket(request);
        socket.onopen = () => socket.close();
        socket.onclose = () => done.resolve();
        return response;
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const def = Promise.withResolvers<void>();
    const ws = new WebSocket(`ws://localhost:${servePort}`);
    ws.onerror = (e) => {
      console.error(e);
      fail();
    };
    ws.onclose = () => def.resolve();

    await def.promise;
    await done.promise;
    ac.abort();
    await server.finished;
  },
);

// Regression test for https://github.com/denoland/deno/issues/22387:
// `await server.shutdown()` used to leave upgraded `serverWebSocket`
// resources alive (failing the post-test resource sanitizer), because
// nothing tore down the active WebSocket when the listener stopped.
Deno.test(
  { permissions: { net: true } },
  async function httpServerWebSocketShutdownNoLeak() {
    const wsOpen = Promise.withResolvers<void>();
    const wsClose = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const server = Deno.serve({
      handler: (request) => {
        const { response, socket } = Deno.upgradeWebSocket(request);
        socket.onopen = () => wsOpen.resolve();
        socket.onclose = () => wsClose.resolve();
        return response;
      },
      port: servePort,
      onListen: onListen(listeningDeferred.resolve),
    });

    await listeningDeferred.promise;

    const clientClose = Promise.withResolvers<void>();
    const ws = new WebSocket(`ws://localhost:${servePort}`);
    ws.onclose = () => clientClose.resolve();
    await wsOpen.promise;

    // Without the leak fix this would either hang (graceful waits forever)
    // or return immediately and leave the serverWebSocket resource alive.
    await server.shutdown();

    // The server-initiated Close(1001) should drive both ends to close.
    await wsClose.promise;
    await clientClose.promise;
    await server.finished;
  },
);

// Same regression as `httpServerWebSocketShutdownNoLeak` but via the abort
// signal path (`signal.abort()` -> `op_http_cancel`), which is a separate op
// from `op_http_close` and must also drive open websockets to close.
Deno.test(
  { permissions: { net: true } },
  async function httpServerWebSocketAbortNoLeak() {
    const wsOpen = Promise.withResolvers<void>();
    const wsClose = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();
    const server = Deno.serve({
      handler: (request) => {
        const { response, socket } = Deno.upgradeWebSocket(request);
        socket.onopen = () => wsOpen.resolve();
        socket.onclose = () => wsClose.resolve();
        return response;
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
    });

    await listeningDeferred.promise;

    const clientClose = Promise.withResolvers<void>();
    const ws = new WebSocket(`ws://localhost:${servePort}`);
    ws.onclose = () => clientClose.resolve();
    await wsOpen.promise;

    ac.abort();

    await wsClose.promise;
    await clientClose.promise;
    await server.finished;
  },
);

// Gap from PR review: a handler that calls `Deno.upgradeWebSocket` *during*
// the graceful shutdown drain — after the listener has been cancelled and
// (in the original implementation) the websocket registry snapshot already
// taken — must still have its new websocket torn down. Without the
// `shutting_down` flag the late socket pinned the server alive forever and
// `await server.shutdown()` hung.
Deno.test(
  { permissions: { net: true } },
  async function httpServerWebSocketUpgradeDuringShutdown() {
    const handlerEntered = Promise.withResolvers<void>();
    const releaseHandler = Promise.withResolvers<void>();
    const wsOpen = Promise.withResolvers<void>();
    const wsClose = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const server = Deno.serve({
      handler: async (request) => {
        // Block inside the handler so `server.shutdown()` runs before the
        // websocket upgrade response is returned. The upgrade — and the
        // subsequent registration — therefore happen *after* the shutdown
        // flag has been set and the listener has been cancelled.
        handlerEntered.resolve();
        await releaseHandler.promise;
        const { response, socket } = Deno.upgradeWebSocket(request);
        socket.onopen = () => wsOpen.resolve();
        socket.onclose = () => wsClose.resolve();
        return response;
      },
      port: servePort,
      onListen: onListen(listeningDeferred.resolve),
    });

    await listeningDeferred.promise;

    const clientClose = Promise.withResolvers<void>();
    const ws = new WebSocket(`ws://localhost:${servePort}`);
    ws.onclose = () => clientClose.resolve();
    await handlerEntered.promise;

    // Begin graceful shutdown while the handler is still inside its
    // pre-upgrade await. Then let the handler return the upgrade response,
    // which triggers `register_server_websocket` *after* shutdown began.
    const shutdownPromise = server.shutdown();
    // Yield to the event loop so the shutdown op has a chance to set the
    // `shutting_down` flag and cancel the listener before the handler
    // resumes. We use multiple microtask hops rather than a timer so the
    // resource sanitizer stays happy.
    for (let i = 0; i < 16; i++) await Promise.resolve();
    releaseHandler.resolve();

    // If the late-registered socket weren't auto-shut-down by the
    // `shutting_down` flag, `shutdownPromise` would never resolve.
    await shutdownPromise;

    await wsClose.promise;
    await clientClose.promise;
    await server.finished;
  },
);

// Gap from PR review: `begin_shutdown` used to overwrite an already-recorded
// `Forced` mode with `Graceful` when both `signal.abort()` and
// `server.shutdown()` are called. The user-visible failure is that a websocket
// upgraded during the small window between the abort and the graceful
// shutdown would be put back onto the graceful path (whose Close-frame write
// could hang). Calling abort + shutdown back-to-back must still drain
// cleanly and not regress the abort path.
Deno.test(
  { permissions: { net: true } },
  async function httpServerWebSocketAbortThenShutdownNoHang() {
    const wsOpen = Promise.withResolvers<void>();
    const wsClose = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();
    const server = Deno.serve({
      handler: (request) => {
        const { response, socket } = Deno.upgradeWebSocket(request);
        socket.onopen = () => wsOpen.resolve();
        socket.onclose = () => wsClose.resolve();
        return response;
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
    });

    await listeningDeferred.promise;

    const clientClose = Promise.withResolvers<void>();
    const ws = new WebSocket(`ws://localhost:${servePort}`);
    ws.onclose = () => clientClose.resolve();
    await wsOpen.promise;

    ac.abort();
    // Calling shutdown after abort previously overwrote the registry's
    // recorded mode (`Forced` → `Graceful`). Now it must be a no-op for
    // the mode and not put open websockets back onto the graceful path.
    await server.shutdown();

    await wsClose.promise;
    await clientClose.promise;
    await server.finished;
  },
);

// Gap from PR review: a websocket that has already started its own close
// handshake from the server side (handler called `socket.close()`) must
// not block graceful `server.shutdown()` if the peer never sends the
// close ack. Before the fix, `server_shutdown()` early-returned for
// already-closed sockets and left the pending read alive forever.
Deno.test(
  { permissions: { net: true } },
  async function httpServerWebSocketShutdownAfterServerClose() {
    const wsOpen = Promise.withResolvers<void>();
    const wsCloseStarted = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    let serverSocket: WebSocket | undefined;
    const server = Deno.serve({
      handler: (request) => {
        const { response, socket } = Deno.upgradeWebSocket(request);
        serverSocket = socket;
        socket.onopen = () => {
          // Initiate the close handshake from the server side and then
          // resolve so the test can proceed to call `server.shutdown()`
          // while the socket is still in `CLOSING` state.
          socket.close();
          wsCloseStarted.resolve();
          wsOpen.resolve();
        };
        return response;
      },
      port: servePort,
      onListen: onListen(listeningDeferred.resolve),
    });

    await listeningDeferred.promise;

    // Build a raw TCP client that completes the WS upgrade but never
    // replies with a Close frame. This wedges the socket in CLOSING
    // until the server gives up reading.
    const conn = await Deno.connect({ port: servePort });
    const key = "dGhlIHNhbXBsZSBub25jZQ==";
    const handshake =
      `GET / HTTP/1.1\r\nHost: localhost:${servePort}\r\nUpgrade: websocket\r\n` +
      `Connection: Upgrade\r\nSec-WebSocket-Key: ${key}\r\n` +
      `Sec-WebSocket-Version: 13\r\n\r\n`;
    await conn.write(new TextEncoder().encode(handshake));
    // Drain the handshake response so the server-side upgrade completes.
    const responseBuf = new Uint8Array(1024);
    await conn.read(responseBuf);

    await wsOpen.promise;
    await wsCloseStarted.promise;

    // Without the mid-close fix, this would hang forever because the
    // pending `op_ws_next_event` read never observed any data and the
    // duplicate-Close-frame early return left no one to cancel it.
    await server.shutdown();
    try {
      conn.close();
    } catch { /* already closed */ }
    await server.finished;
    // Silence unused-variable lint; the server-side WebSocket reference
    // is here to make the test intent obvious.
    void serverSocket;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerWebSocketCanAccessRequest() {
    const ac = new AbortController();
    const done = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    await using server = Deno.serve({
      handler: (request) => {
        const {
          response,
          socket,
        } = Deno.upgradeWebSocket(request);
        socket.onerror = (e) => {
          console.error(e);
          fail();
        };
        socket.onmessage = (_m) => {
          socket.send(request.url.toString());
          socket.close(1001);
        };
        socket.onclose = () => done.resolve();
        return response;
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const def = Promise.withResolvers<void>();
    const ws = new WebSocket(`ws://localhost:${servePort}`);
    ws.onmessage = (m) =>
      assertEquals(m.data, `http://localhost:${servePort}/`);
    ws.onerror = (e) => {
      console.error(e);
      fail();
    };
    ws.onclose = () => def.resolve();
    ws.onopen = () => ws.send("foo");

    await def.promise;
    await done.promise;
    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpVeryLargeRequest() {
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    let headers: Headers;
    await using server = Deno.serve({
      handler: (request) => {
        headers = request.headers;
        deferred.resolve();
        return new Response("");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    // Send GET request with a body + content-length.
    const encoder = new TextEncoder();
    const smthElse = "x".repeat(16 * 1024 + 256);
    const body =
      `GET / HTTP/1.1\r\nHost: 127.0.0.1:2333\r\nContent-Length: 5\r\nSomething-Else: ${smthElse}\r\n\r\n`;
    const writeResult = await conn.write(encoder.encode(body));
    assertEquals(body.length, writeResult);
    await deferred.promise;
    conn.close();
    assertEquals(headers!.get("content-length"), "5");
    assertEquals(headers!.get("something-else"), smthElse);
    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpVeryLargeRequestAndBody() {
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    let headers: Headers;
    let text: string;
    await using server = Deno.serve({
      handler: async (request) => {
        headers = request.headers;
        text = await request.text();
        deferred.resolve();
        return new Response("");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    // Send GET request with a body + content-length.
    const encoder = new TextEncoder();
    const smthElse = "x".repeat(16 * 1024 + 256);
    const reqBody = "hello world".repeat(1024);
    let body =
      `PUT / HTTP/1.1\r\nHost: 127.0.0.1:2333\r\nContent-Length: ${reqBody.length}\r\nSomething-Else: ${smthElse}\r\n\r\n${reqBody}`;

    while (body.length > 0) {
      const writeResult = await conn.write(encoder.encode(body));
      body = body.slice(writeResult);
    }

    await deferred.promise;
    conn.close();

    assertEquals(headers!.get("content-length"), `${reqBody.length}`);
    assertEquals(headers!.get("something-else"), smthElse);
    assertEquals(text!, reqBody);
    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpConnectionClose() {
    const deferred = Promise.withResolvers<void>();
    const ac = new AbortController();
    const listeningDeferred = Promise.withResolvers<void>();

    await using server = Deno.serve({
      handler: () => {
        deferred.resolve();
        return new Response("");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    // Send GET request with a body + connection: close.
    const encoder = new TextEncoder();
    const body =
      `GET / HTTP/1.1\r\nHost: 127.0.0.1:2333\r\nConnection: Close\r\n\r\n`;
    const writeResult = await conn.write(encoder.encode(body));
    assertEquals(body.length, writeResult);

    await deferred.promise;
    conn.close();

    ac.abort();
    await server.finished;
  },
);

async function testDuplex(
  reader: ReadableStreamDefaultReader<Uint8Array>,
  writable: WritableStreamDefaultWriter<Uint8Array>,
) {
  await writable.write(new Uint8Array([1]));
  const chunk1 = await reader.read();
  assert(!chunk1.done);
  assertEquals(chunk1.value, new Uint8Array([1]));
  await writable.write(new Uint8Array([2]));
  const chunk2 = await reader.read();
  assert(!chunk2.done);
  assertEquals(chunk2.value, new Uint8Array([2]));
  await writable.close();
  const chunk3 = await reader.read();
  assert(chunk3.done);
}

Deno.test(
  { permissions: { net: true } },
  async function httpServerStreamDuplexDirect() {
    const { promise, resolve } = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve(
      { port: servePort, signal: ac.signal },
      (request: Request) => {
        assert(request.body);
        resolve();
        return new Response(request.body);
      },
    );

    const { readable, writable } = new TransformStream();
    const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
      method: "POST",
      body: readable,
    });

    await promise;
    assert(resp.body);
    await testDuplex(resp.body.getReader(), writable.getWriter());
    ac.abort();
    await server.finished;
  },
);

// Test that a duplex stream passing through JavaScript also works (ie: that the request body resource
// is still alive). https://github.com/denoland/deno/pull/20206
Deno.test(
  { permissions: { net: true } },
  async function httpServerStreamDuplexJavascript() {
    const { promise, resolve } = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve(
      { port: servePort, signal: ac.signal },
      (request: Request) => {
        assert(request.body);
        resolve();
        const reader = request.body.getReader();
        return new Response(
          new ReadableStream({
            async pull(controller) {
              await new Promise((r) => setTimeout(r, 100));
              const { done, value } = await reader.read();
              if (done) {
                controller.close();
              } else {
                controller.enqueue(value);
              }
            },
          }),
        );
      },
    );

    const { readable, writable } = new TransformStream();
    const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
      method: "POST",
      body: readable,
    });

    await promise;
    assert(resp.body);
    await testDuplex(resp.body.getReader(), writable.getWriter());
    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  // Issue: https://github.com/denoland/deno/issues/10930
  async function httpServerStreamingResponse() {
    // This test enqueues a single chunk for readable
    // stream and waits for client to read that chunk and signal
    // it before enqueueing subsequent chunk. Issue linked above
    // presented a situation where enqueued chunks were not
    // written to the HTTP connection until the next chunk was enqueued.
    const listeningDeferred = Promise.withResolvers<void>();
    const deferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    let counter = 0;

    const deferreds = [
      Promise.withResolvers<void>(),
      Promise.withResolvers<void>(),
      Promise.withResolvers<void>(),
    ];

    async function writeRequest(conn: Deno.Conn) {
      const encoder = new TextEncoder();
      const decoder = new TextDecoder();

      const w = new BufWriter(conn);
      const r = new BufReader(conn);
      const body = `GET / HTTP/1.1\r\nHost: 127.0.0.1:${servePort}\r\n\r\n`;
      const writeResult = await w.write(encoder.encode(body));
      assertEquals(body.length, writeResult);
      await w.flush();
      const tpr = new TextProtoReader(r);
      const statusLine = await tpr.readLine();
      assert(statusLine !== null);
      const headers = await tpr.readMimeHeader();
      assert(headers !== null);

      const chunkedReader = chunkedBodyReader(headers, r);

      const buf = new Uint8Array(5);
      const dest = new Buffer();

      let result: number | null;

      try {
        while ((result = await chunkedReader.read(buf)) !== null) {
          const len = Math.min(buf.byteLength, result);

          await dest.write(buf.subarray(0, len));

          // Resolve a deferred - this will make response stream to
          // enqueue next chunk.
          deferreds[counter - 1].resolve();
        }
        return decoder.decode(dest.bytes());
      } catch (e) {
        console.error(e);
      }
    }

    function periodicStream() {
      return new ReadableStream({
        start(controller) {
          controller.enqueue(`${counter}\n`);
          counter++;
        },

        async pull(controller) {
          if (counter >= 3) {
            return controller.close();
          }

          await deferreds[counter - 1].promise;

          controller.enqueue(`${counter}\n`);
          counter++;
        },
      }).pipeThrough(new TextEncoderStream());
    }

    await using server = Deno.serve({
      handler: () => {
        deferred.resolve();
        return new Response(periodicStream());
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    // start a client
    const clientConn = await Deno.connect({ port: servePort });

    const r1 = await writeRequest(clientConn);
    assertEquals(r1, "0\n1\n2\n");

    ac.abort();
    await deferred.promise;
    await server.finished;
    clientConn.close();
  },
);

// Make sure that the chunks of a large response aren't repeated or corrupted in some other way by
// scatterning sentinels throughout.
// https://github.com/denoland/fresh/issues/1699
Deno.test(
  { permissions: { net: true } },
  async function httpLargeReadableStreamChunk() {
    const ac = new AbortController();
    await using server = Deno.serve({
      handler() {
        return new Response(
          new ReadableStream({
            start(controller) {
              const buffer = new Uint8Array(1024 * 1024);
              // Mark the buffer with sentinels
              for (let i = 0; i < 256; i++) {
                buffer[i * 4096] = i;
              }
              controller.enqueue(buffer);
              controller.close();
            },
          }),
        );
      },
      port: servePort,
      signal: ac.signal,
    });
    const response = await fetch(`http://localhost:${servePort}/`);
    const body = await response.bytes();
    assertEquals(1024 * 1024, body.byteLength);
    for (let i = 0; i < 256; i++) {
      assertEquals(
        i,
        body[i * 4096],
        `sentinel mismatch at index ${i * 4096}`,
      );
    }
    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpRequestLatin1Headers() {
    const listeningDeferred = Promise.withResolvers<void>();
    const deferred = Promise.withResolvers<void>();
    const ac = new AbortController();
    await using server = Deno.serve({
      handler: (request) => {
        assertEquals(request.headers.get("X-Header-Test"), "á");
        deferred.resolve();
        return new Response("hello", { headers: { "X-Header-Test": "Æ" } });
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const clientConn = await Deno.connect({ port: servePort });
    const requestText =
      `GET / HTTP/1.1\r\nHost: 127.0.0.1:${servePort}\r\nX-Header-Test: á\r\n\r\n`;
    const requestBytes = new Uint8Array(requestText.length);
    for (let i = 0; i < requestText.length; i++) {
      requestBytes[i] = requestText.charCodeAt(i);
    }
    let written = 0;
    while (written < requestBytes.byteLength) {
      written += await clientConn.write(requestBytes.slice(written));
    }

    const buf = new Uint8Array(1024);
    await clientConn.read(buf);

    await deferred.promise;
    const responseText = new TextDecoder("iso-8859-1").decode(buf);
    clientConn.close();

    ac.abort();
    await server.finished;

    assertMatch(responseText, /\r\n[Xx]-[Hh]eader-[Tt]est: Æ\r\n/);
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerRequestWithoutPath() {
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve({
      handler: async (request) => {
        // FIXME:
        // assertEquals(new URL(request.url).href, `http://127.0.0.1:${servePort}/`);
        assertEquals(await request.text(), "");
        deferred.resolve();
        return new Response("11");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const clientConn = await Deno.connect({ port: servePort });

    async function writeRequest(conn: Deno.Conn) {
      const encoder = new TextEncoder();

      const w = new BufWriter(conn);
      const r = new BufReader(conn);
      const body =
        `CONNECT 127.0.0.1:${servePort} HTTP/1.1\r\nHost: 127.0.0.1:${servePort}\r\n\r\n`;
      const writeResult = await w.write(encoder.encode(body));
      assertEquals(body.length, writeResult);
      await w.flush();
      const tpr = new TextProtoReader(r);
      const statusLine = await tpr.readLine();
      assert(statusLine !== null);
      const m = statusLine.match(/^(.+?) (.+?) (.+?)$/);
      assert(m !== null, "must be matched");
      const [_, _proto, status, _ok] = m;
      assertEquals(status, "200");
      const headers = await tpr.readMimeHeader();
      assert(headers !== null);
    }

    await writeRequest(clientConn);
    clientConn.close();
    await deferred.promise;

    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpCookieConcatenation() {
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve({
      handler: async (request) => {
        assertEquals(await request.text(), "");
        assertEquals(request.headers.get("cookie"), "foo=bar; bar=foo");
        deferred.resolve();
        return new Response("ok");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
      reusePort: true,
    });

    await listeningDeferred.promise;
    const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
      headers: [
        ["connection", "close"],
        ["cookie", "foo=bar"],
        ["cookie", "bar=foo"],
      ],
    });
    await deferred.promise;

    const text = await resp.text();
    assertEquals(text, "ok");

    ac.abort();
    await server.finished;
  },
);

type SplitHeaderResult = {
  cookieGet: string | null;
  cookieIter: string | undefined;
  xTestGet: string | null;
  xTestIter: string | undefined;
};

const expectedSplitHeaderResult = {
  cookieGet: "a=1; b=2",
  cookieIter: "a=1; b=2",
  xTestGet: "one, two",
  xTestIter: "one, two",
} as const;

function readSplitHeaders(req: Request): SplitHeaderResult {
  const iterHeaders = new Map(req.headers);
  return {
    cookieGet: req.headers.get("cookie"),
    cookieIter: iterHeaders.get("cookie"),
    xTestGet: req.headers.get("x-test"),
    xTestIter: iterHeaders.get("x-test"),
  };
}

Deno.test(
  { permissions: { net: true } },
  async function httpServerHttp1SplitHeaderConcatenation() {
    const ac = new AbortController();
    const listening = Promise.withResolvers<void>();
    const headers = Promise.withResolvers<SplitHeaderResult>();

    await using server = Deno.serve({
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listening.resolve),
      handler: (req) => {
        headers.resolve(readSplitHeaders(req));
        return new Response("ok");
      },
    });
    await listening.promise;

    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();
    await writeAll(
      conn,
      encoder.encode(
        `GET / HTTP/1.1\r\nHost: localhost:${servePort}\r\nCookie: a=1\r\nCookie: b=2\r\nX-Test: one\r\nX-Test: two\r\nConnection: close\r\n\r\n`,
      ),
    );

    assertEquals(await headers.promise, expectedSplitHeaderResult);

    conn.close();
    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerHttp2SplitHeaderConcatenation() {
    const ac = new AbortController();
    const listening = Promise.withResolvers<void>();
    const headers = Promise.withResolvers<SplitHeaderResult>();

    await using server = Deno.serve({
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listening.resolve),
      handler: (req) => {
        headers.resolve(readSplitHeaders(req));
        return new Response("ok");
      },
    });
    await listening.promise;

    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();

    await writeAll(conn, encoder.encode("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"));
    await writeAll(conn, new Uint8Array([0, 0, 0, 4, 0, 0, 0, 0, 0]));

    const literalHeader = (name: string, value: string): number[] => {
      const encodedName = encoder.encode(name);
      const encodedValue = encoder.encode(value);
      return [
        0x00,
        encodedName.length,
        ...encodedName,
        encodedValue.length,
        ...encodedValue,
      ];
    };
    const block = [
      ...literalHeader(":method", "GET"),
      ...literalHeader(":path", "/"),
      ...literalHeader(":scheme", "http"),
      ...literalHeader(":authority", `localhost:${servePort}`),
      ...literalHeader("cookie", "a=1"),
      ...literalHeader("cookie", "b=2"),
      ...literalHeader("x-test", "one"),
      ...literalHeader("x-test", "two"),
    ];
    const len = block.length;
    await writeAll(
      conn,
      new Uint8Array([
        (len >> 16) & 0xff,
        (len >> 8) & 0xff,
        len & 0xff,
        0x01, // HEADERS
        0x05, // END_STREAM | END_HEADERS
        0,
        0,
        0,
        1,
        ...block,
      ]),
    );

    assertEquals(await headers.promise, expectedSplitHeaderResult);

    conn.close();
    ac.abort();
    await server.finished;
  },
);

// https://github.com/denoland/deno/issues/12741
// https://github.com/denoland/deno/pull/12746
// https://github.com/denoland/deno/pull/12798
Deno.test(
  { permissions: { net: true, run: true } },
  async function httpServerDeleteRequestHasBody() {
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    const hostname = "localhost";

    await using server = Deno.serve({
      handler: () => {
        deferred.resolve();
        return new Response("ok");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const url = `http://${hostname}:${servePort}/`;
    const args = ["-X", "DELETE", url];
    const { success } = await new Deno.Command("curl", {
      args,
      stdout: "null",
      stderr: "null",
    }).output();
    assert(success);
    await deferred.promise;
    ac.abort();

    await server.finished;
  },
);

// FIXME:
Deno.test(
  { permissions: { net: true } },
  async function httpServerRespondNonAsciiUint8Array() {
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve({
      handler: (request) => {
        assertEquals(request.body, null);
        deferred.resolve();
        return new Response(new Uint8Array([128]));
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });
    await listeningDeferred.resolve;
    const resp = await fetch(`http://localhost:${servePort}/`);

    await deferred.promise;

    assertEquals(resp.status, 200);
    const body = await resp.bytes();
    assertEquals(body, new Uint8Array([128]));

    ac.abort();
    await server.finished;
  },
);

// Some of these tests are ported from Hyper
// https://github.com/hyperium/hyper/blob/889fa2d87252108eb7668b8bf034ffcc30985117/src/proto/h1/role.rs
// https://github.com/hyperium/hyper/blob/889fa2d87252108eb7668b8bf034ffcc30985117/tests/server.rs

Deno.test(
  { permissions: { net: true } },
  async function httpServerParseRequest() {
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve({
      handler: (request) => {
        assertEquals(request.method, "GET");
        assertEquals(request.headers.get("host"), "deno.land");
        deferred.resolve();
        return new Response("ok");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();
    const body = `GET /echo HTTP/1.1\r\nHost: deno.land\r\n\r\n`;
    const writeResult = await conn.write(encoder.encode(body));
    assertEquals(body.length, writeResult);
    await deferred.promise;
    conn.close();

    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerParseHeaderHtabs() {
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve({
      handler: (request) => {
        assertEquals(request.method, "GET");
        assertEquals(request.headers.get("server"), "hello\tworld");
        deferred.resolve();
        return new Response("ok");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();
    const body = `GET / HTTP/1.1\r\nserver: hello\tworld\r\n\r\n`;
    const writeResult = await conn.write(encoder.encode(body));
    assertEquals(body.length, writeResult);
    await deferred.promise;
    conn.close();

    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerGetShouldIgnoreBody() {
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve({
      handler: async (request) => {
        assertEquals(request.method, "GET");
        assertEquals(await request.text(), "");
        deferred.resolve();
        return new Response("ok");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();
    // Connection: close = don't try to parse the body as a new request
    const body =
      `GET / HTTP/1.1\r\nHost: example.domain\r\nConnection: close\r\n\r\nI shouldn't be read.\r\n`;
    const writeResult = await conn.write(encoder.encode(body));
    assertEquals(body.length, writeResult);
    await deferred.promise;
    conn.close();

    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerPostWithBody() {
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve({
      handler: async (request) => {
        assertEquals(request.method, "POST");
        assertEquals(await request.text(), "I'm a good request.");
        deferred.resolve();
        return new Response("ok");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();
    const body =
      `POST / HTTP/1.1\r\nHost: example.domain\r\nContent-Length: 19\r\n\r\nI'm a good request.`;
    const writeResult = await conn.write(encoder.encode(body));
    assertEquals(body.length, writeResult);
    await deferred.promise;
    conn.close();

    ac.abort();
    await server.finished;
  },
);

type TestCase = {
  headers?: Record<string, string>;
  // deno-lint-ignore no-explicit-any
  body: any;
  expectsChunked?: boolean;
  expectsConnLen?: boolean;
};

function hasHeader(msg: string, name: string): boolean {
  const n = msg.indexOf("\r\n\r\n") || msg.length;
  return msg.slice(0, n).toLowerCase().includes(name.toLowerCase());
}

function getRawHeader(msg: string, name: string): string | null {
  const n = msg.indexOf("\r\n\r\n") || msg.length;
  const prefix = `${name.toLowerCase()}:`;
  for (const line of msg.slice(0, n).split("\r\n")) {
    if (line.toLowerCase().startsWith(prefix)) {
      return line.slice(prefix.length).trim();
    }
  }
  return null;
}

function createServerLengthTest(name: string, testCase: TestCase) {
  Deno.test(name, async function () {
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve({
      handler: (request) => {
        assertEquals(request.method, "GET");
        deferred.resolve();
        return new Response(testCase.body, testCase.headers ?? {});
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();
    const body =
      `GET / HTTP/1.1\r\nHost: example.domain\r\nConnection: close\r\n\r\n`;
    const writeResult = await conn.write(encoder.encode(body));
    assertEquals(body.length, writeResult);
    await deferred.promise;

    const decoder = new TextDecoder();
    let msg = "";
    let assertionsPassed = false;
    let lastAssertionError: unknown;
    while (true) {
      const buf = new Uint8Array(1024);
      const readResult = await conn.read(buf);
      if (!readResult) {
        break;
      }
      msg += decoder.decode(buf.subarray(0, readResult));
      try {
        assert(
          testCase.expectsChunked == hasHeader(msg, "Transfer-Encoding:"),
          msg,
        );
        assert(testCase.expectsChunked == hasHeader(msg, "chunked"), msg);
        assert(
          testCase.expectsConnLen == hasHeader(msg, "Content-Length:"),
          msg,
        );

        const n = msg.indexOf("\r\n\r\n") + 4;

        if (testCase.expectsChunked) {
          assertEquals(msg.slice(n + 1, n + 3), "\r\n");
          assertEquals(msg.slice(msg.length - 7), "\r\n0\r\n\r\n");
        }

        if (testCase.expectsConnLen && typeof testCase.body === "string") {
          assertEquals(
            getRawHeader(msg, "Content-Length"),
            String(new TextEncoder().encode(testCase.body).byteLength),
          );
          assertEquals(msg.slice(n), testCase.body);
        }
        assertionsPassed = true;
        break;
      } catch (error) {
        lastAssertionError = error;
        continue;
      }
    }
    if (!assertionsPassed) {
      if (lastAssertionError) {
        throw lastAssertionError;
      }
      fail("connection closed before response assertions passed");
    }

    conn.close();

    ac.abort();
    await server.finished;
  });
}

// Make a genuinely-opaque readable stream from a string, i.e. one whose total
// length is not known ahead of time. Note: `new Response(s).body` can no longer
// be used here -- a body stream materialized from a static string is now
// recovered and served with a Content-Length (see `recoveredStaticStreamBody`
// below), so it would no longer exercise the chunked path these tests target.
function stream(s: string): ReadableStream<Uint8Array> {
  const bytes = new TextEncoder().encode(s);
  return new ReadableStream({
    pull(controller) {
      if (bytes.byteLength > 0) {
        controller.enqueue(bytes);
      }
      controller.close();
    },
  });
}

createServerLengthTest("fixedResponseKnown", {
  headers: { "content-length": "11" },
  body: "foo bar baz",
  expectsChunked: false,
  expectsConnLen: true,
});

createServerLengthTest("fixedResponseKnownMismatchedLength", {
  headers: { "content-length": "999" },
  body: "hello",
  expectsChunked: false,
  expectsConnLen: true,
});

createServerLengthTest("fixedResponseUnknown", {
  headers: { "content-length": "11" },
  body: stream("foo bar baz"),
  expectsChunked: true,
  expectsConnLen: false,
});

createServerLengthTest("fixedResponseKnownEmpty", {
  headers: { "content-length": "0" },
  body: "",
  expectsChunked: false,
  expectsConnLen: true,
});

createServerLengthTest("chunkedRespondKnown", {
  headers: { "transfer-encoding": "chunked" },
  body: "foo bar baz",
  expectsChunked: false,
  expectsConnLen: true,
});

createServerLengthTest("chunkedRespondUnknown", {
  headers: { "transfer-encoding": "chunked" },
  body: stream("foo bar baz"),
  expectsChunked: true,
  expectsConnLen: false,
});

createServerLengthTest("autoResponseWithKnownLength", {
  body: "foo bar baz",
  expectsChunked: false,
  expectsConnLen: true,
});

createServerLengthTest("autoResponseWithUnknownLength", {
  body: stream("foo bar baz"),
  expectsChunked: true,
  expectsConnLen: false,
});

createServerLengthTest("autoResponseWithKnownLengthEmpty", {
  body: "",
  expectsChunked: false,
  expectsConnLen: true,
});

createServerLengthTest("autoResponseWithUnknownLengthEmpty", {
  body: stream(""),
  expectsChunked: true,
  expectsConnLen: false,
});

// A body stream that was materialized from a static string and not read (the
// common `new Response(oldResponse.body, oldResponse)` framework pattern, e.g.
// Hono header middleware) is recovered to its static body and served on the
// fast path with a Content-Length, rather than being downgraded to chunked.
createServerLengthTest("recoveredStaticStreamBody", {
  body: new Response("foo bar baz").body!,
  expectsChunked: false,
  expectsConnLen: true,
});

Deno.test(
  { permissions: { net: true } },
  async function httpServerPostWithContentLengthBody() {
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve({
      handler: async (request) => {
        assertEquals(request.method, "POST");
        assertEquals(request.headers.get("content-length"), "5");
        assertEquals(await request.text(), "hello");
        deferred.resolve();
        return new Response("ok");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();

    const body =
      `POST / HTTP/1.1\r\nHost: example.domain\r\nContent-Length: 5\r\n\r\nhello`;
    const writeResult = await conn.write(encoder.encode(body));
    assertEquals(body.length, writeResult);
    await deferred.promise;

    conn.close();

    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerPostWithInvalidPrefixContentLength() {
    const ac = new AbortController();
    const { promise, resolve } = Promise.withResolvers<void>();
    await using server = Deno.serve({
      handler: () => {
        throw new Error("unreachable");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(resolve),
      onError: createOnErrorCb(ac),
    });

    await promise;
    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();
    const decoder = new TextDecoder();

    const body =
      `POST / HTTP/1.1\r\nHost: example.domain\r\nContent-Length: +5\r\n\r\nhello`;
    const writeResult = await conn.write(encoder.encode(body));
    assertEquals(body.length, writeResult);

    const buf = new Uint8Array(1024);
    const readResult = await conn.read(buf);
    assert(readResult);
    const msg = decoder.decode(buf.subarray(0, readResult));
    assert(msg.includes("HTTP/1.1 400 Bad Request"));

    conn.close();

    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerPostWithChunkedBody() {
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve({
      handler: async (request) => {
        assertEquals(request.method, "POST");
        assertEquals(await request.text(), "qwert");
        deferred.resolve();
        return new Response("ok");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();

    const body =
      `POST / HTTP/1.1\r\nHost: example.domain\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nq\r\n2\r\nwe\r\n2\r\nrt\r\n0\r\n\r\n`;
    const writeResult = await conn.write(encoder.encode(body));
    assertEquals(body.length, writeResult);
    await deferred.promise;

    conn.close();

    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerPostWithIncompleteBody() {
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve({
      handler: async (r) => {
        deferred.resolve();
        assertEquals(await r.text(), "12345");
        return new Response("ok");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();

    const body =
      `POST / HTTP/1.1\r\nHost: example.domain\r\nContent-Length: 10\r\n\r\n12345`;
    const writeResult = await conn.write(encoder.encode(body));
    assertEquals(body.length, writeResult);

    await deferred.promise;
    conn.close();

    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerIgnoredStreamingBodyClosesConnection() {
    const firstDeferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();
    const requests: string[] = [];

    await using server = Deno.serve({
      handler: (request) => {
        requests.push(`${request.method} ${new URL(request.url).pathname}`);
        firstDeferred.resolve();
        return new Response("ok");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();
    const decoder = new TextDecoder();
    const smuggled = `GET /smuggled HTTP/1.1\r\nHost: example.domain\r\n\r\n`;
    const second =
      `GET /second HTTP/1.1\r\nHost: example.domain\r\nConnection: close\r\n\r\n`;
    const head =
      `POST /first HTTP/1.1\r\nHost: example.domain\r\nContent-Length: ${smuggled.length}\r\n\r\n`;

    await writeAll(conn, encoder.encode(head));
    await firstDeferred.promise;
    try {
      await writeAll(conn, encoder.encode(smuggled + second));
    } catch {
      // The fixed path may close before the client can write the unread body.
    }

    let msg = "";
    while (true) {
      const buf = new Uint8Array(1024);
      let readResult: number | null;
      try {
        readResult = await conn.read(buf);
      } catch (error) {
        if (!(error instanceof Deno.errors.ConnectionReset)) {
          throw error;
        }
        break;
      }
      if (!readResult) {
        break;
      }
      msg += decoder.decode(buf.subarray(0, readResult));
    }

    assertEquals(requests, ["POST /first"]);
    assertStringIncludes(msg, "HTTP/1.1 200 OK");
    assertStringIncludes(msg.toLowerCase(), "connection: close");
    assertEquals(msg.includes("/smuggled"), false);
    assertEquals(
      msg.indexOf("HTTP/1.1 200 OK"),
      msg.lastIndexOf("HTTP/1.1 200 OK"),
    );

    conn.close();

    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerHeadResponseDoesntSendBody() {
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();
    let requests = 0;

    await using server = Deno.serve({
      handler: () => {
        requests++;
        if (requests === 2) {
          deferred.resolve();
        }
        return new Response("NaN".repeat(100));
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();
    const decoder = new TextDecoder();

    const body =
      `HEAD / HTTP/1.1\r\nHost: example.domain\r\n\r\nGET / HTTP/1.1\r\nHost: example.domain\r\nConnection: close\r\n\r\n`;
    const writeResult = await conn.write(encoder.encode(body));
    assertEquals(body.length, writeResult);

    await deferred.promise;

    let msg = "";
    while (true) {
      const buf = new Uint8Array(1024);
      const readResult = await conn.read(buf);
      if (!readResult) {
        break;
      }
      msg += decoder.decode(buf.subarray(0, readResult));
    }

    const firstHeaderEnd = msg.indexOf("\r\n\r\n");
    assert(firstHeaderEnd > 0);
    assertStringIncludes(msg.slice(0, firstHeaderEnd), "content-length: 300");
    const secondResponse = msg.indexOf("HTTP/1.1 200 OK", firstHeaderEnd + 4);
    assert(secondResponse > firstHeaderEnd);
    assertEquals(msg.slice(firstHeaderEnd + 4, secondResponse), "");
    assertStringIncludes(msg.slice(secondResponse), "NaNNaN");

    conn.close();

    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerHeadResponseRespectsExplicitContentLength() {
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve({
      handler: () => new Response(null, { headers: { "content-length": "5" } }),
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();
    const decoder = new TextDecoder();

    const request =
      `HEAD / HTTP/1.1\r\nHost: example.domain\r\nConnection: close\r\n\r\n`;
    const writeResult = await conn.write(encoder.encode(request));
    assertEquals(request.length, writeResult);

    let msg = "";
    while (true) {
      const buf = new Uint8Array(1024);
      const readResult = await conn.read(buf);
      if (!readResult) {
        break;
      }
      msg += decoder.decode(buf.subarray(0, readResult));
    }

    assertStringIncludes(msg, "content-length: 5");
    // A HEAD response must not carry a body, even with content-length > 0.
    assertEquals(msg.indexOf("\r\n\r\n"), msg.length - 4);

    conn.close();

    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerUpgradeWithContentLengthRejected() {
    const ac = new AbortController();
    const listeningDeferred = Promise.withResolvers<void>();

    await using server = Deno.serve({
      handler: () => {
        fail("Upgrade request with Content-Length should be rejected");
        return new Response("ok");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();
    const decoder = new TextDecoder();
    const smuggled = `GET /second HTTP/1.1\r\nHost: example.domain\r\n\r\n`;
    const body =
      `GET /upgrade HTTP/1.1\r\nHost: example.domain\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nContent-Length: ${smuggled.length}\r\n\r\n${smuggled}`;
    const writeResult = await conn.write(encoder.encode(body));
    assertEquals(body.length, writeResult);

    const buf = new Uint8Array(1024);
    const readResult = await conn.read(buf);
    assert(readResult !== null);
    const msg = decoder.decode(buf.subarray(0, readResult));
    assertStringIncludes(msg, "HTTP/1.1 400 Bad Request");
    assertEquals(msg.includes("HTTP/1.1 200 OK"), false);

    conn.close();
    ac.abort();
    await server.finished;
  },
);

function makeTempData(size: number) {
  return new Uint8Array(size).fill(1);
}

async function makeTempFile(size: number) {
  const tmpFile = await Deno.makeTempFile();
  using file = await Deno.open(tmpFile, { write: true, read: true });
  const data = makeTempData(size);
  await file.write(data);

  return await Deno.open(tmpFile, { write: true, read: true });
}

const compressionTestCases = [
  { name: "Empty", length: 0, in: {}, out: {}, expect: null },
  {
    name: "EmptyAcceptGzip",
    length: 0,
    in: { "Accept-Encoding": "gzip" },
    out: {},
    expect: null,
  },
  // This technically would be compressible if not for the size, however the size_hint is not implemented
  // for FileResource and we don't currently peek ahead on resources.
  // {
  //   name: "EmptyAcceptGzip2",
  //   length: 0,
  //   in: { "Accept-Encoding": "gzip" },
  //   out: { "Content-Type": "text/plain" },
  //   expect: null,
  // },
  { name: "Incompressible", length: 1024, in: {}, out: {}, expect: null },
  {
    name: "IncompressibleAcceptGzip",
    length: 1024,
    in: { "Accept-Encoding": "gzip" },
    out: {},
    expect: null,
  },
  {
    name: "IncompressibleType",
    length: 1024,
    in: { "Accept-Encoding": "gzip" },
    out: { "Content-Type": "text/fake" },
    expect: null,
  },
  {
    name: "CompressibleType",
    length: 1024,
    in: { "Accept-Encoding": "gzip" },
    out: { "Content-Type": "text/plain" },
    expect: "gzip",
  },
  {
    name: "CompressibleType2",
    length: 1024,
    in: { "Accept-Encoding": "gzip, deflate, br" },
    out: { "Content-Type": "text/plain" },
    expect: "br",
  },
  {
    name: "CompressibleType3",
    length: 1024,
    in: { "Accept-Encoding": "br" },
    out: { "Content-Type": "text/plain" },
    expect: "br",
  },
  {
    name: "CompressibleTypeChrome",
    length: 1024,
    in: { "Accept-Encoding": "gzip, deflate, br, zstd" },
    out: { "Content-Type": "text/plain" },
    expect: "br",
  },
  {
    name: "CompressibleTypeUnsupportedToken",
    length: 1024,
    in: { "Accept-Encoding": "br, compress" },
    out: { "Content-Type": "text/plain" },
    expect: "br",
  },
  {
    name: "RefusedCompression",
    length: 1024,
    in: { "Accept-Encoding": "br;q=0, gzip;q=0" },
    out: { "Content-Type": "text/plain" },
    expect: null,
  },
  {
    name: "CompressionSubstring",
    length: 1024,
    in: { "Accept-Encoding": "xbr, gzipx" },
    out: { "Content-Type": "text/plain" },
    expect: null,
  },
  {
    name: "IncompressibleRange",
    length: 1024,
    in: { "Accept-Encoding": "gzip" },
    out: { "Content-Type": "text/plain", "Content-Range": "1" },
    expect: null,
  },
  {
    name: "IncompressibleCE",
    length: 1024,
    in: { "Accept-Encoding": "gzip" },
    out: { "Content-Type": "text/plain", "Content-Encoding": "random" },
    expect: null,
  },
  {
    name: "IncompressibleCC",
    length: 1024,
    in: { "Accept-Encoding": "gzip" },
    out: { "Content-Type": "text/plain", "Cache-Control": "no-transform" },
    expect: null,
  },
  {
    name: "BadHeader",
    length: 1024,
    in: { "Accept-Encoding": "\x81" },
    out: { "Content-Type": "text/plain", "Cache-Control": "no-transform" },
    expect: null,
  },
];

for (const testCase of compressionTestCases) {
  const name = `httpServerCompression${testCase.name}`;
  Deno.test(
    { permissions: { net: true, write: true, read: true } },
    {
      [name]: async function () {
        const deferred = Promise.withResolvers<void>();
        const listeningDeferred = Promise.withResolvers<void>();
        const ac = new AbortController();
        await using server = Deno.serve({
          automaticCompression: true,
          handler: async (_request) => {
            const f = await makeTempFile(testCase.length);
            deferred.resolve();
            // deno-lint-ignore no-explicit-any
            const headers = testCase.out as any;
            headers["Content-Length"] = testCase.length.toString();
            return new Response(f.readable, {
              headers: headers as HeadersInit,
            });
          },
          port: servePort,
          signal: ac.signal,
          onListen: onListen(listeningDeferred.resolve),
          onError: createOnErrorCb(ac),
        });
        try {
          await listeningDeferred.promise;
          const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
            headers: testCase.in as HeadersInit,
          });
          await deferred.promise;
          const body = await resp.bytes();
          if (testCase.expect == null) {
            assertEquals(body.byteLength, testCase.length);
            assertEquals(
              resp.headers.get("content-length"),
              testCase.length.toString(),
            );
            assertEquals(
              resp.headers.get("content-encoding"),
              testCase.out["Content-Encoding"] || null,
            );
          } else if (testCase.expect == "gzip" || testCase.expect == "br") {
            // The fetch will transparently decompress this response. Per the
            // fetch spec the `content-encoding` header stays visible;
            // `content-length` is absent because the server streams the
            // compressed body with chunked transfer encoding.
            assertEquals(body.byteLength, testCase.length);
            assertEquals(
              resp.headers.get("content-encoding"),
              testCase.expect,
            );
            assertEquals(resp.headers.get("content-length"), null);
          }
        } finally {
          ac.abort();
          await server.finished;
        }
      },
    }[name],
  );
}

Deno.test(
  { permissions: { net: true } },
  async function httpServerAutomaticCompressionAppliesForNoRequestFastPath() {
    const body = "a".repeat(1000);
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    // A zero-argument handler takes the native "no request" fast path, where
    // the request is never exposed to JS. Automatic compression must still
    // apply: `accept-encoding` is retained so the response can be compressed.
    await using server = Deno.serve({
      automaticCompression: true,
      handler: () => {
        return new Response(body, {
          headers: { "content-type": "text/plain" },
        });
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    try {
      await listeningDeferred.promise;
      const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
        headers: { "accept-encoding": "gzip" },
      });
      assertEquals(await resp.text(), body);
      assertEquals(resp.headers.get("content-encoding"), "gzip");
      assertEquals(resp.headers.get("vary"), "Accept-Encoding");
    } finally {
      ac.abort();
      await server.finished;
    }
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerAutomaticCompressionOffByDefault() {
    const body = "a".repeat(1000);
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve({
      handler: (_request) => {
        return new Response(body, {
          headers: { "content-type": "text/plain" },
        });
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    try {
      await listeningDeferred.promise;
      const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
        headers: { "accept-encoding": "gzip" },
      });
      assertEquals(await resp.text(), body);
      assertEquals(resp.headers.get("content-encoding"), null);
      assertEquals(resp.headers.get("vary"), null);
    } finally {
      ac.abort();
      await server.finished;
    }
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerAutomaticCompressionCanBeDisabledHyper() {
    const body = "a".repeat(1000);
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve({
      automaticCompression: false,
      handler: (_request) => {
        return new Response(body, {
          headers: { "content-type": "text/plain" },
        });
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    try {
      await listeningDeferred.promise;
      const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
        headers: { "accept-encoding": "gzip" },
      });
      assertEquals(await resp.text(), body);
      assertEquals(resp.headers.get("content-encoding"), null);
      assertEquals(resp.headers.get("vary"), null);
    } finally {
      ac.abort();
      await server.finished;
    }
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerAutomaticCompressionPostBodyDirectResponseCloses() {
    const body = "a".repeat(1000);
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve({
      automaticCompression: true,
      handler: (_request) => {
        return new Response(body, {
          headers: { "content-type": "text/plain" },
        });
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();
    await writeAll(
      conn,
      encoder.encode(
        `POST / HTTP/1.1\r\nHost: example.domain\r\nAccept-Encoding: gzip\r\nContent-Length: 5\r\n\r\n`,
      ),
    );

    const reader = new BufReader(conn);
    const textProtoReader = new TextProtoReader(reader);
    const statusLine = await textProtoReader.readLine();
    assertEquals(statusLine, "HTTP/1.1 200 OK");
    const headers = await textProtoReader.readMimeHeader();
    assert(headers !== null);
    assertEquals(headers.get("content-encoding"), "gzip");
    assertEquals(headers.get("transfer-encoding"), "chunked");
    assertEquals(headers.get("connection"), "close");

    conn.close();
    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { run: true } },
  async function httpServerAutomaticCompressionEnvDefault() {
    async function run(envValue: string): Promise<string[]> {
      const { success, stdout } = await new Deno.Command(Deno.execPath(), {
        args: [
          "eval",
          `
          async function check(options) {
            let server;
            const finished = Promise.withResolvers();
            server = Deno.serve({
              ...options,
              port: 0,
              onListen: async ({ port }) => {
                const resp = await fetch("http://127.0.0.1:" + port + "/", {
                  headers: { "accept-encoding": "gzip" },
                });
                console.log(resp.headers.get("content-encoding") ?? "none");
                await resp.text();
                await server.shutdown();
                finished.resolve();
              },
            }, (_request) => {
              return new Response("a".repeat(1000), {
                headers: { "content-type": "text/plain" },
              });
            });
            await finished.promise;
            await server.finished;
          }

          await check({});
          await check({ automaticCompression: false });
          await check({ automaticCompression: true });
        `,
        ],
        env: { DENO_SERVE_AUTOMATIC_COMPRESSION: envValue },
        stdout: "piped",
        stderr: "inherit",
      }).output();

      assert(success);
      return new TextDecoder().decode(stdout).trim().split("\n");
    }

    assertEquals(await run("1"), [
      "gzip",
      "none",
      "gzip",
    ]);
    assertEquals(await run("true"), [
      "gzip",
      "none",
      "gzip",
    ]);
    assertEquals(await run("0"), [
      "none",
      "none",
      "gzip",
    ]);
    assertEquals(await run("False"), [
      "none",
      "none",
      "gzip",
    ]);
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerHttp2Compression() {
    const body = "a".repeat(1000);
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve({
      automaticCompression: true,
      handler: () => {
        return new Response(body, {
          headers: { "content-type": "text/plain" },
        });
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const client = http2.connect(`http://localhost:${servePort}`);
    try {
      const req = client.request({
        ":path": "/",
        "accept-encoding": "br",
      });
      let headers: http2.IncomingHttpHeaders | undefined;
      let compressedLength = 0;
      req.on("response", (responseHeaders) => {
        headers = responseHeaders;
      });
      req.on("data", (chunk) => {
        compressedLength += chunk.length;
      });

      req.end();
      await new Promise<void>((resolve, reject) => {
        req.on("end", resolve);
        req.on("error", reject);
        client.on("error", reject);
      });

      assert(headers);
      assertEquals(Number(headers[":status"]), 200);
      assertEquals(headers["content-encoding"], "br");
      assertEquals(headers["vary"], "Accept-Encoding");
      assertEquals(headers["content-length"], `${compressedLength}`);
      assert(compressedLength < body.length);
    } finally {
      client.close();
      ac.abort();
      await server.finished;
    }
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerHttp2AutomaticCompressionCanBeDisabled() {
    const body = "a".repeat(1000);
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve({
      automaticCompression: false,
      handler: () => {
        return new Response(body, {
          headers: { "content-type": "text/plain" },
        });
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const client = http2.connect(`http://localhost:${servePort}`);
    try {
      const req = client.request({
        ":path": "/",
        "accept-encoding": "br",
      });
      let headers: http2.IncomingHttpHeaders | undefined;
      let responseBody = "";
      req.setEncoding("utf8");
      req.on("response", (responseHeaders) => {
        headers = responseHeaders;
      });
      req.on("data", (chunk) => {
        responseBody += chunk;
      });

      req.end();
      await new Promise<void>((resolve, reject) => {
        req.on("end", resolve);
        req.on("error", reject);
        client.on("error", reject);
      });

      assert(headers);
      assertEquals(Number(headers[":status"]), 200);
      assertEquals(headers["content-encoding"], undefined);
      assertEquals(headers["vary"], undefined);
      assertEquals(headers["content-length"], `${body.length}`);
      assertEquals(responseBody, body);
    } finally {
      client.close();
      ac.abort();
      await server.finished;
    }
  },
);

// Regression test for #36046: cancelling an HTTP/2 stream while the handler is
// still pending used to panic in HttpRecord::recycle when the handler later
// committed a native (fast-path) response, because the record was no longer the
// sole strong reference. Requires the hyper HTTP/2 path, which is only used over
// TLS with ALPN h2.
Deno.test(
  { permissions: { net: true, read: true } },
  async function httpServerHttp2CancelBeforeNativeResponse() {
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();
    const cert = Deno.readTextFileSync("tests/testdata/tls/localhost.crt");
    const key = Deno.readTextFileSync("tests/testdata/tls/localhost.key");

    await using server = Deno.serve({
      handler: async () => {
        // Slow enough that the client cancels before we commit the response.
        await new Promise((r) => setTimeout(r, 50));
        // Native (fast-path) empty response reaches op_http_set_response_native.
        return new Response();
      },
      port: servePort,
      cert,
      key,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;

    const client = http2.connect(`https://localhost:${servePort}`, {
      ca: cert,
      rejectUnauthorized: false,
    });
    client.on("error", () => {});
    try {
      for (let i = 0; i < 50; i++) {
        const req = client.request({ ":path": "/", ":method": "GET" });
        req.on("error", () => {});
        req.end();
        // Cancel the stream while the handler is still pending.
        await new Promise((r) => setTimeout(r, 2 + (i % 5)));
        req.close(http2.constants.NGHTTP2_CANCEL);
      }

      // Give the pending handlers time to commit their (now orphaned)
      // responses; without the fix the server would have panicked by now.
      await new Promise((r) => setTimeout(r, 200));

      // The server must still be healthy and able to serve a normal request.
      const req = client.request({ ":path": "/", ":method": "GET" });
      let status: number | undefined;
      req.on("response", (headers) => {
        status = Number(headers[":status"]);
      });
      req.on("data", () => {});
      req.end();
      await new Promise<void>((resolve, reject) => {
        req.on("end", resolve);
        req.on("error", reject);
      });
      assertEquals(status, 200);
    } finally {
      client.close();
      ac.abort();
      await server.finished;
    }
  },
);

Deno.test(
  { permissions: { net: true, write: true, read: true } },
  async function httpServerPostFile() {
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve({
      handler: async (request) => {
        assertEquals(
          await request.bytes(),
          makeTempData(70 * 1024),
        );
        deferred.resolve();
        return new Response("ok");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const f = await makeTempFile(70 * 1024);
    const response = await fetch(`http://localhost:${servePort}/`, {
      method: "POST",
      body: f.readable,
    });

    await deferred.promise;

    assertEquals(response.status, 200);
    assertEquals(await response.text(), "ok");

    ac.abort();
    await server.finished;
  },
);

for (const delay of ["delay", "nodelay"]) {
  for (const url of ["text", "file", "stream"]) {
    // Ensure that we don't panic when the incoming TCP request was dropped
    // https://github.com/denoland/deno/issues/20315 and that we correctly
    // close/cancel the response
    Deno.test({
      permissions: { read: true, write: true, net: true },
      name: `httpServerTcpCancellation_${url}_${delay}`,
      fn: async function () {
        const ac = new AbortController();
        const streamCancelled = url == "stream"
          ? Promise.withResolvers<void>()
          : undefined;
        const listeningDeferred = Promise.withResolvers<void>();
        const waitForAbort = Promise.withResolvers<void>();
        const waitForRequest = Promise.withResolvers<void>();
        await using server = Deno.serve({
          port: servePort,
          signal: ac.signal,
          onListen: onListen(listeningDeferred.resolve),
          handler: async (req: Request) => {
            let respBody = null;
            if (req.url.includes("/text")) {
              respBody = "text";
            } else if (req.url.includes("/file")) {
              respBody = (await makeTempFile(1024)).readable;
            } else if (req.url.includes("/stream")) {
              respBody = new ReadableStream({
                start(controller) {
                  controller.enqueue(new Uint8Array([1]));
                },
                cancel(reason) {
                  streamCancelled!.resolve(reason);
                },
              });
            } else {
              fail();
            }
            waitForRequest.resolve();
            await waitForAbort.promise;

            if (delay == "delay") {
              await new Promise((r) => setTimeout(r, 1000));
            }
            // Allocate the request body
            req.body;
            return new Response(respBody);
          },
        });

        await listeningDeferred.promise;

        // Create a POST request and drop it once the server has received it
        const conn = await Deno.connect({ port: servePort });
        const writer = conn.writable.getWriter();
        await writer.write(
          new TextEncoder().encode(`POST /${url} HTTP/1.0\n\n`),
        );
        await waitForRequest.promise;
        await writer.close();

        waitForAbort.resolve();

        // Wait for cancellation before we shut the server down
        if (streamCancelled !== undefined) {
          await streamCancelled;
        }

        // Since the handler has a chance of creating resources or running async
        // ops, we need to use a graceful shutdown here to ensure they have fully
        // drained.
        await server.shutdown();

        await server.finished;
      },
    });
  }
}

// NOTE: This test will start failing when we disable "deno_http/legacy_abort" feature.
//
// Test for the internal implementation detail of cached request signals. Ensure that the request's
// signal is aborted if we try to access it after the request has been completed.
Deno.test(
  { permissions: { net: true } },
  async function httpServerSignalCancelled() {
    let stashedRequest;
    const { finished, abort } = await makeServer((req) => {
      // The cache signal is `undefined` because it has not been requested
      assertEquals(getCachedAbortSignal(req), undefined);
      stashedRequest = req;
      return new Response("ok");
    });
    await (await fetch(`http://localhost:${servePort}`)).text();
    abort();
    await finished;

    // `false` is a semaphore for a signal that should be aborted on creation
    assertEquals(getCachedAbortSignal(stashedRequest!), false);
    // Requesting the signal causes it to be materialized
    assert(stashedRequest!.signal.aborted);
    // The cached signal is now a full `AbortSignal`
    assertEquals(
      getCachedAbortSignal(stashedRequest!).constructor,
      AbortSignal,
    );
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerCancelFetch() {
    const request2 = Promise.withResolvers<void>();
    const request2Aborted = Promise.withResolvers<string>();
    let completed = 0;
    let aborted = 0;
    const { finished, abort } = await makeServer(async (req, context) => {
      context.completed.then(() => {
        console.log("completed");
        completed++;
      }).catch(() => {
        console.log("completed (error)");
        completed++;
      });
      req.signal.onabort = () => {
        console.log("aborted", req.url);
        aborted++;
      };
      if (req.url.endsWith("/1")) {
        const fetchRecursive = await fetch(`http://localhost:${servePort}/2`);
        return new Response(fetchRecursive.body);
      } else if (req.url.endsWith("/2")) {
        request2.resolve();
        return new Response(
          new ReadableStream({
            start(_controller) {/* just hang */},
            cancel(reason) {
              request2Aborted.resolve(reason);
            },
          }),
        );
      }
      fail();
    });
    const fetchAbort = new AbortController();
    const fetchPromise = await fetch(`http://localhost:${servePort}/1`, {
      signal: fetchAbort.signal,
    });
    await fetchPromise;
    await request2.promise;
    fetchAbort.abort();
    assertEquals("resource closed", await request2Aborted.promise);

    abort();
    await finished;
    assertEquals(completed, 2);
    assertEquals(aborted, 2);
  },
);

// Regression test for https://github.com/denoland/deno/issues/23537
Deno.test(
  { permissions: { read: true, net: true } },
  async function httpServerUndefinedCert() {
    const ac = new AbortController();
    const { promise, resolve } = Promise.withResolvers<void>();
    const hostname = "127.0.0.1";

    await using server = Deno.serve({
      handler: () => new Response("Hello World"),
      hostname,
      port: servePort,
      signal: ac.signal,
      onListen: onListen(resolve),
      onError: createOnErrorCb(ac),
      // Undefined should be equivalent to missing
      cert: undefined,
      key: undefined,
    });

    await promise;
    const resp = await fetch(`http://localhost:${servePort}/`);

    const respBody = await resp.text();
    assertEquals("Hello World", respBody);

    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { read: true, net: true } },
  async function httpServerWithTls() {
    const ac = new AbortController();
    const hostname = "127.0.0.1";
    const listeningDeferred = Promise.withResolvers<Deno.NetAddr>();

    await using server = Deno.serve({
      handler: () => new Response("Hello World"),
      hostname,
      port: servePort,
      signal: ac.signal,
      onListen: listeningDeferred.resolve,
      onError: createOnErrorCb(ac),
      cert: Deno.readTextFileSync("tests/testdata/tls/localhost.crt"),
      key: Deno.readTextFileSync("tests/testdata/tls/localhost.key"),
    });

    const addr = await listeningDeferred.promise;
    assertEquals(addr.hostname, server.addr.hostname);
    assertEquals(addr.port, server.addr.port);
    assertEquals(addr.transport, server.addr.transport);
    assertEquals(addr.transport, "tcp");
    const caCert = Deno.readTextFileSync("tests/testdata/tls/RootCA.pem");
    const client = Deno.createHttpClient({ caCerts: [caCert] });
    const resp = await fetch(`https://localhost:${servePort}/`, {
      client,
      headers: { "connection": "close" },
    });

    const respBody = await resp.text();
    assertEquals("Hello World", respBody);

    client.close();
    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true, write: true, read: true } },
  async function httpServerRequestCLTE() {
    const ac = new AbortController();
    const listeningDeferred = Promise.withResolvers<void>();

    await using server = Deno.serve({
      handler: () => {
        fail("CL.TE request should be rejected before dispatch");
        return new Response("ok");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();

    const body =
      `POST / HTTP/1.1\r\nHost: example.domain\r\nContent-Length: 13\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\nEXTRA`;
    const writeResult = await conn.write(encoder.encode(body));
    assertEquals(body.length, writeResult);
    const response = new Uint8Array(1024);
    const read = await conn.read(response);
    assert(read !== null);
    assertStringIncludes(
      new TextDecoder().decode(response.subarray(0, read)),
      "HTTP/1.1 400 Bad Request",
    );

    conn.close();

    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true, write: true, read: true } },
  async function httpServerRequestTETE() {
    const ac = new AbortController();
    const { promise, resolve } = Promise.withResolvers<void>();

    await using server = Deno.serve({
      handler: () => {
        throw new Error("oops");
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(resolve),
      onError: createOnErrorCb(ac),
    });

    const encoder = new TextEncoder();
    const decoder = new TextDecoder();

    const variations = [
      "Transfer-Encoding : chunked",
      "Transfer-Encoding: xchunked",
      "Transfer-Encoding: chunkedx",
      "Transfer-Encoding\n: chunked",
    ];

    await promise;
    for (const teHeader of variations) {
      const conn = await Deno.connect({ port: servePort });
      const body =
        `POST / HTTP/1.1\r\nHost: example.domain\r\n${teHeader}\r\n\r\n0\r\n\r\n`;
      const writeResult = await conn.write(encoder.encode(body));
      assertEquals(body.length, writeResult);

      const buf = new Uint8Array(1024);
      const readResult = await conn.read(buf);
      assert(readResult);
      const msg = decoder.decode(buf.subarray(0, readResult));
      assert(msg.includes("HTTP/1.1 400 Bad Request\r\n"));

      conn.close();
    }

    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServer204ResponseDoesntSendContentLength() {
    const { promise, resolve } = Promise.withResolvers<void>();
    const ac = new AbortController();
    await using server = Deno.serve({
      handler: (_request) => new Response(null, { status: 204 }),
      port: servePort,
      signal: ac.signal,
      onListen: onListen(resolve),
      onError: createOnErrorCb(ac),
    });

    try {
      await promise;
      const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
        method: "GET",
        headers: { "connection": "close" },
      });
      assertEquals(resp.status, 204);
      assertEquals(resp.headers.get("Content-Length"), null);
    } finally {
      ac.abort();
      await server.finished;
    }
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServer304ResponseDoesntSendBody() {
    const deferred = Promise.withResolvers<void>();
    const ac = new AbortController();
    const listeningDeferred = Promise.withResolvers<void>();

    await using server = Deno.serve({
      handler: () => {
        deferred.resolve();
        return new Response(null, {
          status: 304,
          headers: {
            "content-length": "100",
            "transfer-encoding": "chunked",
          },
        });
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();
    const decoder = new TextDecoder();

    const body =
      `GET / HTTP/1.1\r\nHost: example.domain\r\nConnection: close\r\n\r\n`;
    const writeResult = await conn.write(encoder.encode(body));
    assertEquals(body.length, writeResult);

    await deferred.promise;

    const buf = new Uint8Array(1024);
    const readResult = await conn.read(buf);
    assert(readResult);
    const msg = decoder.decode(buf.subarray(0, readResult));

    assert(msg.startsWith("HTTP/1.1 304 Not Modified"));
    assertEquals(msg.toLowerCase().includes("content-length:"), false);
    assertEquals(msg.toLowerCase().includes("transfer-encoding:"), false);
    assert(msg.endsWith("\r\n\r\n"));

    conn.close();

    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerExpectContinue() {
    const deferred = Promise.withResolvers<void>();
    const ac = new AbortController();
    const listeningDeferred = Promise.withResolvers<void>();

    await using server = Deno.serve({
      handler: async (req) => {
        deferred.resolve();
        assertEquals(await req.text(), "hello");
        return new Response(null, { status: 304 });
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();
    const decoder = new TextDecoder();

    {
      const body =
        `POST / HTTP/1.1\r\nHost: example.domain\r\nExpect: 100-continue\r\nContent-Length: 5\r\nConnection: close\r\n\r\n`;
      const writeResult = await conn.write(encoder.encode(body));
      assertEquals(body.length, writeResult);
    }

    await deferred.promise;

    {
      const msgExpected = "HTTP/1.1 100 Continue\r\n\r\n";
      const buf = new Uint8Array(encoder.encode(msgExpected).byteLength);
      const readResult = await conn.read(buf);
      assert(readResult);
      const msg = decoder.decode(buf.subarray(0, readResult));
      assert(msg.startsWith(msgExpected));
    }

    {
      const body = "hello";
      const writeResult = await conn.write(encoder.encode(body));
      assertEquals(body.length, writeResult);
    }

    const buf = new Uint8Array(1024);
    const readResult = await conn.read(buf);
    assert(readResult);
    const msg = decoder.decode(buf.subarray(0, readResult));

    assert(msg.startsWith("HTTP/1.1 304 Not Modified"));
    conn.close();

    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerPipesLargeContentLengthRequestBody() {
    const bodyLength = 128 * 1024;
    const body = new Uint8Array(bodyLength);
    body.fill(0x61);
    const received = Promise.withResolvers<number>();

    await using server = await makeServer(async (req) => {
      assert(req.body);
      let total = 0;
      let chunks = 0;
      await req.body.pipeTo(
        new WritableStream<Uint8Array>({
          write(chunk) {
            chunks++;
            total += chunk.byteLength;
          },
        }),
      );
      assert(chunks <= 4);
      received.resolve(total);
      return new Response(null, { status: 204 });
    });

    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();
    const decoder = new TextDecoder();
    await writeAll(
      conn,
      encoder.encode(
        `POST / HTTP/1.1\r\nHost: 127.0.0.1:${servePort}\r\n` +
          `Content-Length: ${bodyLength}\r\n` +
          `Content-Type: application/octet-stream\r\n` +
          `Expect: 100-continue\r\nConnection: close\r\n\r\n`,
      ),
    );

    const continueBytes = await readAtLeast(
      conn,
      "HTTP/1.1 100 Continue\r\n\r\n".length,
    );
    assertEquals(
      decoder.decode(continueBytes),
      "HTTP/1.1 100 Continue\r\n\r\n",
    );

    await writeAll(conn, body);
    assertEquals(await received.promise, bodyLength);

    const response = new Uint8Array(1024);
    const read = await conn.read(response);
    assert(read);
    assertStringIncludes(
      decoder.decode(response.subarray(0, read)),
      "HTTP/1.1 204 No Content",
    );
    conn.close();
    await server.shutdown();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerExpectContinueButNoBodyLOL() {
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve({
      handler: async (req) => {
        deferred.resolve();
        assertEquals(await req.text(), "");
        return new Response(null, { status: 304 });
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const conn = await Deno.connect({ port: servePort });
    const encoder = new TextEncoder();
    const decoder = new TextDecoder();

    {
      // // no content-length or transfer-encoding means no body!
      const body =
        `POST / HTTP/1.1\r\nHost: example.domain\r\nExpect: 100-continue\r\nConnection: close\r\n\r\n`;
      const writeResult = await conn.write(encoder.encode(body));
      assertEquals(body.length, writeResult);
    }

    await deferred.promise;

    const buf = new Uint8Array(1024);
    const readResult = await conn.read(buf);
    assert(readResult);
    const msg = decoder.decode(buf.subarray(0, readResult));

    assert(msg.startsWith("HTTP/1.1 304 Not Modified"));
    conn.close();

    ac.abort();
    await server.finished;
  },
);

const badRequests = [
  ["weirdMethodName", "GE T / HTTP/1.1\r\n\r\n"],
  ["illegalRequestLength", "POST / HTTP/1.1\r\nContent-Length: foo\r\n\r\n"],
  ["illegalRequestLength2", "POST / HTTP/1.1\r\nContent-Length: -1\r\n\r\n"],
  ["illegalRequestLength3", "POST / HTTP/1.1\r\nContent-Length: 1.1\r\n\r\n"],
  ["illegalRequestLength4", "POST / HTTP/1.1\r\nContent-Length: 1.\r\n\r\n"],
];

for (const [name, req] of badRequests) {
  const testFn = {
    [name]: async () => {
      const ac = new AbortController();
      const { promise, resolve } = Promise.withResolvers<void>();

      await using server = Deno.serve({
        handler: () => {
          throw new Error("oops");
        },
        port: servePort,
        signal: ac.signal,
        onListen: onListen(resolve),
        onError: createOnErrorCb(ac),
      });

      await promise;
      const conn = await Deno.connect({ port: servePort });
      const encoder = new TextEncoder();
      const decoder = new TextDecoder();

      {
        const writeResult = await conn.write(encoder.encode(req));
        assertEquals(req.length, writeResult);
      }

      const buf = new Uint8Array(100);
      const readResult = await conn.read(buf);
      assert(readResult);
      const msg = decoder.decode(buf.subarray(0, readResult));

      assert(msg.startsWith("HTTP/1.1 400 "));
      conn.close();

      ac.abort();
      await server.finished;
    },
  }[name];

  Deno.test(
    { permissions: { net: true } },
    testFn,
  );
}

async function writeAll(conn: Deno.Conn, bytes: Uint8Array): Promise<void> {
  let written = 0;
  while (written < bytes.length) {
    written += await conn.write(bytes.subarray(written));
  }
}

function http2LiteralHeader(name: string, value: string): number[] {
  const encoder = new TextEncoder();
  const encodedName = encoder.encode(name);
  const encodedValue = encoder.encode(value);
  return [
    0x00,
    encodedName.length,
    ...encodedName,
    encodedValue.length,
    ...encodedValue,
  ];
}

function http2Frame(
  type: number,
  flags: number,
  streamId: number,
  payload: number[] | Uint8Array,
): Uint8Array {
  const length = payload.length;
  return new Uint8Array([
    (length >> 16) & 0xff,
    (length >> 8) & 0xff,
    length & 0xff,
    type,
    flags,
    (streamId >> 24) & 0x7f,
    (streamId >> 16) & 0xff,
    (streamId >> 8) & 0xff,
    streamId & 0xff,
    ...payload,
  ]);
}

async function writeHttp2ClientPreface(conn: Deno.Conn): Promise<void> {
  const encoder = new TextEncoder();
  await writeAll(conn, encoder.encode("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"));
  await writeAll(conn, http2Frame(0x04, 0, 0, [])); // SETTINGS
}

async function writeHttp2Headers(
  conn: Deno.Conn,
  headers: [string, string][],
  endStream: boolean,
): Promise<void> {
  const block = headers.flatMap(([name, value]) =>
    http2LiteralHeader(name, value)
  );
  await writeAll(
    conn,
    http2Frame(
      0x01,
      endStream ? 0x05 : 0x04, // END_STREAM? | END_HEADERS
      1,
      block,
    ),
  );
}

async function writeHttp2Data(
  conn: Deno.Conn,
  bytes: Uint8Array,
  endStream: boolean,
): Promise<void> {
  await writeAll(conn, http2Frame(0x00, endStream ? 0x01 : 0, 1, bytes));
}

async function readAtLeast(
  conn: Deno.Conn,
  minLength: number,
): Promise<Uint8Array> {
  const out = new Uint8Array(minLength);
  let read = 0;
  while (read < minLength) {
    const n = await conn.read(out.subarray(read));
    assert(n);
    read += n;
  }
  return out;
}

async function assertRawRequestBadRequest(request: string): Promise<void> {
  const ac = new AbortController();
  const { promise, resolve } = Promise.withResolvers<void>();

  await using server = Deno.serve({
    handler: () => {
      fail("invalid request should be rejected before dispatch");
      return new Response("ok");
    },
    port: servePort,
    signal: ac.signal,
    onListen: onListen(resolve),
    onError: createOnErrorCb(ac),
  });

  await promise;
  const conn = await Deno.connect({ port: servePort });
  const encoder = new TextEncoder();
  const decoder = new TextDecoder();

  await writeAll(conn, encoder.encode(request));

  const buf = new Uint8Array(128);
  const readResult = await conn.read(buf);
  assert(readResult);
  const msg = decoder.decode(buf.subarray(0, readResult));
  assert(msg.startsWith("HTTP/1.1 400 "), msg);
  conn.close();

  ac.abort();
  await server.finished;
}

Deno.test(
  { permissions: { net: true } },
  async function httpServerRejectsOversizedRequestHead() {
    await assertRawRequestBadRequest(
      `GET / HTTP/1.1\r\nHost: example.domain\r\nX-Fill: ${
        "a".repeat(64 * 1024)
      }\r\n\r\n`,
    );
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerRejectsTooManyRequestHeaders() {
    const headers = Array.from(
      { length: 129 },
      (_, index) => `X-${index}: a\r\n`,
    ).join("");
    await assertRawRequestBadRequest(
      `GET / HTTP/1.1\r\nHost: example.domain\r\n${headers}\r\n`,
    );
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerConcurrentRequests() {
    const ac = new AbortController();
    const { resolve } = Promise.withResolvers<void>();

    let reqCount = -1;
    let timerId: NodeJS.Timeout | undefined;
    await using server = Deno.serve({
      handler: (_req) => {
        reqCount++;
        if (reqCount === 0) {
          const msg = new TextEncoder().encode("data: hello\r\n\r\n");
          // SSE
          const body = new ReadableStream({
            start(controller) {
              timerId = setInterval(() => {
                controller.enqueue(msg);
              }, 1000);
            },
            cancel() {
              if (timerId !== undefined) {
                clearInterval(timerId);
              }
            },
          });
          return new Response(body, {
            headers: {
              "Content-Type": "text/event-stream",
            },
          });
        }

        return new Response(`hello ${reqCount}`);
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(resolve),
      onError: createOnErrorCb(ac),
    });

    const sseRequest = await fetch(`http://localhost:${servePort}/`);

    const decoder = new TextDecoder();
    const stream = sseRequest.body!.getReader();
    {
      const { done, value } = await stream.read();
      assert(!done);
      assertEquals(decoder.decode(value), "data: hello\r\n\r\n");
    }

    const helloRequest = await fetch(`http://localhost:${servePort}/`);
    assertEquals(helloRequest.status, 200);
    assertEquals(await helloRequest.text(), "hello 1");

    {
      const { done, value } = await stream.read();
      assert(!done);
      assertEquals(decoder.decode(value), "data: hello\r\n\r\n");
    }

    await stream.cancel();
    clearInterval(timerId);
    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function serveWithPrototypePollution() {
    const originalThen = Promise.prototype.then;
    const originalSymbolIterator = Array.prototype[Symbol.iterator];
    try {
      Promise.prototype.then = Array.prototype[Symbol.iterator] = () => {
        throw new Error();
      };
      const ac = new AbortController();
      const { resolve } = Promise.withResolvers<void>();
      const server = Deno.serve({
        handler: (_req) => new Response("ok"),
        hostname: "localhost",
        port: servePort,
        signal: ac.signal,
        onListen: onListen(resolve),
        onError: createOnErrorCb(ac),
      });
      ac.abort();
      await server.finished;
    } finally {
      Promise.prototype.then = originalThen;
      Array.prototype[Symbol.iterator] = originalSymbolIterator;
    }
  },
);

// https://github.com/denoland/deno/issues/15549
Deno.test(
  { permissions: { net: true } },
  async function testIssue15549() {
    const ac = new AbortController();
    const { promise, resolve } = Promise.withResolvers<void>();
    let count = 0;
    await using server = Deno.serve({
      async onListen({ port }: { port: number }) {
        const res1 = await fetch(`http://localhost:${port}/`);
        assertEquals(await res1.text(), "hello world 1");

        const res2 = await fetch(`http://localhost:${port}/`);
        assertEquals(await res2.text(), "hello world 2");

        resolve();
        ac.abort();
      },
      signal: ac.signal,
    }, () => {
      count++;
      return new Response(`hello world ${count}`);
    });

    await promise;
    await server.finished;
  },
);

// https://github.com/denoland/deno/issues/15858
Deno.test(
  "Clone should work",
  { permissions: { net: true } },
  async function httpServerCanCloneRequest() {
    const ac = new AbortController();
    const { promise, resolve } = Promise.withResolvers<number>();

    await using server = Deno.serve({
      handler: async (req) => {
        const cloned = req.clone();
        assertEquals(req.headers, cloned.headers);

        assertEquals(cloned.url, req.url);
        assertEquals(cloned.cache, req.cache);
        assertEquals(cloned.destination, req.destination);
        assertEquals(cloned.headers, req.headers);
        assertEquals(cloned.integrity, req.integrity);
        assertEquals(cloned.isHistoryNavigation, req.isHistoryNavigation);
        assertEquals(cloned.isReloadNavigation, req.isReloadNavigation);
        assertEquals(cloned.keepalive, req.keepalive);
        assertEquals(cloned.method, req.method);
        assertEquals(cloned.mode, req.mode);
        assertEquals(cloned.redirect, req.redirect);
        assertEquals(cloned.referrer, req.referrer);
        assertEquals(cloned.referrerPolicy, req.referrerPolicy);

        // both requests can read body
        await req.text();
        await cloned.json();

        return new Response("ok");
      },
      signal: ac.signal,
      onListen: ({ port }: { port: number }) => resolve(port),
      onError: createOnErrorCb(ac),
    });

    try {
      const port = await promise;
      const resp = await fetch(`http://localhost:${port}/`, {
        headers: { connection: "close" },
        method: "POST",
        body: '{"sus":true}',
      });
      const text = await resp.text();
      assertEquals(text, "ok");
    } finally {
      ac.abort();
      await server.finished;
    }
  },
);

// https://fetch.spec.whatwg.org/#dom-request-clone
Deno.test(
  "Throw if disturbed",
  { permissions: { net: true } },
  async function shouldThrowIfBodyIsUnusableDisturbed() {
    const ac = new AbortController();
    const { promise, resolve } = Promise.withResolvers<number>();

    await using server = Deno.serve({
      handler: async (req) => {
        await req.text();

        try {
          req.clone();
          fail();
        } catch (cloneError) {
          assert(cloneError instanceof TypeError);
          assert(
            cloneError.message.endsWith("Body is unusable"),
          );

          ac.abort();
          await server.finished;
        }

        return new Response("ok");
      },
      signal: ac.signal,
      onListen: ({ port }: { port: number }) => resolve(port),
    });

    try {
      const port = await promise;
      await fetch(`http://localhost:${port}/`, {
        headers: { connection: "close" },
        method: "POST",
        body: '{"bar":true}',
      });
      fail();
    } catch (clientError) {
      assert(clientError instanceof TypeError);
      assert(clientError.message === "fetch failed");
      assert(clientError.cause instanceof Error);
      assert(clientError.cause.message.includes("client error"));
    } finally {
      ac.abort();
      await server.finished;
    }
  },
);

// https://fetch.spec.whatwg.org/#dom-request-clone
Deno.test({
  name: "Throw if locked",
  permissions: { net: true },
  fn: async function shouldThrowIfBodyIsUnusableLocked() {
    const ac = new AbortController();
    const { promise, resolve } = Promise.withResolvers<number>();

    await using server = Deno.serve({
      handler: async (req) => {
        const _reader = req.body?.getReader();

        try {
          req.clone();
          fail();
        } catch (cloneError) {
          assert(cloneError instanceof TypeError);
          assert(
            cloneError.message.endsWith("Body is unusable"),
          );

          ac.abort();
          await server.finished;
        }
        return new Response("ok");
      },
      signal: ac.signal,
      onListen: ({ port }: { port: number }) => resolve(port),
    });

    try {
      const port = await promise;
      await fetch(`http://localhost:${port}/`, {
        headers: { connection: "close" },
        method: "POST",
        body: '{"bar":true}',
      });
      fail();
    } catch (clientError) {
      assert(clientError instanceof TypeError);
      assert(clientError.message === "fetch failed");
      assert(clientError.cause instanceof Error);
      assert(clientError.cause.message.includes("client error"));
    } finally {
      ac.abort();
      await server.finished;
    }
  },
});

// Checks large streaming response
// https://github.com/denoland/deno/issues/16567
Deno.test(
  { permissions: { net: true } },
  async function testIssue16567() {
    const ac = new AbortController();
    const { promise, resolve } = Promise.withResolvers<void>();
    await using server = Deno.serve({
      async onListen({ port }) {
        const res1 = await fetch(`http://localhost:${port}/`);
        assertEquals((await res1.text()).length, 40 * 50_000);

        resolve();
        ac.abort();
      },
      signal: ac.signal,
    }, () =>
      new Response(
        new ReadableStream({
          start(c) {
            // 2MB "a...a" response with 40 chunks
            for (const _ of Array(40)) {
              c.enqueue(new Uint8Array(50_000).fill(97));
            }
            c.close();
          },
        }),
      ));

    await promise;
    await server.finished;
  },
);

function chunkedBodyReader(h: Headers, r: BufReader): Reader {
  // Based on https://tools.ietf.org/html/rfc2616#section-19.4.6
  const tp = new TextProtoReader(r);
  let finished = false;
  const chunks: Array<{
    offset: number;
    data: Uint8Array;
  }> = [];
  async function read(buf: Uint8Array): Promise<number | null> {
    if (finished) return null;
    const [chunk] = chunks;
    if (chunk) {
      const chunkRemaining = chunk.data.byteLength - chunk.offset;
      const readLength = Math.min(chunkRemaining, buf.byteLength);
      for (let i = 0; i < readLength; i++) {
        buf[i] = chunk.data[chunk.offset + i];
      }
      chunk.offset += readLength;
      if (chunk.offset === chunk.data.byteLength) {
        chunks.shift();
        // Consume \r\n;
        if ((await tp.readLine()) === null) {
          throw new Deno.errors.UnexpectedEof();
        }
      }
      return readLength;
    }
    const line = await tp.readLine();
    if (line === null) throw new Deno.errors.UnexpectedEof();
    // TODO(bartlomieju): handle chunk extension
    const [chunkSizeString] = line.split(";");
    const chunkSize = parseInt(chunkSizeString, 16);
    if (Number.isNaN(chunkSize) || chunkSize < 0) {
      throw new Deno.errors.InvalidData("Invalid chunk size");
    }
    if (chunkSize > 0) {
      if (chunkSize > buf.byteLength) {
        let eof = await r.readFull(buf);
        if (eof === null) {
          throw new Deno.errors.UnexpectedEof();
        }
        const restChunk = new Uint8Array(chunkSize - buf.byteLength);
        eof = await r.readFull(restChunk);
        if (eof === null) {
          throw new Deno.errors.UnexpectedEof();
        } else {
          chunks.push({
            offset: 0,
            data: restChunk,
          });
        }
        return buf.byteLength;
      } else {
        const bufToFill = buf.subarray(0, chunkSize);
        const eof = await r.readFull(bufToFill);
        if (eof === null) {
          throw new Deno.errors.UnexpectedEof();
        }
        // Consume \r\n
        if ((await tp.readLine()) === null) {
          throw new Deno.errors.UnexpectedEof();
        }
        return chunkSize;
      }
    } else {
      assert(chunkSize === 0);
      // Consume \r\n
      if ((await r.readLine()) === null) {
        throw new Deno.errors.UnexpectedEof();
      }
      await readTrailers(h, r);
      finished = true;
      return null;
    }
  }
  return { read };
}

async function readTrailers(
  headers: Headers,
  r: BufReader,
) {
  const trailers = parseTrailer(headers.get("trailer"));
  if (trailers == null) return;
  const trailerNames = [...trailers.keys()];
  const tp = new TextProtoReader(r);
  const result = await tp.readMimeHeader();
  if (result == null) {
    throw new Deno.errors.InvalidData("Missing trailer header");
  }
  const undeclared = [...result.keys()].filter(
    (k) => !trailerNames.includes(k),
  );
  if (undeclared.length > 0) {
    throw new Deno.errors.InvalidData(
      `Undeclared trailers: ${Deno.inspect(undeclared)}.`,
    );
  }
  for (const [k, v] of result) {
    headers.append(k, v);
  }
  const missingTrailers = trailerNames.filter((k) => !result.has(k));
  if (missingTrailers.length > 0) {
    throw new Deno.errors.InvalidData(
      `Missing trailers: ${Deno.inspect(missingTrailers)}.`,
    );
  }
  headers.delete("trailer");
}

function parseTrailer(field: string | null): Headers | undefined {
  if (field == null) {
    return undefined;
  }
  const trailerNames = field.split(",").map((v) => v.trim().toLowerCase());
  if (trailerNames.length === 0) {
    throw new Deno.errors.InvalidData("Empty trailer header");
  }
  const prohibited = trailerNames.filter((k) => isProhibitedForTrailer(k));
  if (prohibited.length > 0) {
    throw new Deno.errors.InvalidData(
      `Prohibited trailer names: ${Deno.inspect(prohibited)}.`,
    );
  }
  return new Headers(trailerNames.map((key) => [key, ""]));
}

function isProhibitedForTrailer(key: string): boolean {
  const s = new Set(["transfer-encoding", "content-length", "trailer"]);
  return s.has(key.toLowerCase());
}

// TODO(mmastrac): curl on Windows CI stopped supporting --http2?
Deno.test(
  {
    permissions: { net: true, run: true },
    ignore: Deno.build.os === "windows",
  },
  async function httpServeCurlH2C() {
    const ac = new AbortController();
    await using server = Deno.serve(
      { port: servePort, signal: ac.signal },
      () => new Response("hello world!"),
    );

    assertEquals(
      "hello world!",
      await curlRequest([`http://localhost:${servePort}/path`]),
    );
    assertEquals(
      "hello world!",
      await curlRequest([`http://localhost:${servePort}/path`, "--http2"]),
    );
    assertEquals(
      "hello world!",
      await curlRequest([
        `http://localhost:${servePort}/path`,
        "--http2",
        "--http2-prior-knowledge",
      ]),
    );

    ac.abort();
    await server.finished;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServeRawHttp2PrefaceAutodetect() {
    const ac = new AbortController();
    const { promise, resolve } = Promise.withResolvers<void>();
    await using server = Deno.serve(
      { port: servePort, signal: ac.signal, onListen: onListen(resolve) },
      () => {
        fail("HTTP/2 preface should not be dispatched as an HTTP/1 request");
        return new Response("unexpected");
      },
    );
    await promise;

    const conn = await Deno.connect({ port: servePort });
    const preface = new TextEncoder().encode(
      "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n",
    );
    const emptySettingsFrame = new Uint8Array([
      0,
      0,
      0,
      4,
      0,
      0,
      0,
      0,
      0,
    ]);
    await writeAll(conn, preface);
    await writeAll(conn, emptySettingsFrame);

    const frameHeader = await readAtLeast(conn, 9);
    assertEquals(frameHeader[3], 4); // SETTINGS
    assertEquals(frameHeader[5], 0);
    assertEquals(frameHeader[6], 0);
    assertEquals(frameHeader[7], 0);
    assertEquals(frameHeader[8], 0);

    conn.close();
    ac.abort();
    await server.finished;
  },
);

// TODO(mmastrac): This test should eventually use fetch, when we support trailers there.
// This test is ignored because it's flaky and relies on cURL's verbose output.
Deno.test(
  { permissions: { net: true, run: true, read: true }, ignore: true },
  async function httpServerTrailers() {
    const ac = new AbortController();
    const { resolve } = Promise.withResolvers<void>();

    await using server = Deno.serve({
      handler: () => {
        const response = new Response("Hello World", {
          headers: {
            "trailer": "baz",
            "transfer-encoding": "chunked",
            "foo": "bar",
          },
        });
        addTrailers(response, [["baz", "why"]]);
        return response;
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(resolve),
      onError: createOnErrorCb(ac),
    });

    // We don't have a great way to access this right now, so just fetch the trailers with cURL
    const [_, stderr] = await curlRequestWithStdErr([
      `http://localhost:${servePort}/path`,
      "-v",
      "--http2",
      "--http2-prior-knowledge",
    ]);
    assertMatch(stderr, /baz: why/);
    ac.abort();
    await server.finished;
  },
);

// TODO(mmastrac): curl on CI stopped supporting --http2?
Deno.test(
  {
    permissions: {
      net: true,
      run: true,
      read: true,
    },
    ignore: Deno.build.os === "windows",
  },
  async function httpsServeCurlH2C() {
    const ac = new AbortController();
    await using server = Deno.serve(
      {
        signal: ac.signal,
        port: servePort,
        cert: Deno.readTextFileSync("tests/testdata/tls/localhost.crt"),
        key: Deno.readTextFileSync("tests/testdata/tls/localhost.key"),
      },
      () => new Response("hello world!"),
    );

    assertEquals(
      "hello world!",
      await curlRequest([`https://localhost:${servePort}/path`, "-k"]),
    );
    assertEquals(
      "hello world!",
      await curlRequest([
        `https://localhost:${servePort}/path`,
        "-k",
        "--http2",
      ]),
    );
    assertEquals(
      "hello world!",
      await curlRequest([
        `https://localhost:${servePort}/path`,
        "-k",
        "--http2",
        "--http2-prior-knowledge",
      ]),
    );

    ac.abort();
    await server.finished;
  },
);

Deno.test("Deno.HttpServer is not thenable", async () => {
  // deno-lint-ignore require-await
  async function serveTest() {
    const server = Deno.serve({ port: servePort }, (_) => new Response(""));
    assert(!("then" in server));
    return server;
  }
  const server = await serveTest();
  await server.shutdown();
});

Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: { run: true, read: true, write: true, net: true },
  },
  async function httpServerUnixDomainSocket() {
    const { promise, resolve } = Promise.withResolvers<Deno.UnixAddr>();
    const ac = new AbortController();
    const filePath = tmpUnixSocketPath();
    await using server = Deno.serve(
      {
        signal: ac.signal,
        path: filePath,
        onListen(info) {
          resolve(info);
        },
        onError: createOnErrorCb(ac),
      },
      (_req, { remoteAddr }) => {
        assertEquals(remoteAddr, { path: filePath, transport: "unix" });
        return new Response("hello world!");
      },
    );

    assertEquals((await promise).path, filePath);
    assertEquals(
      "hello world!",
      await curlRequest(["--unix-socket", filePath, "http://localhost"]),
    );
    ac.abort();
    await server.finished;
  },
);

Deno.test(
  {
    ignore: Deno.build.os !== "linux",
    permissions: { run: true, net: true },
  },
  async function httpServerVsockSocket() {
    const { promise, resolve } = Promise.withResolvers<Deno.VsockAddr>();
    const ac = new AbortController();
    await using server = Deno.serve(
      {
        signal: ac.signal,
        cid: -1,
        port: 8000,
        onListen(info) {
          resolve(info);
        },
        onError: createOnErrorCb(ac),
      },
      (_req, { remoteAddr }) => {
        assertEquals(remoteAddr.transport, "vsock");
        assertEquals(remoteAddr.cid, 1);
        assertEquals(remoteAddr.port, conn.localAddr.port);
        return new Response("hello world!");
      },
    );

    assertEquals((await promise).cid, 4294967295);
    assertEquals((await promise).port, 8000);

    const conn = await Deno.connect({
      transport: "vsock",
      cid: 1,
      port: 8000,
    });
    await conn.write(
      new TextEncoder().encode("GET / HTTP/1.1\r\nhost: example.com\r\n\r\n"),
    );
    const data = new Uint8Array(512);
    const n = await conn.read(data);
    const body = new TextDecoder().decode(data.subarray(0, n!));

    assertEquals(
      "hello world!",
      body.split("\r\n").at(-1),
    );

    await conn.close();
    ac.abort();
    await server.finished;
  },
);

// serve Handler must return Response class or promise that resolves Response class
Deno.test(
  { permissions: { net: true, run: true } },
  async function handleServeCallbackReturn() {
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve(
      {
        port: servePort,
        onListen: onListen(listeningDeferred.resolve),
        signal: ac.signal,
        onError: (error) => {
          assert(error instanceof TypeError);
          assert(
            error.message ===
              "Return value from serve handler must be a response or a promise resolving to a response",
          );
          deferred.resolve();
          return new Response("Customized Internal Error from onError");
        },
      },
      () => {
        // Trick the typechecker
        return <Response> <unknown> undefined;
      },
    );
    await listeningDeferred.promise;
    const respText = await curlRequest([`http://localhost:${servePort}`]);
    await deferred.promise;
    ac.abort();
    await server.finished;
    assert(respText === "Customized Internal Error from onError");
  },
);

// Handler returning a Response-like object (prototype matches but no internal
// slot — e.g. a subclass that skipped super(), or a Response from a different
// realm/polyfill) must not crash the serve loop. This was the crash reported
// in https://github.com/denoland/deno/issues/33893 for nitro+vite, where the
// loop tore down with "Cannot read properties of undefined (reading 'status')"
// inside `mapped` in `ext:deno_http/00_serve.ts`.
function makeResponseLike(): Response {
  const fake = Object.create(Response.prototype);
  // Own-data properties for getters Deno.serve probes during validation,
  // so the inherited (branded) accessors don't run.
  Object.defineProperty(fake, "type", { value: "default" });
  Object.defineProperty(fake, "bodyUsed", { value: false });
  return fake as Response;
}

Deno.test(
  { permissions: { net: true, run: true } },
  async function handleServeCallbackReturnsResponseLike() {
    const deferred = Promise.withResolvers<void>();
    const listeningDeferred = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve(
      {
        port: servePort,
        onListen: onListen(listeningDeferred.resolve),
        signal: ac.signal,
        onError: (error) => {
          assert(error instanceof TypeError);
          assert(
            error.message.includes("must be a Response") &&
              error.message.includes("constructor in this realm"),
          );
          deferred.resolve();
          return new Response("recovered", { status: 200 });
        },
      },
      () => makeResponseLike(),
    );
    await listeningDeferred.promise;
    const respText = await curlRequest([`http://localhost:${servePort}`]);
    await deferred.promise;
    ac.abort();
    await server.finished;
    assert(respText === "recovered");
  },
);

// onError handler returning a Response-like object must also not crash the
// serve loop — it should fall through to the default 500 instead.
Deno.test(
  { permissions: { net: true, run: true } },
  async function handleServeErrorCallbackReturnsResponseLike() {
    const { promise, resolve } = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve(
      {
        port: servePort,
        onListen: onListen(resolve),
        signal: ac.signal,
        onError: () => makeResponseLike(),
      },
      () => {
        throw new Error("boom");
      },
    );
    await promise;
    const respText = await curlRequest([`http://localhost:${servePort}`]);
    ac.abort();
    await server.finished;
    assert(respText === "Internal Server Error");
  },
);

// onError Handler must return Response class or promise that resolves Response class
Deno.test(
  { permissions: { net: true, run: true } },
  async function handleServeErrorCallbackReturn() {
    const { promise, resolve } = Promise.withResolvers<void>();
    const ac = new AbortController();

    await using server = Deno.serve(
      {
        port: servePort,
        onListen: onListen(resolve),
        signal: ac.signal,
        onError: () => {
          // Trick the typechecker
          return <Response> <unknown> undefined;
        },
      },
      () => {
        // Trick the typechecker
        return <Response> <unknown> undefined;
      },
    );
    await promise;
    const respText = await curlRequest([`http://localhost:${servePort}`]);
    ac.abort();
    await server.finished;
    assert(respText === "Internal Server Error");
  },
);

Deno.test(
  {
    permissions: { net: true, run: true, read: true },
    ignore: Deno.build.os !== "linux",
  },
  async function gzipFlushResponseStream() {
    const { promise, resolve } = Promise.withResolvers<void>();
    const ac = new AbortController();

    console.log("Starting server", servePort);
    let timer: NodeJS.Timeout | undefined = undefined;
    let _controller;

    await using server = Deno.serve(
      {
        port: servePort,
        onListen: onListen(resolve),
        signal: ac.signal,
      },
      () => {
        const body = new ReadableStream({
          start(controller) {
            timer = setInterval(() => {
              const message = `It is ${new Date().toISOString()}\n`;
              controller.enqueue(new TextEncoder().encode(message));
            }, 1000);
            _controller = controller;
          },
          cancel() {
            if (timer !== undefined) {
              clearInterval(timer);
            }
          },
        });
        return new Response(body, {
          headers: {
            "content-type": "text/plain",
            "x-content-type-options": "nosniff",
          },
        });
      },
    );
    await promise;
    const e = await execCode3("/usr/bin/sh", [
      "-c",
      `curl --stderr - -N --compressed --no-progress-meter http://localhost:${servePort}`,
    ]);
    await e.waitStdoutText("It is ");
    clearTimeout(timer);
    _controller!.close();
    await e.finished();
    ac.abort();
    await server.finished;
  },
);

Deno.test({
  name: "HTTP Server test (error on non-unix platform)",
  ignore: Deno.build.os !== "windows",
}, async () => {
  await assertRejects(
    async () => {
      const ac = new AbortController();
      await using server = Deno.serve({
        path: "path/to/socket",
        handler: (_req) => new Response("Hello, world"),
        signal: ac.signal,
        onListen({ path: _path }) {
          console.log(`Server started at ${_path}`);
        },
      });
      server.finished.then(() => console.log("Server closed"));
      console.log("Closing server...");
      ac.abort();
      await new Promise((resolve) => setTimeout(resolve, 100)); // Example of awaiting something
    },
    Error,
    'Operation `"op_net_listen_unix"` not supported on non-unix platforms.',
  );
});

Deno.test({
  name: "onListen callback gets 0.0.0.0 hostname as is",
}, async () => {
  const { promise, resolve } = Promise.withResolvers<{ hostname: string }>();

  await using server = Deno.serve({
    handler: (_) => new Response("ok"),
    hostname: "0.0.0.0",
    port: 0,
    onListen: resolve,
  });
  const { hostname } = await promise;
  assertEquals(hostname, "0.0.0.0");
  await server.shutdown();
});

Deno.test({
  name: "AbortSignal aborted when request is cancelled",
}, async () => {
  const { promise, resolve } = Promise.withResolvers<void>();

  let cancelled = false;

  await using server = Deno.serve({
    hostname: "0.0.0.0",
    port: servePort,
    onListen: () => resolve(),
  }, async (request) => {
    request.signal.addEventListener("abort", () => cancelled = true);
    assert(!request.signal.aborted);
    await new Promise((resolve) => setTimeout(resolve, 3000)); // abort during waiting
    assert(request.signal.aborted);
    return new Response("Ok");
  });

  await promise;
  await fetch(`http://localhost:${servePort}/`, {
    signal: AbortSignal.timeout(1000),
  }).catch(() => {});

  await server.shutdown();

  assert(cancelled);
});

Deno.test({
  name: "AbortSignal event aborted when request is cancelled",
}, async () => {
  const { promise, resolve } = Promise.withResolvers<void>();

  await using server = Deno.serve({
    hostname: "0.0.0.0",
    port: servePort,
    onListen: () => resolve(),
  }, async (request) => {
    const { promise: promiseAbort, resolve: resolveAbort } = Promise
      .withResolvers<void>();
    request.signal.addEventListener("abort", () => resolveAbort());
    assert(!request.signal.aborted);

    await promiseAbort;

    return new Response("Ok");
  });

  await promise;
  await fetch(`http://localhost:${servePort}/`, {
    signal: AbortSignal.timeout(100),
  }).catch(() => {});

  await server.shutdown();
});

Deno.test({
  name: "support shutdown while open idle connection exists",
}, async () => {
  const { promise, resolve } = Promise.withResolvers<void>();

  await using server = Deno.serve({
    hostname: "0.0.0.0",
    port: servePort,
    onListen: () => resolve(),
  }, () => new Response("Ok"));

  await promise;

  using conn = await Deno.connect({ port: servePort });
  await delay(0);

  await server.shutdown();

  const read = await conn.read(new Uint8Array(10));
  assertEquals(read, null);
});

// https://github.com/denoland/deno/issues/27083
Deno.test(
  { permissions: { net: true } },
  async function httpServerWebSocketInspectRequest() {
    const ac = new AbortController();
    const listeningDeferred = Promise.withResolvers<void>();
    const doneDeferred = Promise.withResolvers<void>();
    await using server = Deno.serve({
      handler: (request) => {
        const {
          response,
          socket,
        } = Deno.upgradeWebSocket(request);

        socket.onopen = () => {
          Deno.inspect(request); // should not throw
        };
        socket.onerror = (e) => {
          console.error(e);
          fail();
        };
        socket.onmessage = (m) => {
          socket.send(m.data);
          socket.close(1001);
        };
        socket.onclose = () => doneDeferred.resolve();
        return response;
      },
      port: servePort,
      signal: ac.signal,
      onListen: onListen(listeningDeferred.resolve),
      onError: createOnErrorCb(ac),
    });

    await listeningDeferred.promise;
    const def = Promise.withResolvers<void>();
    const ws = new WebSocket(`ws://localhost:${servePort}`);
    ws.onmessage = (m) => assertEquals(m.data, "foo");
    ws.onerror = (e) => {
      console.error(e);
      fail();
    };
    ws.onclose = () => def.resolve();
    ws.onopen = () => ws.send("foo");

    await def.promise;
    await doneDeferred.promise;
    ac.abort();
    await server.finished;
  },
);

Deno.test({
  name:
    "req.body.getReader().read() throws the error with reasonable error message",
}, async () => {
  const { promise, resolve, reject } = Promise.withResolvers<Error>();
  const server = Deno.serve({ onListen, port: 0 }, async (req) => {
    const reader = req.body!.getReader();

    try {
      while (true) {
        await delay(100);
        const { done } = await reader.read();
        if (done) break;
      }
    } catch (e) {
      // deno-lint-ignore no-explicit-any
      resolve(e as any);
    }

    reject(new Error("Should not reach here"));
    server.shutdown();
    return new Response();
  });

  async function onListen({ port }: { port: number }) {
    const body = "a".repeat(1000);
    const header = `POST / HTTP/1.1\r\n` +
      `Host: 127.0.0.1:${port}\r\n` +
      `Content-Length: 1000\r\n` +
      "\r\n";

    const connection = await Deno.connect({ hostname: "127.0.0.1", port });
    await connection.write(new TextEncoder().encode(header));
    await delay(100);
    await connection.write(new TextEncoder().encode(body));
    connection.close();
  }
  await server.finished;
  const e = await promise;
  assertIsError(
    e,
    Deno.errors.BadResource,
    "Cannot read request body as underlying resource unavailable",
  );
});

Deno.test(
  {
    ignore: Deno.build.os !== "linux",
    permissions: { run: true, net: true },
  },
  async function httpServerVsockWebSocketUpgrade() {
    const ac = new AbortController();
    const { promise, resolve } = Promise.withResolvers<Deno.VsockAddr>();
    const serverWebSocketClosed = Promise.withResolvers<void>();

    await using server = Deno.serve(
      {
        signal: ac.signal,
        cid: -1,
        port: 8001,
        onListen(info) {
          resolve(info);
        },
        onError: createOnErrorCb(ac),
      },
      (request) => {
        const { socket, response } = Deno.upgradeWebSocket(request);

        socket.onmessage = (event) => {
          // Echo the message back to the client
          if (socket.readyState === WebSocket.OPEN) {
            socket.send(event.data);
          }
        };

        socket.onclose = () => {
          serverWebSocketClosed.resolve();
        };

        return response;
      },
    );

    assertEquals((await promise).cid, 4294967295);
    assertEquals((await promise).port, 8001);

    const conn = await Deno.connect({
      transport: "vsock",
      cid: 1,
      port: 8001,
    });

    // Send HTTP upgrade request
    await conn.write(
      new TextEncoder().encode(
        "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Key: test\r\n\r\n",
      ),
    );

    // Read upgrade response
    const responseData = new Uint8Array(512);
    const responseRead = await conn.read(responseData);
    const responseText = new TextDecoder().decode(
      responseData.subarray(0, responseRead!),
    );
    assert(responseText.includes("101 Switching Protocols"));

    await conn.close();
    await serverWebSocketClosed.promise;
    ac.abort();
    await server.finished;
  },
);

async function captureLegacyAbortWarnings(
  fn: () => Promise<void>,
): Promise<string[]> {
  resetLegacyAbortWarning();
  const warnings: string[] = [];
  const originalWarn = console.warn;
  console.warn = (...args: unknown[]) => {
    if (typeof args[0] === "string" && args[0].includes("request.signal")) {
      warnings.push(args[0]);
      return;
    }
    originalWarn(...args);
  };
  try {
    await fn();
  } finally {
    console.warn = originalWarn;
    resetLegacyAbortWarning();
  }
  return warnings;
}

Deno.test(
  { permissions: { net: true } },
  async function httpServerLegacyAbortWarningFiresOnce() {
    const warnings = await captureLegacyAbortWarnings(async () => {
      const { finished, shutdown } = await makeServer((req) => {
        req.signal.addEventListener("abort", () => {});
        return new Response("ok");
      });
      for (let i = 0; i < 3; i++) {
        await (await fetch(`http://localhost:${servePort}`)).text();
      }
      await shutdown();
      await finished;
    });
    assertEquals(warnings.length, 1);
    assertStringIncludes(warnings[0], "request.signal");
    assertStringIncludes(warnings[0], "--unstable-no-legacy-abort");
  },
);

Deno.test(
  { permissions: { net: true } },
  async function httpServerLegacyAbortWarningSkipsWhenSignalUntouched() {
    const warnings = await captureLegacyAbortWarnings(async () => {
      const { finished, shutdown } = await makeServer(() => {
        return new Response("ok");
      });
      for (let i = 0; i < 3; i++) {
        await (await fetch(`http://localhost:${servePort}`)).text();
      }
      await shutdown();
      await finished;
    });
    assertEquals(warnings.length, 0);
  },
);

Deno.test(
  { permissions: { net: true, run: true, read: true } },
  async function httpServerLegacyAbortWarningSilentUnderUnstableFlag() {
    const subprocessPort = servePort + 1;
    const code = `
      const originalWarn = console.warn;
      let warned = false;
      console.warn = (...args) => {
        if (typeof args[0] === "string" && args[0].includes("request.signal")) {
          warned = true;
        }
        originalWarn(...args);
      };
      const { promise, resolve } = Promise.withResolvers();
      const server = Deno.serve({
        port: ${subprocessPort},
        handler: (req) => {
          req.signal.addEventListener("abort", () => {});
          return new Response("ok");
        },
        onListen: resolve,
      });
      await promise;
      for (let i = 0; i < 3; i++) {
        await (await fetch("http://localhost:${subprocessPort}")).text();
      }
      console.log(warned ? "WARNED" : "QUIET");
      await server.shutdown();
    `;
    const command = new Deno.Command(Deno.execPath(), {
      args: ["eval", "--unstable-no-legacy-abort", code],
      stdout: "piped",
      stderr: "piped",
    });
    const { code: exitCode, stdout } = await command.output();
    const output = new TextDecoder().decode(stdout);
    assertEquals(exitCode, 0);
    assertStringIncludes(output, "QUIET");
    assert(!output.includes("WARNED"));
  },
);
