2024-01-01 14:58:21 -05:00
|
|
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
2022-08-18 08:05:02 -04:00
|
|
|
|
2024-02-12 21:05:10 -05:00
|
|
|
import { assertMatch, assertRejects } from "@std/assert/mod.ts";
|
|
|
|
import { Buffer, BufReader, BufWriter } from "@std/io/mod.ts";
|
2022-10-17 18:57:31 -04:00
|
|
|
import { TextProtoReader } from "../testdata/run/textproto.ts";
|
2022-08-18 08:05:02 -04:00
|
|
|
import {
|
|
|
|
assert,
|
|
|
|
assertEquals,
|
2023-01-14 09:06:28 -05:00
|
|
|
assertStringIncludes,
|
2022-08-18 08:05:02 -04:00
|
|
|
assertThrows,
|
2024-03-07 08:58:46 -05:00
|
|
|
curlRequest,
|
|
|
|
curlRequestWithStdErr,
|
2023-05-19 17:14:40 -04:00
|
|
|
execCode,
|
2024-05-28 16:46:04 -04:00
|
|
|
execCode3,
|
2022-08-18 08:05:02 -04:00
|
|
|
fail,
|
2023-10-03 22:37:39 -04:00
|
|
|
tmpUnixSocketPath,
|
2022-08-18 08:05:02 -04:00
|
|
|
} from "./test_util.ts";
|
|
|
|
|
2023-05-22 15:35:59 -04:00
|
|
|
// Since these tests may run in parallel, ensure this port is unique to this file
|
|
|
|
const servePort = 4502;
|
|
|
|
|
2023-04-26 18:58:18 -04:00
|
|
|
const {
|
|
|
|
upgradeHttpRaw,
|
2023-05-18 22:10:25 -04:00
|
|
|
addTrailers,
|
2023-05-31 19:20:39 -04:00
|
|
|
serveHttpOnListener,
|
|
|
|
serveHttpOnConnection,
|
2024-04-29 11:40:02 -04:00
|
|
|
getCachedAbortSignal,
|
2023-04-26 18:58:18 -04:00
|
|
|
// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
|
|
|
|
} = Deno[Deno.internal];
|
|
|
|
|
2022-08-18 08:05:02 -04:00
|
|
|
function createOnErrorCb(ac: AbortController): (err: unknown) => Response {
|
|
|
|
return (err) => {
|
|
|
|
console.error(err);
|
|
|
|
ac.abort();
|
|
|
|
return new Response("Internal server error", { status: 500 });
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
function onListen(
|
|
|
|
resolve: (value: void | PromiseLike<void>) => void,
|
2022-08-18 08:05:02 -04:00
|
|
|
): ({ hostname, port }: { hostname: string; port: number }) => void {
|
|
|
|
return () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
resolve();
|
2022-08-18 08:05:02 -04:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-09-11 20:06:38 -04:00
|
|
|
async function makeServer(
|
2024-04-19 20:09:50 -04:00
|
|
|
handler: (
|
|
|
|
req: Request,
|
|
|
|
info: Deno.ServeHandlerInfo,
|
|
|
|
) => Response | Promise<Response>,
|
2023-09-11 20:06:38 -04:00
|
|
|
): Promise<
|
2023-11-01 15:26:12 -04:00
|
|
|
{
|
|
|
|
finished: Promise<void>;
|
|
|
|
abort: () => void;
|
|
|
|
shutdown: () => Promise<void>;
|
|
|
|
[Symbol.asyncDispose](): PromiseLike<void>;
|
|
|
|
}
|
2023-09-11 20:06:38 -04:00
|
|
|
> {
|
2023-04-22 13:48:21 -04:00
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2023-04-22 13:48:21 -04:00
|
|
|
|
|
|
|
const server = Deno.serve({
|
2023-09-11 20:06:38 -04:00
|
|
|
handler,
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2023-04-22 13:48:21 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(resolve),
|
2023-04-22 13:48:21 -04:00
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2023-09-11 20:06:38 -04:00
|
|
|
return {
|
|
|
|
finished: server.finished,
|
|
|
|
abort() {
|
|
|
|
ac.abort();
|
|
|
|
},
|
|
|
|
async shutdown() {
|
|
|
|
await server.shutdown();
|
|
|
|
},
|
2023-11-01 15:26:12 -04:00
|
|
|
[Symbol.asyncDispose]() {
|
|
|
|
return server[Symbol.asyncDispose]();
|
|
|
|
},
|
2023-09-11 20:06:38 -04:00
|
|
|
};
|
|
|
|
}
|
2023-04-22 13:48:21 -04:00
|
|
|
|
2023-09-11 20:06:38 -04:00
|
|
|
Deno.test(async function httpServerShutsDownPortBeforeResolving() {
|
|
|
|
const { finished, abort } = await makeServer((_req) => new Response("ok"));
|
|
|
|
assertThrows(() => Deno.listen({ port: servePort }));
|
|
|
|
abort();
|
|
|
|
await finished;
|
2023-04-22 13:48:21 -04:00
|
|
|
|
2023-05-22 15:35:59 -04:00
|
|
|
const listener = Deno.listen({ port: servePort });
|
2023-04-22 13:48:21 -04:00
|
|
|
listener!.close();
|
|
|
|
});
|
|
|
|
|
2023-09-11 20:06:38 -04:00
|
|
|
// 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() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferredQueue: {
|
|
|
|
input: ReturnType<typeof Promise.withResolvers<string>>;
|
|
|
|
out: ReturnType<typeof Promise.withResolvers<void>>;
|
|
|
|
}[] = [];
|
2023-09-11 20:06:38 -04:00
|
|
|
const { finished, abort } = await makeServer((_req) => {
|
2023-11-22 06:11:20 -05:00
|
|
|
const { input, out } = deferredQueue.shift()!;
|
2023-09-11 20:06:38 -04:00
|
|
|
return new Response(
|
|
|
|
new ReadableStream({
|
|
|
|
async start(controller) {
|
|
|
|
controller.enqueue(new Uint8Array([46]));
|
2023-11-22 06:11:20 -05:00
|
|
|
out.resolve();
|
|
|
|
controller.enqueue(encoder.encode(await input.promise));
|
2023-09-11 20:06:38 -04:00
|
|
|
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();
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
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);
|
2023-09-11 20:06:38 -04:00
|
|
|
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`));
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred3.out.promise;
|
2023-09-11 20:06:38 -04:00
|
|
|
|
|
|
|
// 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() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferredQueue: {
|
|
|
|
input: ReturnType<typeof Promise.withResolvers<string>>;
|
|
|
|
out: ReturnType<typeof Promise.withResolvers<void>>;
|
|
|
|
}[] = [];
|
2023-09-11 20:06:38 -04:00
|
|
|
const { finished, shutdown } = await makeServer((_req) => {
|
2023-11-22 06:11:20 -05:00
|
|
|
const { input, out } = deferredQueue.shift()!;
|
2023-09-11 20:06:38 -04:00
|
|
|
return new Response(
|
|
|
|
new ReadableStream({
|
|
|
|
async start(controller) {
|
|
|
|
controller.enqueue(new Uint8Array([46]));
|
2023-11-22 06:11:20 -05:00
|
|
|
out.resolve();
|
|
|
|
controller.enqueue(encoder.encode(await input.promise));
|
2023-09-11 20:06:38 -04:00
|
|
|
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();
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
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);
|
2023-09-11 20:06:38 -04:00
|
|
|
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`));
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred3.out.promise;
|
2023-09-11 20:06:38 -04:00
|
|
|
|
|
|
|
// 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() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2023-09-11 20:06:38 -04:00
|
|
|
const { finished, shutdown } = await makeServer(async (_req) => {
|
2023-11-22 06:11:20 -05:00
|
|
|
resolve();
|
2023-09-11 20:06:38 -04:00
|
|
|
await new Promise((r) => setTimeout(r, 10));
|
|
|
|
return new Response((await makeTempFile(1024 * 1024)).readable);
|
|
|
|
});
|
|
|
|
|
|
|
|
const f = fetch(`http://localhost:${servePort}`);
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2023-09-11 20:06:38 -04:00
|
|
|
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() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const waitForAbort = Promise.withResolvers<void>();
|
|
|
|
const waitForRequest = Promise.withResolvers<void>();
|
2023-09-11 20:06:38 -04:00
|
|
|
const { finished, shutdown } = await makeServer(async (_req) => {
|
2023-11-22 06:11:20 -05:00
|
|
|
waitForRequest.resolve();
|
|
|
|
await waitForAbort.promise;
|
2023-09-11 20:06:38 -04:00
|
|
|
await new Promise((r) => setTimeout(r, 10));
|
|
|
|
return new Response((await makeTempFile(1024 * 1024)).readable);
|
|
|
|
});
|
|
|
|
|
|
|
|
const f = fetch(`http://localhost:${servePort}`);
|
2023-11-22 06:11:20 -05:00
|
|
|
await waitForRequest.promise;
|
2023-09-11 20:06:38 -04:00
|
|
|
const s = shutdown();
|
2023-11-22 06:11:20 -05:00
|
|
|
waitForAbort.resolve();
|
2023-09-11 20:06:38 -04:00
|
|
|
assertEquals((await (await f).text()).length, 1048576);
|
|
|
|
await s;
|
|
|
|
await finished;
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-11-01 15:26:12 -04:00
|
|
|
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}`);
|
2024-05-22 20:27:58 -04:00
|
|
|
dataPromise = resp.bytes();
|
2023-11-01 15:26:12 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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(),
|
2024-05-22 20:27:58 -04:00
|
|
|
resp.bytes(),
|
2023-11-01 15:26:12 -04:00
|
|
|
]);
|
|
|
|
|
|
|
|
assertEquals(data.byteLength, 1048576);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-05-19 17:14:40 -04:00
|
|
|
Deno.test(
|
|
|
|
{ permissions: { read: true, run: true } },
|
|
|
|
async function httpServerUnref() {
|
|
|
|
const [statusCode, _output] = await execCode(`
|
|
|
|
async function main() {
|
2023-06-24 07:55:08 -04:00
|
|
|
const server = Deno.serve({ port: ${servePort}, handler: () => null });
|
2023-05-19 17:14:40 -04:00
|
|
|
server.unref();
|
|
|
|
await server.finished; // This doesn't block the program from exiting
|
|
|
|
}
|
|
|
|
main();
|
|
|
|
`);
|
|
|
|
assertEquals(statusCode, 0);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2022-08-24 10:38:51 -04:00
|
|
|
Deno.test(async function httpServerCanResolveHostnames() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2022-08-24 10:38:51 -04:00
|
|
|
|
|
|
|
const server = Deno.serve({
|
|
|
|
handler: (_req) => new Response("ok"),
|
|
|
|
hostname: "localhost",
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-08-24 10:38:51 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(resolve),
|
2022-08-24 10:38:51 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2023-05-22 15:35:59 -04:00
|
|
|
const resp = await fetch(`http://localhost:${servePort}/`, {
|
2022-08-24 10:38:51 -04:00
|
|
|
headers: { "connection": "close" },
|
|
|
|
});
|
|
|
|
const text = await resp.text();
|
|
|
|
assertEquals(text, "ok");
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-21 15:15:52 -04:00
|
|
|
});
|
|
|
|
|
2022-11-26 22:50:14 -05:00
|
|
|
Deno.test(async function httpServerRejectsOnAddrInUse() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2022-08-25 23:12:11 -04:00
|
|
|
|
|
|
|
const server = Deno.serve({
|
|
|
|
handler: (_req) => new Response("ok"),
|
|
|
|
hostname: "localhost",
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-11-26 22:50:14 -05:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(resolve),
|
2022-11-26 22:50:14 -05:00
|
|
|
onError: createOnErrorCb(ac),
|
2022-08-25 23:12:11 -04:00
|
|
|
});
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2022-08-25 23:12:11 -04:00
|
|
|
|
2023-05-18 20:59:23 -04:00
|
|
|
assertThrows(
|
2022-08-25 23:12:11 -04:00
|
|
|
() =>
|
|
|
|
Deno.serve({
|
|
|
|
handler: (_req) => new Response("ok"),
|
|
|
|
hostname: "localhost",
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-11-26 22:50:14 -05:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(resolve),
|
2022-11-26 22:50:14 -05:00
|
|
|
onError: createOnErrorCb(ac),
|
2022-08-25 23:12:11 -04:00
|
|
|
}),
|
|
|
|
Deno.errors.AddrInUse,
|
|
|
|
);
|
2022-11-26 22:50:14 -05:00
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-25 23:12:11 -04:00
|
|
|
});
|
|
|
|
|
2022-08-18 08:05:02 -04:00
|
|
|
Deno.test({ permissions: { net: true } }, async function httpServerBasic() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
2024-04-19 20:09:50 -04:00
|
|
|
const listeningDeferred = Promise.withResolvers<Deno.NetAddr>();
|
2022-08-18 08:05:02 -04:00
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2023-02-24 17:36:07 -05:00
|
|
|
handler: async (request, { remoteAddr }) => {
|
2022-08-19 08:36:01 -04:00
|
|
|
// FIXME(bartlomieju):
|
|
|
|
// make sure that request can be inspected
|
|
|
|
console.log(request);
|
2023-05-22 15:35:59 -04:00
|
|
|
assertEquals(new URL(request.url).href, `http://127.0.0.1:${servePort}/`);
|
2022-08-19 08:36:01 -04:00
|
|
|
assertEquals(await request.text(), "");
|
2023-02-24 17:36:07 -05:00
|
|
|
assertEquals(remoteAddr.hostname, "127.0.0.1");
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response("Hello World", { headers: { "foo": "bar" } });
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2024-04-19 20:09:50 -04:00
|
|
|
onListen: (addr) => listeningDeferred.resolve(addr),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2024-04-19 20:09:50 -04:00
|
|
|
const addr = await listeningDeferred.promise;
|
|
|
|
assertEquals(addr.hostname, server.addr.hostname);
|
|
|
|
assertEquals(addr.port, server.addr.port);
|
2023-05-22 15:35:59 -04:00
|
|
|
const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
|
2022-08-18 08:05:02 -04:00
|
|
|
headers: { "connection": "close" },
|
|
|
|
});
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
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();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
});
|
|
|
|
|
2023-05-31 19:20:39 -04:00
|
|
|
// Test serving of HTTP on an arbitrary listener.
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerOnListener() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
2024-04-19 20:09:50 -04:00
|
|
|
const listeningDeferred = Promise.withResolvers();
|
2023-05-31 19:20:39 -04:00
|
|
|
const listener = Deno.listen({ port: servePort });
|
|
|
|
const 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");
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2023-05-31 19:20:39 -04:00
|
|
|
return new Response("Hello World", { headers: { "foo": "bar" } });
|
|
|
|
},
|
|
|
|
createOnErrorCb(ac),
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen(listeningDeferred.resolve),
|
2023-05-31 19:20:39 -04:00
|
|
|
);
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-31 19:20:39 -04:00
|
|
|
const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
|
|
|
|
headers: { "connection": "close" },
|
|
|
|
});
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-31 19:20:39 -04:00
|
|
|
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();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2023-05-31 19:20:39 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
// Test serving of HTTP on an arbitrary connection.
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerOnConnection() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2023-05-31 19:20:39 -04:00
|
|
|
const listener = Deno.listen({ port: servePort });
|
|
|
|
const acceptPromise = listener.accept();
|
|
|
|
const fetchPromise = fetch(`http://127.0.0.1:${servePort}/`, {
|
|
|
|
headers: { "connection": "close" },
|
|
|
|
});
|
|
|
|
|
|
|
|
const 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");
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2023-05-31 19:20:39 -04:00
|
|
|
return new Response("Hello World", { headers: { "foo": "bar" } });
|
|
|
|
},
|
|
|
|
createOnErrorCb(ac),
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen(listeningDeferred.resolve),
|
2023-05-31 19:20:39 -04:00
|
|
|
);
|
|
|
|
|
|
|
|
const resp = await fetchPromise;
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2023-05-31 19:20:39 -04:00
|
|
|
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();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2023-05-31 19:20:39 -04:00
|
|
|
listener.close();
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-04-22 13:48:21 -04:00
|
|
|
Deno.test({ permissions: { net: true } }, async function httpServerOnError() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2023-04-22 13:48:21 -04:00
|
|
|
let requestStash: Request | null;
|
|
|
|
|
|
|
|
const server = Deno.serve({
|
|
|
|
handler: async (request: Request) => {
|
|
|
|
requestStash = request;
|
|
|
|
await new Promise((r) => setTimeout(r, 100));
|
|
|
|
throw "fail";
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2023-04-22 13:48:21 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(resolve),
|
2023-04-22 13:48:21 -04:00
|
|
|
onError: () => {
|
|
|
|
return new Response("failed: " + requestStash!.url, { status: 500 });
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2023-05-22 15:35:59 -04:00
|
|
|
const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
|
2023-04-22 13:48:21 -04:00
|
|
|
headers: { "connection": "close" },
|
|
|
|
});
|
|
|
|
const text = await resp.text();
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2023-04-22 13:48:21 -04:00
|
|
|
|
2023-05-22 15:35:59 -04:00
|
|
|
assertEquals(text, `failed: http://127.0.0.1:${servePort}/`);
|
2023-04-22 13:48:21 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerOnErrorFails() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2023-05-17 22:00:17 -04:00
|
|
|
// 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
|
2023-04-22 13:48:21 -04:00
|
|
|
let requestStash: Request | null;
|
|
|
|
|
|
|
|
const server = Deno.serve({
|
|
|
|
handler: async (request: Request) => {
|
|
|
|
requestStash = request;
|
|
|
|
await new Promise((r) => setTimeout(r, 100));
|
|
|
|
throw "fail";
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2023-04-22 13:48:21 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(resolve),
|
2023-04-22 13:48:21 -04:00
|
|
|
onError: () => {
|
|
|
|
throw "again";
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2023-05-22 15:35:59 -04:00
|
|
|
const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
|
2023-04-22 13:48:21 -04:00
|
|
|
headers: { "connection": "close" },
|
|
|
|
});
|
|
|
|
const text = await resp.text();
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2023-04-22 13:48:21 -04:00
|
|
|
|
|
|
|
assertEquals(text, "Internal Server Error");
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2022-08-23 18:08:56 -04:00
|
|
|
Deno.test({ permissions: { net: true } }, async function httpServerOverload1() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-23 18:08:56 -04:00
|
|
|
|
|
|
|
const server = Deno.serve({
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-08-23 18:08:56 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-23 18:08:56 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
}, async (request) => {
|
|
|
|
// FIXME(bartlomieju):
|
|
|
|
// make sure that request can be inspected
|
|
|
|
console.log(request);
|
2023-05-22 15:35:59 -04:00
|
|
|
assertEquals(new URL(request.url).href, `http://127.0.0.1:${servePort}/`);
|
2022-08-23 18:08:56 -04:00
|
|
|
assertEquals(await request.text(), "");
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-23 18:08:56 -04:00
|
|
|
return new Response("Hello World", { headers: { "foo": "bar" } });
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-22 15:35:59 -04:00
|
|
|
const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
|
2022-08-23 18:08:56 -04:00
|
|
|
headers: { "connection": "close" },
|
|
|
|
});
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-23 18:08:56 -04:00
|
|
|
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();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-23 18:08:56 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test({ permissions: { net: true } }, async function httpServerOverload2() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-23 18:08:56 -04:00
|
|
|
|
2023-04-26 08:54:03 -04:00
|
|
|
const server = Deno.serve({
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2023-04-26 08:54:03 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2023-04-26 08:54:03 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
}, async (request) => {
|
2022-08-23 18:08:56 -04:00
|
|
|
// FIXME(bartlomieju):
|
|
|
|
// make sure that request can be inspected
|
|
|
|
console.log(request);
|
2023-05-22 15:35:59 -04:00
|
|
|
assertEquals(new URL(request.url).href, `http://127.0.0.1:${servePort}/`);
|
2022-08-23 18:08:56 -04:00
|
|
|
assertEquals(await request.text(), "");
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-23 18:08:56 -04:00
|
|
|
return new Response("Hello World", { headers: { "foo": "bar" } });
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-22 15:35:59 -04:00
|
|
|
const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
|
2022-08-23 18:08:56 -04:00
|
|
|
headers: { "connection": "close" },
|
|
|
|
});
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-23 18:08:56 -04:00
|
|
|
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();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-23 18:08:56 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
2023-05-18 20:59:23 -04:00
|
|
|
function httpServerErrorOverloadMissingHandler() {
|
2022-08-23 18:08:56 -04:00
|
|
|
// @ts-ignore - testing invalid overload
|
2023-05-18 20:59:23 -04:00
|
|
|
assertThrows(() => Deno.serve(), TypeError, "handler");
|
2022-08-23 18:08:56 -04:00
|
|
|
// @ts-ignore - testing invalid overload
|
2023-05-18 20:59:23 -04:00
|
|
|
assertThrows(() => Deno.serve({}), TypeError, "handler");
|
|
|
|
assertThrows(
|
2022-08-23 18:08:56 -04:00
|
|
|
// @ts-ignore - testing invalid overload
|
|
|
|
() => Deno.serve({ handler: undefined }),
|
|
|
|
TypeError,
|
|
|
|
"handler",
|
|
|
|
);
|
2023-05-18 20:59:23 -04:00
|
|
|
assertThrows(
|
2022-08-23 18:08:56 -04:00
|
|
|
// @ts-ignore - testing invalid overload
|
|
|
|
() => Deno.serve(undefined, { handler: () => {} }),
|
|
|
|
TypeError,
|
|
|
|
"handler",
|
|
|
|
);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2022-08-21 08:27:14 -04:00
|
|
|
Deno.test({ permissions: { net: true } }, async function httpServerPort0() {
|
|
|
|
const ac = new AbortController();
|
|
|
|
|
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler() {
|
2022-08-21 08:27:14 -04:00
|
|
|
return new Response("Hello World");
|
|
|
|
},
|
|
|
|
port: 0,
|
|
|
|
signal: ac.signal,
|
|
|
|
onListen({ port }) {
|
|
|
|
assert(port > 0 && port < 65536);
|
|
|
|
ac.abort();
|
|
|
|
},
|
|
|
|
});
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-21 08:27:14 -04:00
|
|
|
});
|
|
|
|
|
2022-08-22 04:01:43 -04:00
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerDefaultOnListenCallback() {
|
|
|
|
const ac = new AbortController();
|
|
|
|
|
|
|
|
const consoleLog = console.log;
|
|
|
|
console.log = (msg) => {
|
|
|
|
try {
|
|
|
|
const match = msg.match(/Listening on http:\/\/localhost:(\d+)\//);
|
2023-04-22 13:48:21 -04:00
|
|
|
assert(!!match, `Didn't match ${msg}`);
|
2022-08-22 04:01:43 -04:00
|
|
|
const port = +match[1];
|
|
|
|
assert(port > 0 && port < 65536);
|
|
|
|
} finally {
|
|
|
|
ac.abort();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
try {
|
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler() {
|
2022-08-22 04:01:43 -04:00
|
|
|
return new Response("Hello World");
|
|
|
|
},
|
|
|
|
hostname: "0.0.0.0",
|
|
|
|
port: 0,
|
|
|
|
signal: ac.signal,
|
|
|
|
});
|
|
|
|
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-22 04:01:43 -04:00
|
|
|
} finally {
|
|
|
|
console.log = consoleLog;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2022-08-18 08:05:02 -04:00
|
|
|
// https://github.com/denoland/deno/issues/15107
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpLazyHeadersIssue15107() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
|
|
|
let headers: Headers;
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: async (request) => {
|
2022-08-19 08:36:01 -04:00
|
|
|
await request.text();
|
|
|
|
headers = request.headers;
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response("");
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-22 15:35:59 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
// 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);
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
conn.close();
|
|
|
|
assertEquals(headers!.get("content-length"), "5");
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-04-22 13:48:21 -04:00
|
|
|
function createUrlTest(
|
|
|
|
name: string,
|
|
|
|
methodAndPath: string,
|
|
|
|
host: string | null,
|
|
|
|
expected: string,
|
|
|
|
) {
|
|
|
|
Deno.test(`httpServerUrl${name}`, async () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
const listeningDeferred = Promise.withResolvers<number>();
|
|
|
|
const urlDeferred = Promise.withResolvers<string>();
|
2023-04-22 13:48:21 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
const server = Deno.serve({
|
2023-05-17 22:00:17 -04:00
|
|
|
handler: (request: Request) => {
|
2023-11-22 06:11:20 -05:00
|
|
|
urlDeferred.resolve(request.url);
|
2023-04-22 13:48:21 -04:00
|
|
|
return new Response("");
|
|
|
|
},
|
|
|
|
port: 0,
|
|
|
|
signal: ac.signal,
|
|
|
|
onListen: ({ port }: { port: number }) => {
|
2023-11-22 06:11:20 -05:00
|
|
|
listeningDeferred.resolve(port);
|
2023-04-22 13:48:21 -04:00
|
|
|
},
|
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
const port = await listeningDeferred.promise;
|
2023-04-22 13:48:21 -04:00
|
|
|
const conn = await Deno.connect({ port });
|
|
|
|
|
|
|
|
const encoder = new TextEncoder();
|
|
|
|
const body = `${methodAndPath} HTTP/1.1\r\n${
|
|
|
|
host ? ("Host: " + host + "\r\n") : ""
|
|
|
|
}Content-Length: 5\r\n\r\n12345`;
|
|
|
|
const writeResult = await conn.write(encoder.encode(body));
|
|
|
|
assertEquals(body.length, writeResult);
|
|
|
|
|
|
|
|
try {
|
|
|
|
const expectedResult = expected.replace("HOST", "localhost").replace(
|
|
|
|
"PORT",
|
|
|
|
`${port}`,
|
|
|
|
);
|
2023-11-22 06:11:20 -05:00
|
|
|
assertEquals(await urlDeferred.promise, expectedResult);
|
2023-04-22 13:48:21 -04:00
|
|
|
} finally {
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2023-04-22 13:48:21 -04:00
|
|
|
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, "*");
|
|
|
|
createUrlTest(
|
|
|
|
"WithAuthorityForm",
|
|
|
|
"CONNECT deno.land:80",
|
|
|
|
null,
|
|
|
|
"deno.land:80",
|
|
|
|
);
|
|
|
|
|
|
|
|
// TODO(mmastrac): These should probably be 400 errors
|
|
|
|
createUrlTest("WithInvalidAsterisk", "GET *", null, "*");
|
|
|
|
createUrlTest("WithInvalidNakedPath", "GET path", null, "path");
|
|
|
|
createUrlTest(
|
|
|
|
"WithInvalidNakedAuthority",
|
|
|
|
"GET deno.land:1234",
|
|
|
|
null,
|
|
|
|
"deno.land:1234",
|
|
|
|
);
|
|
|
|
|
2022-08-18 08:05:02 -04:00
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerGetRequestBody() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: (request) => {
|
2022-08-19 08:36:01 -04:00
|
|
|
assertEquals(request.body, null);
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response("", { headers: {} });
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-22 15:35:59 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
// Send GET request with a body + content-length.
|
|
|
|
const encoder = new TextEncoder();
|
|
|
|
const body =
|
2023-05-22 15:35:59 -04:00
|
|
|
`GET / HTTP/1.1\r\nHost: 127.0.0.1:${servePort}\r\nContent-Length: 5\r\n\r\n12345`;
|
2022-08-18 08:05:02 -04:00
|
|
|
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();
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-09-15 10:08:21 -04:00
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerAbortedRequestBody() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
2023-09-15 10:08:21 -04:00
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2023-09-15 10:08:21 -04:00
|
|
|
|
|
|
|
const server = Deno.serve({
|
|
|
|
handler: async (request) => {
|
|
|
|
await assertRejects(async () => {
|
|
|
|
await request.text();
|
|
|
|
});
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2023-09-15 10:08:21 -04:00
|
|
|
// Not actually used
|
|
|
|
return new Response();
|
|
|
|
},
|
|
|
|
port: servePort,
|
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2023-09-15 10:08:21 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-09-15 10:08:21 -04:00
|
|
|
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();
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2023-09-15 10:08:21 -04:00
|
|
|
ac.abort();
|
|
|
|
await server.finished;
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-04-26 09:33:30 -04:00
|
|
|
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}`);
|
2022-08-18 08:05:02 -04:00
|
|
|
|
2023-04-26 09:33:30 -04:00
|
|
|
if (delay == 0) {
|
|
|
|
doAction(controller, i + 1);
|
|
|
|
} else {
|
|
|
|
setTimeout(() => doAction(controller, i + 1), delay);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-17 22:00:17 -04:00
|
|
|
function makeStream(_count: number, delay: number): ReadableStream {
|
2023-04-26 09:33:30 -04:00
|
|
|
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 () => {
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2023-05-17 22:00:17 -04:00
|
|
|
handler: (_request) => {
|
2023-04-26 09:33:30 -04:00
|
|
|
return new Response(makeStream(count, delay));
|
2022-08-19 08:36:01 -04:00
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
feat(ext/web): resourceForReadableStream (#20180)
Extracted from fast streams work.
This is a resource wrapper for `ReadableStream`, allowing us to treat
all `ReadableStream` instances as resources, and remove special paths in
both `fetch` and `serve`.
Performance with a ReadableStream response yields ~18% improvement:
```
return new Response(new ReadableStream({
start(controller) {
controller.enqueue(new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]));
controller.close();
}
})
```
This patch:
```
12:36 $ third_party/prebuilt/mac/wrk http://localhost:8080
Running 10s test @ http://localhost:8080
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 99.96us 100.03us 6.65ms 98.84%
Req/Sec 47.73k 2.43k 51.02k 89.11%
959308 requests in 10.10s, 117.10MB read
Requests/sec: 94978.71
Transfer/sec: 11.59MB
```
main:
```
Running 10s test @ http://localhost:8080
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 163.03us 685.51us 19.73ms 99.27%
Req/Sec 39.50k 3.98k 66.11k 95.52%
789582 requests in 10.10s, 82.83MB read
Requests/sec: 78182.65
Transfer/sec: 8.20MB
```
2023-08-17 09:52:37 -04:00
|
|
|
try {
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2023-09-26 06:46:06 -04:00
|
|
|
const resp = await fetch(`http://127.0.0.1:${servePort}/`);
|
feat(ext/web): resourceForReadableStream (#20180)
Extracted from fast streams work.
This is a resource wrapper for `ReadableStream`, allowing us to treat
all `ReadableStream` instances as resources, and remove special paths in
both `fetch` and `serve`.
Performance with a ReadableStream response yields ~18% improvement:
```
return new Response(new ReadableStream({
start(controller) {
controller.enqueue(new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]));
controller.close();
}
})
```
This patch:
```
12:36 $ third_party/prebuilt/mac/wrk http://localhost:8080
Running 10s test @ http://localhost:8080
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 99.96us 100.03us 6.65ms 98.84%
Req/Sec 47.73k 2.43k 51.02k 89.11%
959308 requests in 10.10s, 117.10MB read
Requests/sec: 94978.71
Transfer/sec: 11.59MB
```
main:
```
Running 10s test @ http://localhost:8080
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 163.03us 685.51us 19.73ms 99.27%
Req/Sec 39.50k 3.98k 66.11k 95.52%
789582 requests in 10.10s, 82.83MB read
Requests/sec: 78182.65
Transfer/sec: 8.20MB
```
2023-08-17 09:52:37 -04:00
|
|
|
if (action == "Throw") {
|
2023-09-15 10:53:38 -04:00
|
|
|
await assertRejects(async () => {
|
feat(ext/web): resourceForReadableStream (#20180)
Extracted from fast streams work.
This is a resource wrapper for `ReadableStream`, allowing us to treat
all `ReadableStream` instances as resources, and remove special paths in
both `fetch` and `serve`.
Performance with a ReadableStream response yields ~18% improvement:
```
return new Response(new ReadableStream({
start(controller) {
controller.enqueue(new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]));
controller.close();
}
})
```
This patch:
```
12:36 $ third_party/prebuilt/mac/wrk http://localhost:8080
Running 10s test @ http://localhost:8080
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 99.96us 100.03us 6.65ms 98.84%
Req/Sec 47.73k 2.43k 51.02k 89.11%
959308 requests in 10.10s, 117.10MB read
Requests/sec: 94978.71
Transfer/sec: 11.59MB
```
main:
```
Running 10s test @ http://localhost:8080
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 163.03us 685.51us 19.73ms 99.27%
Req/Sec 39.50k 3.98k 66.11k 95.52%
789582 requests in 10.10s, 82.83MB read
Requests/sec: 78182.65
Transfer/sec: 8.20MB
```
2023-08-17 09:52:37 -04:00
|
|
|
await resp.text();
|
2023-09-15 10:53:38 -04:00
|
|
|
});
|
feat(ext/web): resourceForReadableStream (#20180)
Extracted from fast streams work.
This is a resource wrapper for `ReadableStream`, allowing us to treat
all `ReadableStream` instances as resources, and remove special paths in
both `fetch` and `serve`.
Performance with a ReadableStream response yields ~18% improvement:
```
return new Response(new ReadableStream({
start(controller) {
controller.enqueue(new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]));
controller.close();
}
})
```
This patch:
```
12:36 $ third_party/prebuilt/mac/wrk http://localhost:8080
Running 10s test @ http://localhost:8080
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 99.96us 100.03us 6.65ms 98.84%
Req/Sec 47.73k 2.43k 51.02k 89.11%
959308 requests in 10.10s, 117.10MB read
Requests/sec: 94978.71
Transfer/sec: 11.59MB
```
main:
```
Running 10s test @ http://localhost:8080
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 163.03us 685.51us 19.73ms 99.27%
Req/Sec 39.50k 3.98k 66.11k 95.52%
789582 requests in 10.10s, 82.83MB read
Requests/sec: 78182.65
Transfer/sec: 8.20MB
```
2023-08-17 09:52:37 -04:00
|
|
|
} else {
|
|
|
|
const text = await resp.text();
|
2023-04-26 09:33:30 -04:00
|
|
|
|
feat(ext/web): resourceForReadableStream (#20180)
Extracted from fast streams work.
This is a resource wrapper for `ReadableStream`, allowing us to treat
all `ReadableStream` instances as resources, and remove special paths in
both `fetch` and `serve`.
Performance with a ReadableStream response yields ~18% improvement:
```
return new Response(new ReadableStream({
start(controller) {
controller.enqueue(new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]));
controller.close();
}
})
```
This patch:
```
12:36 $ third_party/prebuilt/mac/wrk http://localhost:8080
Running 10s test @ http://localhost:8080
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 99.96us 100.03us 6.65ms 98.84%
Req/Sec 47.73k 2.43k 51.02k 89.11%
959308 requests in 10.10s, 117.10MB read
Requests/sec: 94978.71
Transfer/sec: 11.59MB
```
main:
```
Running 10s test @ http://localhost:8080
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 163.03us 685.51us 19.73ms 99.27%
Req/Sec 39.50k 3.98k 66.11k 95.52%
789582 requests in 10.10s, 82.83MB read
Requests/sec: 78182.65
Transfer/sec: 8.20MB
```
2023-08-17 09:52:37 -04:00
|
|
|
let expected = "";
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
|
|
expected += `a${i}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
assertEquals(text, expected);
|
2023-04-26 09:33:30 -04:00
|
|
|
}
|
feat(ext/web): resourceForReadableStream (#20180)
Extracted from fast streams work.
This is a resource wrapper for `ReadableStream`, allowing us to treat
all `ReadableStream` instances as resources, and remove special paths in
both `fetch` and `serve`.
Performance with a ReadableStream response yields ~18% improvement:
```
return new Response(new ReadableStream({
start(controller) {
controller.enqueue(new Uint8Array([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]));
controller.close();
}
})
```
This patch:
```
12:36 $ third_party/prebuilt/mac/wrk http://localhost:8080
Running 10s test @ http://localhost:8080
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 99.96us 100.03us 6.65ms 98.84%
Req/Sec 47.73k 2.43k 51.02k 89.11%
959308 requests in 10.10s, 117.10MB read
Requests/sec: 94978.71
Transfer/sec: 11.59MB
```
main:
```
Running 10s test @ http://localhost:8080
2 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 163.03us 685.51us 19.73ms 99.27%
Req/Sec 39.50k 3.98k 66.11k 95.52%
789582 requests in 10.10s, 82.83MB read
Requests/sec: 78182.65
Transfer/sec: 8.20MB
```
2023-08-17 09:52:37 -04:00
|
|
|
} finally {
|
|
|
|
ac.abort();
|
2023-09-15 10:53:38 -04:00
|
|
|
await server.shutdown();
|
2023-04-26 09:33:30 -04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-05-17 22:00:17 -04:00
|
|
|
for (const count of [0, 1, 2, 3]) {
|
fix(ext/http): ensure request body resource lives as long as response is alive (#20206)
Deno.serve's fast streaming implementation was not keeping the request
body resource ID alive. We were taking the `Rc<Resource>` from the
resource table during the response, so a hairpin duplex response that
fed back the request body would work.
However, if any JS code attempted to read from the request body (which
requires the resource ID to be valid), the response would fail with a
difficult-to-diagnose "EOF" error.
This was affecting more complex duplex uses of `Deno.fetch` (though as
far as I can tell was unreported).
Simple test:
```ts
const reader = request.body.getReader();
return new Response(
new ReadableStream({
async pull(controller) {
const { done, value } = await reader.read();
if (done) {
controller.close();
} else {
controller.enqueue(value);
}
},
}),
```
And then attempt to use the stream in duplex mode:
```ts
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);
}
```
In older versions of Deno, this would just lock up. I believe after
23ff0e722e3c4b0827940853c53c5ee2ede5ec9f, it started throwing a more
explicit error:
```
httpServerStreamDuplexJavascript => ./cli/tests/unit/serve_test.ts:1339:6
error: TypeError: request or response body error: error reading a body from connection: Connection reset by peer (os error 54)
at async Object.pull (ext:deno_web/06_streams.js:810:27)
```
2023-08-20 21:35:26 -04:00
|
|
|
for (const delay of [0, 1, 25]) {
|
2023-04-26 09:33:30 -04:00
|
|
|
// Creating a stream that errors in start will throw
|
|
|
|
if (delay > 0) {
|
|
|
|
createStreamTest(count, delay, "Throw");
|
|
|
|
}
|
|
|
|
createStreamTest(count, delay, "Close");
|
|
|
|
}
|
|
|
|
}
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
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();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: async (request) => {
|
2022-08-19 08:36:01 -04:00
|
|
|
const reqBody = await request.text();
|
|
|
|
assertEquals("hello world", reqBody);
|
|
|
|
return new Response("yo");
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2023-05-22 15:35:59 -04:00
|
|
|
const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
|
2022-08-18 08:05:02 -04:00
|
|
|
body: stream.readable,
|
|
|
|
method: "POST",
|
|
|
|
headers: { "connection": "close" },
|
|
|
|
});
|
|
|
|
|
|
|
|
assertEquals(await resp.text(), "yo");
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
Deno.test({ permissions: { net: true } }, async function httpServerClose() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: () => new Response("ok"),
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2023-05-22 15:35:59 -04:00
|
|
|
const client = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
client.close();
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
});
|
|
|
|
|
2023-05-16 19:00:59 -04:00
|
|
|
// https://github.com/denoland/deno/issues/15427
|
|
|
|
Deno.test({ permissions: { net: true } }, async function httpServerCloseGet() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
|
|
|
const requestDeferred = Promise.withResolvers<void>();
|
|
|
|
const responseDeferred = Promise.withResolvers<void>();
|
2023-05-16 19:00:59 -04:00
|
|
|
const server = Deno.serve({
|
|
|
|
handler: async () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
requestDeferred.resolve();
|
2023-05-16 19:00:59 -04:00
|
|
|
await new Promise((r) => setTimeout(r, 500));
|
2023-11-22 06:11:20 -05:00
|
|
|
responseDeferred.resolve();
|
2023-05-16 19:00:59 -04:00
|
|
|
return new Response("ok");
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2023-05-16 19:00:59 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2023-05-16 19:00:59 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-22 15:35:59 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2023-05-16 19:00:59 -04:00
|
|
|
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);
|
2023-11-22 06:11:20 -05:00
|
|
|
await requestDeferred.promise;
|
2023-05-16 19:00:59 -04:00
|
|
|
conn.close();
|
2023-11-22 06:11:20 -05:00
|
|
|
await responseDeferred.promise;
|
2023-05-16 19:00:59 -04:00
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2023-05-16 19:00:59 -04:00
|
|
|
});
|
|
|
|
|
2022-08-18 08:05:02 -04:00
|
|
|
// FIXME:
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerEmptyBlobResponse() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: () => new Response(new Blob([])),
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2023-09-26 06:46:06 -04:00
|
|
|
const resp = await fetch(`http://127.0.0.1:${servePort}/`);
|
2022-08-18 08:05:02 -04:00
|
|
|
const respBody = await resp.text();
|
|
|
|
|
|
|
|
assertEquals("", respBody);
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-01-14 09:06:28 -05:00
|
|
|
// https://github.com/denoland/deno/issues/17291
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerIncorrectChunkedResponse() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
|
|
|
const errorDeferred = Promise.withResolvers<void>();
|
2023-01-14 09:06:28 -05:00
|
|
|
const server = Deno.serve({
|
|
|
|
handler: () => {
|
|
|
|
const body = new ReadableStream({
|
|
|
|
start(controller) {
|
|
|
|
// Non-encoded string is not a valid readable chunk.
|
2023-04-03 11:44:18 -04:00
|
|
|
// @ts-ignore we're testing that input is invalid
|
2023-01-14 09:06:28 -05:00
|
|
|
controller.enqueue("wat");
|
|
|
|
},
|
2023-04-03 11:44:18 -04:00
|
|
|
type: "bytes",
|
2023-01-14 09:06:28 -05:00
|
|
|
});
|
|
|
|
return new Response(body);
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2023-01-14 09:06:28 -05:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2023-01-14 09:06:28 -05:00
|
|
|
onError: (err) => {
|
|
|
|
const errResp = new Response(
|
|
|
|
`Internal server error: ${(err as Error).message}`,
|
|
|
|
{ status: 500 },
|
|
|
|
);
|
2023-11-22 06:11:20 -05:00
|
|
|
errorDeferred.resolve();
|
2023-01-14 09:06:28 -05:00
|
|
|
return errResp;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-09-26 06:46:06 -04:00
|
|
|
const resp = await fetch(`http://127.0.0.1:${servePort}/`);
|
2023-01-14 09:06:28 -05:00
|
|
|
// Incorrectly implemented reader ReadableStream should reject.
|
2023-04-03 11:44:18 -04:00
|
|
|
assertStringIncludes(await resp.text(), "Failed to execute 'enqueue'");
|
2023-11-22 06:11:20 -05:00
|
|
|
await errorDeferred.promise;
|
2023-01-14 09:06:28 -05:00
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2023-01-14 09:06:28 -05:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2022-09-07 06:51:30 -04:00
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerCorrectLengthForUnicodeString() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2022-09-07 06:51:30 -04:00
|
|
|
|
|
|
|
const server = Deno.serve({
|
|
|
|
handler: () => new Response("韓國".repeat(10)),
|
2023-05-24 14:36:41 -04:00
|
|
|
port: servePort,
|
2022-09-07 06:51:30 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(resolve),
|
2022-09-07 06:51:30 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2023-05-24 14:36:41 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-09-07 06:51:30 -04:00
|
|
|
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();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2023-04-03 11:44:18 -04:00
|
|
|
assert(msg.includes("content-length: 60"));
|
2022-09-07 06:51:30 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2022-08-18 08:05:02 -04:00
|
|
|
Deno.test({ permissions: { net: true } }, async function httpServerWebSocket() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
|
|
|
const doneDeferred = Promise.withResolvers<void>();
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2023-05-17 22:00:17 -04:00
|
|
|
handler: (request) => {
|
2022-08-19 08:36:01 -04:00
|
|
|
const {
|
|
|
|
response,
|
|
|
|
socket,
|
|
|
|
} = Deno.upgradeWebSocket(request);
|
2023-04-22 13:48:21 -04:00
|
|
|
socket.onerror = (e) => {
|
|
|
|
console.error(e);
|
|
|
|
fail();
|
|
|
|
};
|
2022-08-19 08:36:01 -04:00
|
|
|
socket.onmessage = (m) => {
|
|
|
|
socket.send(m.data);
|
|
|
|
socket.close(1001);
|
|
|
|
};
|
2023-11-22 06:11:20 -05:00
|
|
|
socket.onclose = () => doneDeferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return response;
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
|
|
|
const def = Promise.withResolvers<void>();
|
2023-05-22 15:35:59 -04:00
|
|
|
const ws = new WebSocket(`ws://localhost:${servePort}`);
|
2022-08-18 08:05:02 -04:00
|
|
|
ws.onmessage = (m) => assertEquals(m.data, "foo");
|
2023-04-22 13:48:21 -04:00
|
|
|
ws.onerror = (e) => {
|
|
|
|
console.error(e);
|
|
|
|
fail();
|
|
|
|
};
|
2022-08-18 08:05:02 -04:00
|
|
|
ws.onclose = () => def.resolve();
|
|
|
|
ws.onopen = () => ws.send("foo");
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await def.promise;
|
|
|
|
await doneDeferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
});
|
|
|
|
|
2023-04-26 18:58:18 -04:00
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerWebSocketRaw() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2023-04-26 18:58:18 -04:00
|
|
|
const server = Deno.serve({
|
|
|
|
handler: async (request) => {
|
|
|
|
const { conn, response } = upgradeHttpRaw(request);
|
|
|
|
const buf = new Uint8Array(1024);
|
|
|
|
let read;
|
|
|
|
|
|
|
|
// Write our fake HTTP upgrade
|
|
|
|
await conn.write(
|
|
|
|
new TextEncoder().encode(
|
|
|
|
"HTTP/1.1 101 Switching Protocols\r\nConnection: Upgraded\r\n\r\nExtra",
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2023-04-26 18:58:18 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(resolve),
|
2023-04-26 18:58:18 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2023-04-26 18:58:18 -04:00
|
|
|
|
2023-05-22 15:35:59 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2023-04-26 18:58:18 -04:00
|
|
|
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();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2023-04-26 18:58:18 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-04-23 11:59:46 -04:00
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerWebSocketUpgradeTwice() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const done = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2023-04-23 11:59:46 -04:00
|
|
|
const server = Deno.serve({
|
2023-05-17 22:00:17 -04:00
|
|
|
handler: (request) => {
|
2023-04-23 11:59:46 -04:00
|
|
|
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);
|
|
|
|
};
|
2023-09-16 01:48:31 -04:00
|
|
|
socket.onclose = () => done.resolve();
|
2023-04-23 11:59:46 -04:00
|
|
|
return response;
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2023-04-23 11:59:46 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2023-04-23 11:59:46 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
|
|
|
const def = Promise.withResolvers<void>();
|
2023-05-22 15:35:59 -04:00
|
|
|
const ws = new WebSocket(`ws://localhost:${servePort}`);
|
2023-04-23 11:59:46 -04:00
|
|
|
ws.onmessage = (m) => assertEquals(m.data, "foo");
|
|
|
|
ws.onerror = (e) => {
|
|
|
|
console.error(e);
|
|
|
|
fail();
|
|
|
|
};
|
|
|
|
ws.onclose = () => def.resolve();
|
|
|
|
ws.onopen = () => ws.send("foo");
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await def.promise;
|
|
|
|
await done.promise;
|
2023-04-23 11:59:46 -04:00
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2023-04-23 11:59:46 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerWebSocketCloseFast() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const done = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2023-04-23 11:59:46 -04:00
|
|
|
const server = Deno.serve({
|
2023-05-17 22:00:17 -04:00
|
|
|
handler: (request) => {
|
2023-04-23 11:59:46 -04:00
|
|
|
const {
|
|
|
|
response,
|
|
|
|
socket,
|
|
|
|
} = Deno.upgradeWebSocket(request);
|
|
|
|
socket.onopen = () => socket.close();
|
2023-09-16 01:48:31 -04:00
|
|
|
socket.onclose = () => done.resolve();
|
2023-04-23 11:59:46 -04:00
|
|
|
return response;
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2023-04-23 11:59:46 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2023-04-23 11:59:46 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
|
|
|
const def = Promise.withResolvers<void>();
|
2023-05-22 15:35:59 -04:00
|
|
|
const ws = new WebSocket(`ws://localhost:${servePort}`);
|
2023-04-23 11:59:46 -04:00
|
|
|
ws.onerror = (e) => {
|
|
|
|
console.error(e);
|
|
|
|
fail();
|
|
|
|
};
|
|
|
|
ws.onclose = () => def.resolve();
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await def.promise;
|
|
|
|
await done.promise;
|
2023-04-23 11:59:46 -04:00
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2023-04-23 11:59:46 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-04-22 13:48:21 -04:00
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerWebSocketCanAccessRequest() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const done = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2023-04-22 13:48:21 -04:00
|
|
|
const server = Deno.serve({
|
2023-05-17 22:00:17 -04:00
|
|
|
handler: (request) => {
|
2023-04-22 13:48:21 -04:00
|
|
|
const {
|
|
|
|
response,
|
|
|
|
socket,
|
|
|
|
} = Deno.upgradeWebSocket(request);
|
|
|
|
socket.onerror = (e) => {
|
|
|
|
console.error(e);
|
|
|
|
fail();
|
|
|
|
};
|
2023-05-17 22:00:17 -04:00
|
|
|
socket.onmessage = (_m) => {
|
2023-04-22 13:48:21 -04:00
|
|
|
socket.send(request.url.toString());
|
|
|
|
socket.close(1001);
|
|
|
|
};
|
2023-09-16 01:48:31 -04:00
|
|
|
socket.onclose = () => done.resolve();
|
2023-04-22 13:48:21 -04:00
|
|
|
return response;
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2023-04-22 13:48:21 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2023-04-22 13:48:21 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
|
|
|
const def = Promise.withResolvers<void>();
|
2023-05-22 15:35:59 -04:00
|
|
|
const ws = new WebSocket(`ws://localhost:${servePort}`);
|
|
|
|
ws.onmessage = (m) =>
|
|
|
|
assertEquals(m.data, `http://localhost:${servePort}/`);
|
2023-04-22 13:48:21 -04:00
|
|
|
ws.onerror = (e) => {
|
|
|
|
console.error(e);
|
|
|
|
fail();
|
|
|
|
};
|
|
|
|
ws.onclose = () => def.resolve();
|
|
|
|
ws.onopen = () => ws.send("foo");
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await def.promise;
|
|
|
|
await done.promise;
|
2023-04-22 13:48:21 -04:00
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2023-04-22 13:48:21 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2022-08-18 08:05:02 -04:00
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpVeryLargeRequest() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
|
|
|
let headers: Headers;
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2023-05-17 22:00:17 -04:00
|
|
|
handler: (request) => {
|
2022-08-19 08:36:01 -04:00
|
|
|
headers = request.headers;
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response("");
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-22 15:35:59 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
// 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);
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
conn.close();
|
|
|
|
assertEquals(headers!.get("content-length"), "5");
|
|
|
|
assertEquals(headers!.get("something-else"), smthElse);
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpVeryLargeRequestAndBody() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
|
|
|
let headers: Headers;
|
|
|
|
let text: string;
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: async (request) => {
|
2022-08-19 08:36:01 -04:00
|
|
|
headers = request.headers;
|
|
|
|
text = await request.text();
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response("");
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-22 15:35:59 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
conn.close();
|
|
|
|
|
|
|
|
assertEquals(headers!.get("content-length"), `${reqBody.length}`);
|
|
|
|
assertEquals(headers!.get("something-else"), smthElse);
|
|
|
|
assertEquals(text!, reqBody);
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpConnectionClose() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response("");
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-22 15:35:59 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
// 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);
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
conn.close();
|
|
|
|
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
fix(ext/http): ensure request body resource lives as long as response is alive (#20206)
Deno.serve's fast streaming implementation was not keeping the request
body resource ID alive. We were taking the `Rc<Resource>` from the
resource table during the response, so a hairpin duplex response that
fed back the request body would work.
However, if any JS code attempted to read from the request body (which
requires the resource ID to be valid), the response would fail with a
difficult-to-diagnose "EOF" error.
This was affecting more complex duplex uses of `Deno.fetch` (though as
far as I can tell was unreported).
Simple test:
```ts
const reader = request.body.getReader();
return new Response(
new ReadableStream({
async pull(controller) {
const { done, value } = await reader.read();
if (done) {
controller.close();
} else {
controller.enqueue(value);
}
},
}),
```
And then attempt to use the stream in duplex mode:
```ts
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);
}
```
In older versions of Deno, this would just lock up. I believe after
23ff0e722e3c4b0827940853c53c5ee2ede5ec9f, it started throwing a more
explicit error:
```
httpServerStreamDuplexJavascript => ./cli/tests/unit/serve_test.ts:1339:6
error: TypeError: request or response body error: error reading a body from connection: Connection reset by peer (os error 54)
at async Object.pull (ext:deno_web/06_streams.js:810:27)
```
2023-08-20 21:35:26 -04:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2023-04-22 13:48:21 -04:00
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
fix(ext/http): ensure request body resource lives as long as response is alive (#20206)
Deno.serve's fast streaming implementation was not keeping the request
body resource ID alive. We were taking the `Rc<Resource>` from the
resource table during the response, so a hairpin duplex response that
fed back the request body would work.
However, if any JS code attempted to read from the request body (which
requires the resource ID to be valid), the response would fail with a
difficult-to-diagnose "EOF" error.
This was affecting more complex duplex uses of `Deno.fetch` (though as
far as I can tell was unreported).
Simple test:
```ts
const reader = request.body.getReader();
return new Response(
new ReadableStream({
async pull(controller) {
const { done, value } = await reader.read();
if (done) {
controller.close();
} else {
controller.enqueue(value);
}
},
}),
```
And then attempt to use the stream in duplex mode:
```ts
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);
}
```
In older versions of Deno, this would just lock up. I believe after
23ff0e722e3c4b0827940853c53c5ee2ede5ec9f, it started throwing a more
explicit error:
```
httpServerStreamDuplexJavascript => ./cli/tests/unit/serve_test.ts:1339:6
error: TypeError: request or response body error: error reading a body from connection: Connection reset by peer (os error 54)
at async Object.pull (ext:deno_web/06_streams.js:810:27)
```
2023-08-20 21:35:26 -04:00
|
|
|
async function httpServerStreamDuplexDirect() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2023-04-22 13:48:21 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
2023-04-26 08:54:03 -04:00
|
|
|
const server = Deno.serve(
|
2023-05-22 15:35:59 -04:00
|
|
|
{ port: servePort, signal: ac.signal },
|
fix(ext/http): ensure request body resource lives as long as response is alive (#20206)
Deno.serve's fast streaming implementation was not keeping the request
body resource ID alive. We were taking the `Rc<Resource>` from the
resource table during the response, so a hairpin duplex response that
fed back the request body would work.
However, if any JS code attempted to read from the request body (which
requires the resource ID to be valid), the response would fail with a
difficult-to-diagnose "EOF" error.
This was affecting more complex duplex uses of `Deno.fetch` (though as
far as I can tell was unreported).
Simple test:
```ts
const reader = request.body.getReader();
return new Response(
new ReadableStream({
async pull(controller) {
const { done, value } = await reader.read();
if (done) {
controller.close();
} else {
controller.enqueue(value);
}
},
}),
```
And then attempt to use the stream in duplex mode:
```ts
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);
}
```
In older versions of Deno, this would just lock up. I believe after
23ff0e722e3c4b0827940853c53c5ee2ede5ec9f, it started throwing a more
explicit error:
```
httpServerStreamDuplexJavascript => ./cli/tests/unit/serve_test.ts:1339:6
error: TypeError: request or response body error: error reading a body from connection: Connection reset by peer (os error 54)
at async Object.pull (ext:deno_web/06_streams.js:810:27)
```
2023-08-20 21:35:26 -04:00
|
|
|
(request: Request) => {
|
2023-04-26 08:54:03 -04:00
|
|
|
assert(request.body);
|
2023-11-22 06:11:20 -05:00
|
|
|
resolve();
|
2023-04-26 08:54:03 -04:00
|
|
|
return new Response(request.body);
|
|
|
|
},
|
|
|
|
);
|
2023-04-22 13:48:21 -04:00
|
|
|
|
fix(ext/http): ensure request body resource lives as long as response is alive (#20206)
Deno.serve's fast streaming implementation was not keeping the request
body resource ID alive. We were taking the `Rc<Resource>` from the
resource table during the response, so a hairpin duplex response that
fed back the request body would work.
However, if any JS code attempted to read from the request body (which
requires the resource ID to be valid), the response would fail with a
difficult-to-diagnose "EOF" error.
This was affecting more complex duplex uses of `Deno.fetch` (though as
far as I can tell was unreported).
Simple test:
```ts
const reader = request.body.getReader();
return new Response(
new ReadableStream({
async pull(controller) {
const { done, value } = await reader.read();
if (done) {
controller.close();
} else {
controller.enqueue(value);
}
},
}),
```
And then attempt to use the stream in duplex mode:
```ts
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);
}
```
In older versions of Deno, this would just lock up. I believe after
23ff0e722e3c4b0827940853c53c5ee2ede5ec9f, it started throwing a more
explicit error:
```
httpServerStreamDuplexJavascript => ./cli/tests/unit/serve_test.ts:1339:6
error: TypeError: request or response body error: error reading a body from connection: Connection reset by peer (os error 54)
at async Object.pull (ext:deno_web/06_streams.js:810:27)
```
2023-08-20 21:35:26 -04:00
|
|
|
const { readable, writable } = new TransformStream();
|
2023-05-22 15:35:59 -04:00
|
|
|
const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
|
2023-04-22 13:48:21 -04:00
|
|
|
method: "POST",
|
fix(ext/http): ensure request body resource lives as long as response is alive (#20206)
Deno.serve's fast streaming implementation was not keeping the request
body resource ID alive. We were taking the `Rc<Resource>` from the
resource table during the response, so a hairpin duplex response that
fed back the request body would work.
However, if any JS code attempted to read from the request body (which
requires the resource ID to be valid), the response would fail with a
difficult-to-diagnose "EOF" error.
This was affecting more complex duplex uses of `Deno.fetch` (though as
far as I can tell was unreported).
Simple test:
```ts
const reader = request.body.getReader();
return new Response(
new ReadableStream({
async pull(controller) {
const { done, value } = await reader.read();
if (done) {
controller.close();
} else {
controller.enqueue(value);
}
},
}),
```
And then attempt to use the stream in duplex mode:
```ts
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);
}
```
In older versions of Deno, this would just lock up. I believe after
23ff0e722e3c4b0827940853c53c5ee2ede5ec9f, it started throwing a more
explicit error:
```
httpServerStreamDuplexJavascript => ./cli/tests/unit/serve_test.ts:1339:6
error: TypeError: request or response body error: error reading a body from connection: Connection reset by peer (os error 54)
at async Object.pull (ext:deno_web/06_streams.js:810:27)
```
2023-08-20 21:35:26 -04:00
|
|
|
body: readable,
|
2023-04-22 13:48:21 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
|
|
|
assert(resp.body);
|
fix(ext/http): ensure request body resource lives as long as response is alive (#20206)
Deno.serve's fast streaming implementation was not keeping the request
body resource ID alive. We were taking the `Rc<Resource>` from the
resource table during the response, so a hairpin duplex response that
fed back the request body would work.
However, if any JS code attempted to read from the request body (which
requires the resource ID to be valid), the response would fail with a
difficult-to-diagnose "EOF" error.
This was affecting more complex duplex uses of `Deno.fetch` (though as
far as I can tell was unreported).
Simple test:
```ts
const reader = request.body.getReader();
return new Response(
new ReadableStream({
async pull(controller) {
const { done, value } = await reader.read();
if (done) {
controller.close();
} else {
controller.enqueue(value);
}
},
}),
```
And then attempt to use the stream in duplex mode:
```ts
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);
}
```
In older versions of Deno, this would just lock up. I believe after
23ff0e722e3c4b0827940853c53c5ee2ede5ec9f, it started throwing a more
explicit error:
```
httpServerStreamDuplexJavascript => ./cli/tests/unit/serve_test.ts:1339:6
error: TypeError: request or response body error: error reading a body from connection: Connection reset by peer (os error 54)
at async Object.pull (ext:deno_web/06_streams.js:810:27)
```
2023-08-20 21:35:26 -04:00
|
|
|
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() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
fix(ext/http): ensure request body resource lives as long as response is alive (#20206)
Deno.serve's fast streaming implementation was not keeping the request
body resource ID alive. We were taking the `Rc<Resource>` from the
resource table during the response, so a hairpin duplex response that
fed back the request body would work.
However, if any JS code attempted to read from the request body (which
requires the resource ID to be valid), the response would fail with a
difficult-to-diagnose "EOF" error.
This was affecting more complex duplex uses of `Deno.fetch` (though as
far as I can tell was unreported).
Simple test:
```ts
const reader = request.body.getReader();
return new Response(
new ReadableStream({
async pull(controller) {
const { done, value } = await reader.read();
if (done) {
controller.close();
} else {
controller.enqueue(value);
}
},
}),
```
And then attempt to use the stream in duplex mode:
```ts
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);
}
```
In older versions of Deno, this would just lock up. I believe after
23ff0e722e3c4b0827940853c53c5ee2ede5ec9f, it started throwing a more
explicit error:
```
httpServerStreamDuplexJavascript => ./cli/tests/unit/serve_test.ts:1339:6
error: TypeError: request or response body error: error reading a body from connection: Connection reset by peer (os error 54)
at async Object.pull (ext:deno_web/06_streams.js:810:27)
```
2023-08-20 21:35:26 -04:00
|
|
|
const ac = new AbortController();
|
2023-04-22 13:48:21 -04:00
|
|
|
|
fix(ext/http): ensure request body resource lives as long as response is alive (#20206)
Deno.serve's fast streaming implementation was not keeping the request
body resource ID alive. We were taking the `Rc<Resource>` from the
resource table during the response, so a hairpin duplex response that
fed back the request body would work.
However, if any JS code attempted to read from the request body (which
requires the resource ID to be valid), the response would fail with a
difficult-to-diagnose "EOF" error.
This was affecting more complex duplex uses of `Deno.fetch` (though as
far as I can tell was unreported).
Simple test:
```ts
const reader = request.body.getReader();
return new Response(
new ReadableStream({
async pull(controller) {
const { done, value } = await reader.read();
if (done) {
controller.close();
} else {
controller.enqueue(value);
}
},
}),
```
And then attempt to use the stream in duplex mode:
```ts
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);
}
```
In older versions of Deno, this would just lock up. I believe after
23ff0e722e3c4b0827940853c53c5ee2ede5ec9f, it started throwing a more
explicit error:
```
httpServerStreamDuplexJavascript => ./cli/tests/unit/serve_test.ts:1339:6
error: TypeError: request or response body error: error reading a body from connection: Connection reset by peer (os error 54)
at async Object.pull (ext:deno_web/06_streams.js:810:27)
```
2023-08-20 21:35:26 -04:00
|
|
|
const server = Deno.serve(
|
|
|
|
{ port: servePort, signal: ac.signal },
|
|
|
|
(request: Request) => {
|
|
|
|
assert(request.body);
|
2023-11-22 06:11:20 -05:00
|
|
|
resolve();
|
fix(ext/http): ensure request body resource lives as long as response is alive (#20206)
Deno.serve's fast streaming implementation was not keeping the request
body resource ID alive. We were taking the `Rc<Resource>` from the
resource table during the response, so a hairpin duplex response that
fed back the request body would work.
However, if any JS code attempted to read from the request body (which
requires the resource ID to be valid), the response would fail with a
difficult-to-diagnose "EOF" error.
This was affecting more complex duplex uses of `Deno.fetch` (though as
far as I can tell was unreported).
Simple test:
```ts
const reader = request.body.getReader();
return new Response(
new ReadableStream({
async pull(controller) {
const { done, value } = await reader.read();
if (done) {
controller.close();
} else {
controller.enqueue(value);
}
},
}),
```
And then attempt to use the stream in duplex mode:
```ts
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);
}
```
In older versions of Deno, this would just lock up. I believe after
23ff0e722e3c4b0827940853c53c5ee2ede5ec9f, it started throwing a more
explicit error:
```
httpServerStreamDuplexJavascript => ./cli/tests/unit/serve_test.ts:1339:6
error: TypeError: request or response body error: error reading a body from connection: Connection reset by peer (os error 54)
at async Object.pull (ext:deno_web/06_streams.js:810:27)
```
2023-08-20 21:35:26 -04:00
|
|
|
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());
|
2023-04-22 13:48:21 -04:00
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2023-04-22 13:48:21 -04:00
|
|
|
},
|
|
|
|
);
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
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.
|
2023-11-22 06:11:20 -05:00
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
|
|
|
const deferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
|
|
|
let counter = 0;
|
|
|
|
|
|
|
|
const deferreds = [
|
2023-11-22 06:11:20 -05:00
|
|
|
Promise.withResolvers<void>(),
|
|
|
|
Promise.withResolvers<void>(),
|
|
|
|
Promise.withResolvers<void>(),
|
2022-08-18 08:05:02 -04:00
|
|
|
];
|
|
|
|
|
|
|
|
async function writeRequest(conn: Deno.Conn) {
|
|
|
|
const encoder = new TextEncoder();
|
|
|
|
const decoder = new TextDecoder();
|
|
|
|
|
|
|
|
const w = new BufWriter(conn);
|
|
|
|
const r = new BufReader(conn);
|
2023-05-22 15:35:59 -04:00
|
|
|
const body = `GET / HTTP/1.1\r\nHost: 127.0.0.1:${servePort}\r\n\r\n`;
|
2022-08-18 08:05:02 -04:00
|
|
|
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);
|
2022-10-17 18:57:31 -04:00
|
|
|
const headers = await tpr.readMimeHeader();
|
2022-08-18 08:05:02 -04:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferreds[counter - 1].promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
controller.enqueue(`${counter}\n`);
|
|
|
|
counter++;
|
|
|
|
},
|
|
|
|
}).pipeThrough(new TextEncoderStream());
|
|
|
|
}
|
|
|
|
|
2023-06-13 14:05:23 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response(periodicStream());
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
// start a client
|
2023-05-22 15:35:59 -04:00
|
|
|
const clientConn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
const r1 = await writeRequest(clientConn);
|
|
|
|
assertEquals(r1, "0\n1\n2\n");
|
|
|
|
|
|
|
|
ac.abort();
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
clientConn.close();
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-08-23 19:03:05 -04:00
|
|
|
// 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();
|
|
|
|
const 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}/`);
|
2024-05-22 20:27:58 -04:00
|
|
|
const body = await response.bytes();
|
2023-08-23 19:03:05 -04:00
|
|
|
assertEquals(1024 * 1024, body.byteLength);
|
|
|
|
for (let i = 0; i < 256; i++) {
|
|
|
|
assertEquals(
|
|
|
|
i,
|
2024-05-22 20:27:58 -04:00
|
|
|
body[i * 4096],
|
2023-08-23 19:03:05 -04:00
|
|
|
`sentinel mismatch at index ${i * 4096}`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
ac.abort();
|
|
|
|
await server.finished;
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2022-08-18 08:05:02 -04:00
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpRequestLatin1Headers() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
|
|
|
const deferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: (request) => {
|
2022-08-19 08:36:01 -04:00
|
|
|
assertEquals(request.headers.get("X-Header-Test"), "á");
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response("hello", { headers: { "X-Header-Test": "Æ" } });
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-22 15:35:59 -04:00
|
|
|
const clientConn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
const requestText =
|
2023-05-22 15:35:59 -04:00
|
|
|
`GET / HTTP/1.1\r\nHost: 127.0.0.1:${servePort}\r\nX-Header-Test: á\r\n\r\n`;
|
2022-08-18 08:05:02 -04:00
|
|
|
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);
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2023-05-17 22:00:17 -04:00
|
|
|
const responseText = new TextDecoder("iso-8859-1").decode(buf);
|
2022-08-18 08:05:02 -04:00
|
|
|
clientConn.close();
|
|
|
|
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2023-04-22 13:48:21 -04:00
|
|
|
|
|
|
|
assertMatch(responseText, /\r\n[Xx]-[Hh]eader-[Tt]est: Æ\r\n/);
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerRequestWithoutPath() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: async (request) => {
|
2022-08-19 08:36:01 -04:00
|
|
|
// FIXME:
|
2023-05-22 15:35:59 -04:00
|
|
|
// assertEquals(new URL(request.url).href, `http://127.0.0.1:${servePort}/`);
|
2022-08-19 08:36:01 -04:00
|
|
|
assertEquals(await request.text(), "");
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response("11");
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-22 15:35:59 -04:00
|
|
|
const clientConn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
async function writeRequest(conn: Deno.Conn) {
|
|
|
|
const encoder = new TextEncoder();
|
|
|
|
|
|
|
|
const w = new BufWriter(conn);
|
|
|
|
const r = new BufReader(conn);
|
|
|
|
const body =
|
2023-05-22 15:35:59 -04:00
|
|
|
`CONNECT 127.0.0.1:${servePort} HTTP/1.1\r\nHost: 127.0.0.1:${servePort}\r\n\r\n`;
|
2022-08-18 08:05:02 -04:00
|
|
|
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");
|
2022-10-17 18:57:31 -04:00
|
|
|
const headers = await tpr.readMimeHeader();
|
2022-08-18 08:05:02 -04:00
|
|
|
assert(headers !== null);
|
|
|
|
}
|
|
|
|
|
|
|
|
await writeRequest(clientConn);
|
|
|
|
clientConn.close();
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpCookieConcatenation() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: async (request) => {
|
2022-08-19 08:36:01 -04:00
|
|
|
assertEquals(await request.text(), "");
|
2023-04-03 11:44:18 -04:00
|
|
|
assertEquals(request.headers.get("cookie"), "foo=bar; bar=foo");
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response("ok");
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
2023-04-04 10:17:36 -04:00
|
|
|
reusePort: true,
|
2022-08-18 08:05:02 -04:00
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-22 15:35:59 -04:00
|
|
|
const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
|
2022-08-18 08:05:02 -04:00
|
|
|
headers: [
|
|
|
|
["connection", "close"],
|
|
|
|
["cookie", "foo=bar"],
|
|
|
|
["cookie", "bar=foo"],
|
|
|
|
],
|
|
|
|
});
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
const text = await resp.text();
|
|
|
|
assertEquals(text, "ok");
|
|
|
|
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
// 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() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
|
|
|
const hostname = "localhost";
|
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response("ok");
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-22 15:35:59 -04:00
|
|
|
const url = `http://${hostname}:${servePort}/`;
|
2022-08-18 08:05:02 -04:00
|
|
|
const args = ["-X", "DELETE", url];
|
2022-12-02 08:43:17 -05:00
|
|
|
const { success } = await new Deno.Command("curl", {
|
2022-08-18 08:05:02 -04:00
|
|
|
args,
|
|
|
|
stdout: "null",
|
|
|
|
stderr: "null",
|
2022-12-02 08:43:17 -05:00
|
|
|
}).output();
|
2022-08-18 08:05:02 -04:00
|
|
|
assert(success);
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
ac.abort();
|
|
|
|
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
// FIXME:
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerRespondNonAsciiUint8Array() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2023-05-17 22:00:17 -04:00
|
|
|
handler: (request) => {
|
2022-08-19 08:36:01 -04:00
|
|
|
assertEquals(request.body, null);
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response(new Uint8Array([128]));
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.resolve;
|
2023-05-22 15:35:59 -04:00
|
|
|
const resp = await fetch(`http://localhost:${servePort}/`);
|
2022-08-18 08:05:02 -04:00
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
assertEquals(resp.status, 200);
|
2024-05-22 20:27:58 -04:00
|
|
|
const body = await resp.bytes();
|
|
|
|
assertEquals(body, new Uint8Array([128]));
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
// 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() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2023-05-17 22:00:17 -04:00
|
|
|
handler: (request) => {
|
2022-08-19 08:36:01 -04:00
|
|
|
assertEquals(request.method, "GET");
|
|
|
|
assertEquals(request.headers.get("host"), "deno.land");
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response("ok");
|
|
|
|
},
|
2023-05-24 14:36:41 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-24 14:36:41 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
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);
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
conn.close();
|
|
|
|
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerParseHeaderHtabs() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2023-05-17 22:00:17 -04:00
|
|
|
handler: (request) => {
|
2022-08-19 08:36:01 -04:00
|
|
|
assertEquals(request.method, "GET");
|
|
|
|
assertEquals(request.headers.get("server"), "hello\tworld");
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response("ok");
|
|
|
|
},
|
2023-05-24 14:36:41 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-24 14:36:41 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
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);
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
conn.close();
|
|
|
|
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerGetShouldIgnoreBody() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: async (request) => {
|
2022-08-19 08:36:01 -04:00
|
|
|
assertEquals(request.method, "GET");
|
|
|
|
assertEquals(await request.text(), "");
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response("ok");
|
|
|
|
},
|
2023-05-24 14:36:41 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-24 14:36:41 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
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);
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
conn.close();
|
|
|
|
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerPostWithBody() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: async (request) => {
|
2022-08-19 08:36:01 -04:00
|
|
|
assertEquals(request.method, "POST");
|
|
|
|
assertEquals(await request.text(), "I'm a good request.");
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response("ok");
|
|
|
|
},
|
2023-05-24 14:36:41 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-24 14:36:41 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
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);
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
conn.close();
|
|
|
|
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
type TestCase = {
|
|
|
|
headers?: Record<string, string>;
|
2023-05-17 22:00:17 -04:00
|
|
|
// deno-lint-ignore no-explicit-any
|
2022-08-18 08:05:02 -04:00
|
|
|
body: any;
|
2023-05-17 22:00:17 -04:00
|
|
|
expectsChunked?: boolean;
|
|
|
|
expectsConnLen?: boolean;
|
2022-08-18 08:05:02 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
function hasHeader(msg: string, name: string): boolean {
|
2023-05-17 22:00:17 -04:00
|
|
|
const n = msg.indexOf("\r\n\r\n") || msg.length;
|
2022-08-18 08:05:02 -04:00
|
|
|
return msg.slice(0, n).includes(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
function createServerLengthTest(name: string, testCase: TestCase) {
|
|
|
|
Deno.test(name, async function () {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2023-05-17 22:00:17 -04:00
|
|
|
handler: (request) => {
|
2022-08-19 08:36:01 -04:00
|
|
|
assertEquals(request.method, "GET");
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response(testCase.body, testCase.headers ?? {});
|
|
|
|
},
|
2023-05-24 14:36:41 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-24 14:36:41 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
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);
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
const decoder = new TextDecoder();
|
2022-09-01 09:28:34 -04:00
|
|
|
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));
|
|
|
|
try {
|
|
|
|
assert(
|
2023-05-17 22:00:17 -04:00
|
|
|
testCase.expectsChunked == hasHeader(msg, "Transfer-Encoding:"),
|
2022-09-01 09:28:34 -04:00
|
|
|
);
|
2023-05-17 22:00:17 -04:00
|
|
|
assert(testCase.expectsChunked == hasHeader(msg, "chunked"));
|
|
|
|
assert(testCase.expectsConnLen == hasHeader(msg, "Content-Length:"));
|
2022-08-18 08:05:02 -04:00
|
|
|
|
2022-09-01 09:28:34 -04:00
|
|
|
const n = msg.indexOf("\r\n\r\n") + 4;
|
2022-08-18 08:05:02 -04:00
|
|
|
|
2023-05-17 22:00:17 -04:00
|
|
|
if (testCase.expectsChunked) {
|
2022-09-01 09:28:34 -04:00
|
|
|
assertEquals(msg.slice(n + 1, n + 3), "\r\n");
|
|
|
|
assertEquals(msg.slice(msg.length - 7), "\r\n0\r\n\r\n");
|
|
|
|
}
|
2022-08-18 08:05:02 -04:00
|
|
|
|
2023-05-17 22:00:17 -04:00
|
|
|
if (testCase.expectsConnLen && typeof testCase.body === "string") {
|
2022-09-01 09:28:34 -04:00
|
|
|
assertEquals(msg.slice(n), testCase.body);
|
|
|
|
}
|
|
|
|
break;
|
2023-05-17 22:00:17 -04:00
|
|
|
} catch {
|
2022-09-01 09:28:34 -04:00
|
|
|
continue;
|
2022-08-18 08:05:02 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
conn.close();
|
|
|
|
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Quick and dirty way to make a readable stream from a string. Alternatively,
|
|
|
|
// `readableStreamFromReader(file)` could be used.
|
|
|
|
function stream(s: string): ReadableStream<Uint8Array> {
|
|
|
|
return new Response(s).body!;
|
|
|
|
}
|
|
|
|
|
|
|
|
createServerLengthTest("fixedResponseKnown", {
|
|
|
|
headers: { "content-length": "11" },
|
|
|
|
body: "foo bar baz",
|
2023-05-17 22:00:17 -04:00
|
|
|
expectsChunked: false,
|
|
|
|
expectsConnLen: true,
|
2022-08-18 08:05:02 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
createServerLengthTest("fixedResponseUnknown", {
|
|
|
|
headers: { "content-length": "11" },
|
|
|
|
body: stream("foo bar baz"),
|
2023-05-17 22:00:17 -04:00
|
|
|
expectsChunked: true,
|
|
|
|
expectsConnLen: false,
|
2022-08-18 08:05:02 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
createServerLengthTest("fixedResponseKnownEmpty", {
|
|
|
|
headers: { "content-length": "0" },
|
|
|
|
body: "",
|
2023-05-17 22:00:17 -04:00
|
|
|
expectsChunked: false,
|
|
|
|
expectsConnLen: true,
|
2022-08-18 08:05:02 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
createServerLengthTest("chunkedRespondKnown", {
|
|
|
|
headers: { "transfer-encoding": "chunked" },
|
|
|
|
body: "foo bar baz",
|
2023-05-17 22:00:17 -04:00
|
|
|
expectsChunked: false,
|
|
|
|
expectsConnLen: true,
|
2022-08-18 08:05:02 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
createServerLengthTest("chunkedRespondUnknown", {
|
|
|
|
headers: { "transfer-encoding": "chunked" },
|
|
|
|
body: stream("foo bar baz"),
|
2023-05-17 22:00:17 -04:00
|
|
|
expectsChunked: true,
|
|
|
|
expectsConnLen: false,
|
2022-08-18 08:05:02 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
createServerLengthTest("autoResponseWithKnownLength", {
|
|
|
|
body: "foo bar baz",
|
2023-05-17 22:00:17 -04:00
|
|
|
expectsChunked: false,
|
|
|
|
expectsConnLen: true,
|
2022-08-18 08:05:02 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
createServerLengthTest("autoResponseWithUnknownLength", {
|
|
|
|
body: stream("foo bar baz"),
|
2023-05-17 22:00:17 -04:00
|
|
|
expectsChunked: true,
|
|
|
|
expectsConnLen: false,
|
2022-08-18 08:05:02 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
createServerLengthTest("autoResponseWithKnownLengthEmpty", {
|
|
|
|
body: "",
|
2023-05-17 22:00:17 -04:00
|
|
|
expectsChunked: false,
|
|
|
|
expectsConnLen: true,
|
2022-08-18 08:05:02 -04:00
|
|
|
});
|
|
|
|
|
2023-04-22 13:48:21 -04:00
|
|
|
createServerLengthTest("autoResponseWithUnknownLengthEmpty", {
|
|
|
|
body: stream(""),
|
2023-05-17 22:00:17 -04:00
|
|
|
expectsChunked: true,
|
|
|
|
expectsConnLen: false,
|
2023-04-22 13:48:21 -04:00
|
|
|
});
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerPostWithContentLengthBody() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: async (request) => {
|
2022-08-19 08:36:01 -04:00
|
|
|
assertEquals(request.method, "POST");
|
|
|
|
assertEquals(request.headers.get("content-length"), "5");
|
|
|
|
assertEquals(await request.text(), "hello");
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response("ok");
|
|
|
|
},
|
2023-05-24 14:36:41 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-24 14:36:41 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
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);
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
conn.close();
|
|
|
|
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerPostWithInvalidPrefixContentLength() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: () => {
|
2022-08-19 08:36:01 -04:00
|
|
|
throw new Error("unreachable");
|
|
|
|
},
|
2023-05-24 14:36:41 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2023-05-24 14:36:41 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
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));
|
2023-04-03 11:44:18 -04:00
|
|
|
assert(msg.includes("HTTP/1.1 400 Bad Request"));
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
conn.close();
|
|
|
|
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerPostWithChunkedBody() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: async (request) => {
|
2022-08-19 08:36:01 -04:00
|
|
|
assertEquals(request.method, "POST");
|
|
|
|
assertEquals(await request.text(), "qwert");
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response("ok");
|
|
|
|
},
|
2023-05-24 14:36:41 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-24 14:36:41 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
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);
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
conn.close();
|
|
|
|
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerPostWithIncompleteBody() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: async (r) => {
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
assertEquals(await r.text(), "12345");
|
|
|
|
return new Response("ok");
|
|
|
|
},
|
2023-05-24 14:36:41 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-24 14:36:41 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
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);
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
conn.close();
|
|
|
|
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerHeadResponseDoesntSendBody() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2023-02-12 05:43:05 -05:00
|
|
|
return new Response("NaN".repeat(100));
|
2022-08-19 08:36:01 -04:00
|
|
|
},
|
2023-05-24 14:36:41 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-24 14:36:41 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
const encoder = new TextEncoder();
|
|
|
|
const decoder = new TextDecoder();
|
|
|
|
|
|
|
|
const body =
|
|
|
|
`HEAD / 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);
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
const buf = new Uint8Array(1024);
|
|
|
|
const readResult = await conn.read(buf);
|
|
|
|
assert(readResult);
|
|
|
|
const msg = decoder.decode(buf.subarray(0, readResult));
|
|
|
|
|
2023-04-03 11:44:18 -04:00
|
|
|
assert(msg.includes("content-length: 300\r\n"));
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
conn.close();
|
|
|
|
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-05-10 07:23:14 -04:00
|
|
|
function makeTempData(size: number) {
|
|
|
|
return new Uint8Array(size).fill(1);
|
|
|
|
}
|
2022-08-18 08:05:02 -04:00
|
|
|
|
2023-05-10 07:23:14 -04:00
|
|
|
async function makeTempFile(size: number) {
|
|
|
|
const tmpFile = await Deno.makeTempFile();
|
2024-01-21 18:20:59 -05:00
|
|
|
using file = await Deno.open(tmpFile, { write: true, read: true });
|
2023-05-10 07:23:14 -04:00
|
|
|
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,
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
2023-05-10 07:23:14 -04:00
|
|
|
// 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,
|
|
|
|
// },
|
2023-06-26 09:10:27 -04:00
|
|
|
{ name: "Incompressible", length: 1024, in: {}, out: {}, expect: null },
|
2023-05-10 07:23:14 -04:00
|
|
|
{
|
2023-06-26 09:10:27 -04:00
|
|
|
name: "IncompressibleAcceptGzip",
|
2023-05-10 07:23:14 -04:00
|
|
|
length: 1024,
|
|
|
|
in: { "Accept-Encoding": "gzip" },
|
|
|
|
out: {},
|
|
|
|
expect: null,
|
|
|
|
},
|
|
|
|
{
|
2023-06-26 09:10:27 -04:00
|
|
|
name: "IncompressibleType",
|
2023-05-10 07:23:14 -04:00
|
|
|
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: "gzip",
|
|
|
|
},
|
2023-05-24 13:54:47 -04:00
|
|
|
{
|
|
|
|
name: "CompressibleType3",
|
|
|
|
length: 1024,
|
|
|
|
in: { "Accept-Encoding": "br" },
|
|
|
|
out: { "Content-Type": "text/plain" },
|
|
|
|
expect: "br",
|
|
|
|
},
|
2023-05-10 07:23:14 -04:00
|
|
|
{
|
2023-06-26 09:10:27 -04:00
|
|
|
name: "IncompressibleRange",
|
2023-05-10 07:23:14 -04:00
|
|
|
length: 1024,
|
|
|
|
in: { "Accept-Encoding": "gzip" },
|
|
|
|
out: { "Content-Type": "text/plain", "Content-Range": "1" },
|
|
|
|
expect: null,
|
|
|
|
},
|
|
|
|
{
|
2023-06-26 09:10:27 -04:00
|
|
|
name: "IncompressibleCE",
|
2023-05-10 07:23:14 -04:00
|
|
|
length: 1024,
|
|
|
|
in: { "Accept-Encoding": "gzip" },
|
|
|
|
out: { "Content-Type": "text/plain", "Content-Encoding": "random" },
|
|
|
|
expect: null,
|
|
|
|
},
|
|
|
|
{
|
2023-06-26 09:10:27 -04:00
|
|
|
name: "IncompressibleCC",
|
2023-05-10 07:23:14 -04:00
|
|
|
length: 1024,
|
|
|
|
in: { "Accept-Encoding": "gzip" },
|
|
|
|
out: { "Content-Type": "text/plain", "Cache-Control": "no-transform" },
|
|
|
|
expect: null,
|
|
|
|
},
|
2024-01-26 10:33:55 -05:00
|
|
|
{
|
|
|
|
name: "BadHeader",
|
|
|
|
length: 1024,
|
|
|
|
in: { "Accept-Encoding": "\x81" },
|
|
|
|
out: { "Content-Type": "text/plain", "Cache-Control": "no-transform" },
|
|
|
|
expect: null,
|
|
|
|
},
|
2023-05-10 07:23:14 -04:00
|
|
|
];
|
|
|
|
|
|
|
|
for (const testCase of compressionTestCases) {
|
|
|
|
const name = `httpServerCompression${testCase.name}`;
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true, write: true, read: true } },
|
|
|
|
{
|
|
|
|
[name]: async function () {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2023-05-10 07:23:14 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
const server = Deno.serve({
|
2023-05-17 22:00:17 -04:00
|
|
|
handler: async (_request) => {
|
2023-05-10 07:23:14 -04:00
|
|
|
const f = await makeTempFile(testCase.length);
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2023-05-17 22:00:17 -04:00
|
|
|
// deno-lint-ignore no-explicit-any
|
2023-05-10 07:23:14 -04:00
|
|
|
const headers = testCase.out as any;
|
|
|
|
headers["Content-Length"] = testCase.length.toString();
|
|
|
|
return new Response(f.readable, {
|
|
|
|
headers: headers as HeadersInit,
|
|
|
|
});
|
|
|
|
},
|
2023-05-24 14:36:41 -04:00
|
|
|
port: servePort,
|
2023-05-10 07:23:14 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2023-05-10 07:23:14 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
try {
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-24 14:36:41 -04:00
|
|
|
const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
|
2023-05-10 07:23:14 -04:00
|
|
|
headers: testCase.in as HeadersInit,
|
|
|
|
});
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2024-05-22 20:27:58 -04:00
|
|
|
const body = await resp.bytes();
|
2023-05-10 07:23:14 -04:00
|
|
|
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") {
|
|
|
|
// Note the fetch will transparently decompress this response, BUT we can detect that a response
|
|
|
|
// was compressed by the lack of a content length.
|
|
|
|
assertEquals(body.byteLength, testCase.length);
|
|
|
|
assertEquals(resp.headers.get("content-encoding"), null);
|
|
|
|
assertEquals(resp.headers.get("content-length"), null);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2023-05-10 07:23:14 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
}[name],
|
|
|
|
);
|
|
|
|
}
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true, write: true, read: true } },
|
|
|
|
async function httpServerPostFile() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: async (request) => {
|
2023-05-10 07:23:14 -04:00
|
|
|
assertEquals(
|
2024-05-22 20:27:58 -04:00
|
|
|
await request.bytes(),
|
2023-05-10 07:23:14 -04:00
|
|
|
makeTempData(70 * 1024),
|
|
|
|
);
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response("ok");
|
|
|
|
},
|
2023-05-24 14:36:41 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-10 07:23:14 -04:00
|
|
|
const f = await makeTempFile(70 * 1024);
|
2023-05-24 14:36:41 -04:00
|
|
|
const response = await fetch(`http://localhost:${servePort}/`, {
|
2022-08-18 08:05:02 -04:00
|
|
|
method: "POST",
|
|
|
|
body: f.readable,
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
assertEquals(response.status, 200);
|
|
|
|
assertEquals(await response.text(), "ok");
|
|
|
|
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-11-18 15:16:53 -05:00
|
|
|
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();
|
2023-11-22 06:11:20 -05:00
|
|
|
const streamCancelled = url == "stream"
|
|
|
|
? Promise.withResolvers<void>()
|
|
|
|
: undefined;
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
|
|
|
const waitForAbort = Promise.withResolvers<void>();
|
|
|
|
const waitForRequest = Promise.withResolvers<void>();
|
2023-11-18 15:16:53 -05:00
|
|
|
const server = Deno.serve({
|
|
|
|
port: servePort,
|
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2023-11-18 15:16:53 -05:00
|
|
|
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();
|
2023-11-22 06:11:20 -05:00
|
|
|
await waitForAbort.promise;
|
2023-11-18 15:16:53 -05:00
|
|
|
|
|
|
|
if (delay == "delay") {
|
|
|
|
await new Promise((r) => setTimeout(r, 1000));
|
|
|
|
}
|
|
|
|
// Allocate the request body
|
|
|
|
req.body;
|
|
|
|
return new Response(respBody);
|
|
|
|
},
|
|
|
|
});
|
2023-08-28 15:29:34 -04:00
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-08-28 15:29:34 -04:00
|
|
|
|
2023-11-18 15:16:53 -05:00
|
|
|
// 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`),
|
|
|
|
);
|
2023-11-22 06:11:20 -05:00
|
|
|
await waitForRequest.promise;
|
2023-11-18 15:16:53 -05:00
|
|
|
await writer.close();
|
2023-08-28 15:29:34 -04:00
|
|
|
|
2023-11-18 15:16:53 -05:00
|
|
|
waitForAbort.resolve();
|
2023-08-28 15:29:34 -04:00
|
|
|
|
2023-11-18 15:16:53 -05:00
|
|
|
// Wait for cancellation before we shut the server down
|
|
|
|
if (streamCancelled !== undefined) {
|
|
|
|
await streamCancelled;
|
|
|
|
}
|
2023-09-25 11:23:55 -04:00
|
|
|
|
2023-11-18 15:16:53 -05:00
|
|
|
// 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();
|
2023-09-25 11:23:55 -04:00
|
|
|
|
2023-11-18 15:16:53 -05:00
|
|
|
await server.finished;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
2023-08-28 15:29:34 -04:00
|
|
|
}
|
|
|
|
|
2024-04-29 11:40:02 -04:00
|
|
|
// 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,
|
|
|
|
);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-09-25 11:23:55 -04:00
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerCancelFetch() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const request2 = Promise.withResolvers<void>();
|
|
|
|
const request2Aborted = Promise.withResolvers<string>();
|
2024-04-24 14:03:37 -04:00
|
|
|
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++;
|
|
|
|
};
|
2023-09-25 11:23:55 -04:00
|
|
|
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;
|
2023-11-22 06:11:20 -05:00
|
|
|
await request2.promise;
|
2023-09-25 11:23:55 -04:00
|
|
|
fetchAbort.abort();
|
2023-11-22 06:11:20 -05:00
|
|
|
assertEquals("resource closed", await request2Aborted.promise);
|
2023-09-25 11:23:55 -04:00
|
|
|
|
|
|
|
abort();
|
|
|
|
await finished;
|
2024-04-24 14:03:37 -04:00
|
|
|
assertEquals(completed, 2);
|
|
|
|
assertEquals(aborted, 2);
|
2023-09-25 11:23:55 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2024-04-24 14:27:15 -04:00
|
|
|
// 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";
|
|
|
|
|
|
|
|
const 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;
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2022-08-18 08:05:02 -04:00
|
|
|
Deno.test(
|
|
|
|
{ permissions: { read: true, net: true } },
|
|
|
|
async function httpServerWithTls() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const hostname = "127.0.0.1";
|
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: () => new Response("Hello World"),
|
2022-08-18 08:05:02 -04:00
|
|
|
hostname,
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
chore: move cli/tests/ -> tests/ (#22369)
This looks like a massive PR, but it's only a move from cli/tests ->
tests, and updates of relative paths for files.
This is the first step towards aggregate all of the integration test
files under tests/, which will lead to a set of integration tests that
can run without the CLI binary being built.
While we could leave these tests under `cli`, it would require us to
keep a more complex directory structure for the various test runners. In
addition, we have a lot of complexity to ignore various test files in
the `cli` project itself (cargo publish exclusion rules, autotests =
false, etc).
And finally, the `tests/` folder will eventually house the `test_ffi`,
`test_napi` and other testing code, reducing the size of the root repo
directory.
For easier review, the extremely large and noisy "move" is in the first
commit (with no changes -- just a move), while the remainder of the
changes to actual files is in the second commit.
2024-02-10 15:22:13 -05:00
|
|
|
cert: Deno.readTextFileSync("tests/testdata/tls/localhost.crt"),
|
|
|
|
key: Deno.readTextFileSync("tests/testdata/tls/localhost.key"),
|
2022-08-18 08:05:02 -04:00
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
chore: move cli/tests/ -> tests/ (#22369)
This looks like a massive PR, but it's only a move from cli/tests ->
tests, and updates of relative paths for files.
This is the first step towards aggregate all of the integration test
files under tests/, which will lead to a set of integration tests that
can run without the CLI binary being built.
While we could leave these tests under `cli`, it would require us to
keep a more complex directory structure for the various test runners. In
addition, we have a lot of complexity to ignore various test files in
the `cli` project itself (cargo publish exclusion rules, autotests =
false, etc).
And finally, the `tests/` folder will eventually house the `test_ffi`,
`test_napi` and other testing code, reducing the size of the root repo
directory.
For easier review, the extremely large and noisy "move" is in the first
commit (with no changes -- just a move), while the remainder of the
changes to actual files is in the second commit.
2024-02-10 15:22:13 -05:00
|
|
|
const caCert = Deno.readTextFileSync("tests/testdata/tls/RootCA.pem");
|
2022-08-18 08:05:02 -04:00
|
|
|
const client = Deno.createHttpClient({ caCerts: [caCert] });
|
2023-05-22 15:35:59 -04:00
|
|
|
const resp = await fetch(`https://localhost:${servePort}/`, {
|
2022-08-18 08:05:02 -04:00
|
|
|
client,
|
|
|
|
headers: { "connection": "close" },
|
|
|
|
});
|
|
|
|
|
|
|
|
const respBody = await resp.text();
|
|
|
|
assertEquals("Hello World", respBody);
|
|
|
|
|
|
|
|
client.close();
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true, write: true, read: true } },
|
|
|
|
async function httpServerRequestCLTE() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
|
|
|
const deferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: async (req) => {
|
2022-08-19 08:36:01 -04:00
|
|
|
assertEquals(await req.text(), "");
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response("ok");
|
|
|
|
},
|
2023-05-24 14:36:41 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-24 14:36:41 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
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);
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
conn.close();
|
|
|
|
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true, write: true, read: true } },
|
|
|
|
async function httpServerRequestTETE() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: () => {
|
2022-08-19 08:36:01 -04:00
|
|
|
throw new Error("oops");
|
|
|
|
},
|
2023-05-24 14:36:41 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
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",
|
|
|
|
];
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
for (const teHeader of variations) {
|
2023-05-24 14:36:41 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
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));
|
2023-04-03 11:44:18 -04:00
|
|
|
assert(msg.includes("HTTP/1.1 400 Bad Request\r\n"));
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
conn.close();
|
|
|
|
}
|
|
|
|
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2022-10-05 02:51:59 -04:00
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServer204ResponseDoesntSendContentLength() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2022-10-05 02:51:59 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
const server = Deno.serve({
|
|
|
|
handler: (_request) => new Response(null, { status: 204 }),
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-10-05 02:51:59 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(resolve),
|
2022-10-05 02:51:59 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2023-05-22 15:35:59 -04:00
|
|
|
const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
|
2022-10-05 02:51:59 -04:00
|
|
|
method: "GET",
|
|
|
|
headers: { "connection": "close" },
|
|
|
|
});
|
2023-04-22 13:48:21 -04:00
|
|
|
assertEquals(resp.status, 204);
|
2022-10-05 02:51:59 -04:00
|
|
|
assertEquals(resp.headers.get("Content-Length"), null);
|
|
|
|
} finally {
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-10-05 02:51:59 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2022-08-18 08:05:02 -04:00
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServer304ResponseDoesntSendBody() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: () => {
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response(null, { status: 304 });
|
|
|
|
},
|
2023-05-24 14:36:41 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-24 14:36:41 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
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);
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
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"));
|
2022-08-24 15:26:57 -04:00
|
|
|
assert(msg.endsWith("\r\n\r\n"));
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
conn.close();
|
|
|
|
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerExpectContinue() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: async (req) => {
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
assertEquals(await req.text(), "hello");
|
|
|
|
return new Response(null, { status: 304 });
|
|
|
|
},
|
2023-05-24 14:36:41 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-24 14:36:41 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
{
|
|
|
|
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();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerExpectContinueButNoBodyLOL() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: async (req) => {
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2022-08-19 08:36:01 -04:00
|
|
|
assertEquals(await req.text(), "");
|
|
|
|
return new Response(null, { status: 304 });
|
|
|
|
},
|
2023-05-24 14:36:41 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-05-24 14:36:41 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2022-08-18 08:05:02 -04:00
|
|
|
|
|
|
|
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();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
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();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2022-08-18 08:05:02 -04:00
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2022-08-23 18:08:56 -04:00
|
|
|
handler: () => {
|
2022-08-19 08:36:01 -04:00
|
|
|
throw new Error("oops");
|
|
|
|
},
|
2023-05-24 14:36:41 -04:00
|
|
|
port: servePort,
|
2022-08-18 08:05:02 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(resolve),
|
2022-08-18 08:05:02 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2023-05-24 14:36:41 -04:00
|
|
|
const conn = await Deno.connect({ port: servePort });
|
2022-08-18 08:05:02 -04:00
|
|
|
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();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-18 08:05:02 -04:00
|
|
|
},
|
|
|
|
}[name];
|
|
|
|
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
testFn,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-08-19 00:44:56 -04:00
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerConcurrentRequests() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { resolve } = Promise.withResolvers<void>();
|
2022-08-19 00:44:56 -04:00
|
|
|
|
|
|
|
let reqCount = -1;
|
|
|
|
let timerId: number | undefined;
|
2022-08-19 08:36:01 -04:00
|
|
|
const server = Deno.serve({
|
2023-05-17 22:00:17 -04:00
|
|
|
handler: (_req) => {
|
2022-08-19 08:36:01 -04:00
|
|
|
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 (typeof timerId === "number") {
|
|
|
|
clearInterval(timerId);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
|
|
|
return new Response(body, {
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "text/event-stream",
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
2022-08-19 00:44:56 -04:00
|
|
|
|
2022-08-19 08:36:01 -04:00
|
|
|
return new Response(`hello ${reqCount}`);
|
|
|
|
},
|
2023-05-24 14:36:41 -04:00
|
|
|
port: servePort,
|
2022-08-19 00:44:56 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(resolve),
|
2022-08-19 00:44:56 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
2023-05-24 14:36:41 -04:00
|
|
|
const sseRequest = await fetch(`http://localhost:${servePort}/`);
|
2022-08-19 00:44:56 -04:00
|
|
|
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
2023-05-24 14:36:41 -04:00
|
|
|
const helloRequest = await fetch(`http://localhost:${servePort}/`);
|
2022-08-19 00:44:56 -04:00
|
|
|
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();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-08-19 00:44:56 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2022-10-29 05:25:23 -04:00
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
2023-03-13 06:24:31 -04:00
|
|
|
async function serveWithPrototypePollution() {
|
2022-10-29 05:25:23 -04:00
|
|
|
const originalThen = Promise.prototype.then;
|
2023-03-13 06:24:31 -04:00
|
|
|
const originalSymbolIterator = Array.prototype[Symbol.iterator];
|
2022-10-29 05:25:23 -04:00
|
|
|
try {
|
2023-03-13 06:24:31 -04:00
|
|
|
Promise.prototype.then = Array.prototype[Symbol.iterator] = () => {
|
2022-10-29 05:25:23 -04:00
|
|
|
throw new Error();
|
|
|
|
};
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { resolve } = Promise.withResolvers<void>();
|
2022-10-29 05:25:23 -04:00
|
|
|
const server = Deno.serve({
|
|
|
|
handler: (_req) => new Response("ok"),
|
|
|
|
hostname: "localhost",
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2022-10-29 05:25:23 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(resolve),
|
2022-10-29 05:25:23 -04:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-10-29 05:25:23 -04:00
|
|
|
} finally {
|
|
|
|
Promise.prototype.then = originalThen;
|
2023-03-13 06:24:31 -04:00
|
|
|
Array.prototype[Symbol.iterator] = originalSymbolIterator;
|
2022-10-29 05:25:23 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2022-09-28 14:52:24 -04:00
|
|
|
// https://github.com/denoland/deno/issues/15549
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function testIssue15549() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2022-09-28 14:52:24 -04:00
|
|
|
let count = 0;
|
2023-04-26 08:54:03 -04:00
|
|
|
const server = Deno.serve({
|
2023-04-22 13:48:21 -04:00
|
|
|
async onListen({ port }: { port: number }) {
|
2023-09-26 06:46:06 -04:00
|
|
|
const res1 = await fetch(`http://localhost:${port}/`);
|
2022-09-28 14:52:24 -04:00
|
|
|
assertEquals(await res1.text(), "hello world 1");
|
|
|
|
|
2023-09-26 06:46:06 -04:00
|
|
|
const res2 = await fetch(`http://localhost:${port}/`);
|
2022-09-28 14:52:24 -04:00
|
|
|
assertEquals(await res2.text(), "hello world 2");
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
resolve();
|
2022-09-28 14:52:24 -04:00
|
|
|
ac.abort();
|
|
|
|
},
|
|
|
|
signal: ac.signal,
|
2023-04-26 08:54:03 -04:00
|
|
|
}, () => {
|
|
|
|
count++;
|
|
|
|
return new Response(`hello world ${count}`);
|
2022-09-28 14:52:24 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
await promise;
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-09-28 14:52:24 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-01-14 23:08:34 -05:00
|
|
|
// https://github.com/denoland/deno/issues/15858
|
|
|
|
Deno.test(
|
2023-07-30 09:13:28 -04:00
|
|
|
"Clone should work",
|
2023-01-14 23:08:34 -05:00
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function httpServerCanCloneRequest() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<number>();
|
2023-01-14 23:08:34 -05:00
|
|
|
|
|
|
|
const server = Deno.serve({
|
|
|
|
handler: async (req) => {
|
|
|
|
const cloned = req.clone();
|
|
|
|
assertEquals(req.headers, cloned.headers);
|
|
|
|
|
2023-07-30 09:13:28 -04:00
|
|
|
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);
|
|
|
|
|
2023-01-14 23:08:34 -05:00
|
|
|
// both requests can read body
|
|
|
|
await req.text();
|
|
|
|
await cloned.json();
|
|
|
|
|
|
|
|
return new Response("ok");
|
|
|
|
},
|
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: ({ port }: { port: number }) => resolve(port),
|
2023-01-14 23:08:34 -05:00
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
2023-11-22 06:11:20 -05:00
|
|
|
const port = await promise;
|
2023-04-22 13:48:21 -04:00
|
|
|
const resp = await fetch(`http://localhost:${port}/`, {
|
2023-01-14 23:08:34 -05:00
|
|
|
headers: { connection: "close" },
|
|
|
|
method: "POST",
|
|
|
|
body: '{"sus":true}',
|
|
|
|
});
|
|
|
|
const text = await resp.text();
|
|
|
|
assertEquals(text, "ok");
|
|
|
|
} finally {
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2023-01-14 23:08:34 -05:00
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-07-30 09:13:28 -04:00
|
|
|
// https://fetch.spec.whatwg.org/#dom-request-clone
|
|
|
|
Deno.test(
|
|
|
|
"Throw if disturbed",
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function shouldThrowIfBodyIsUnusableDisturbed() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<number>();
|
2023-07-30 09:13:28 -04:00
|
|
|
|
|
|
|
const server = Deno.serve({
|
|
|
|
handler: async (req) => {
|
|
|
|
await req.text();
|
|
|
|
|
|
|
|
try {
|
|
|
|
req.clone();
|
2023-08-01 14:24:42 -04:00
|
|
|
fail();
|
|
|
|
} catch (cloneError) {
|
|
|
|
assert(cloneError instanceof TypeError);
|
2023-07-30 09:13:28 -04:00
|
|
|
assert(
|
2023-08-01 14:24:42 -04:00
|
|
|
cloneError.message.endsWith("Body is unusable."),
|
2023-07-30 09:13:28 -04:00
|
|
|
);
|
|
|
|
|
|
|
|
ac.abort();
|
|
|
|
await server.finished;
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Response("ok");
|
|
|
|
},
|
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: ({ port }: { port: number }) => resolve(port),
|
2023-07-30 09:13:28 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
2023-11-22 06:11:20 -05:00
|
|
|
const port = await promise;
|
2023-07-30 09:13:28 -04:00
|
|
|
await fetch(`http://localhost:${port}/`, {
|
|
|
|
headers: { connection: "close" },
|
|
|
|
method: "POST",
|
|
|
|
body: '{"bar":true}',
|
|
|
|
});
|
2023-08-01 14:24:42 -04:00
|
|
|
fail();
|
|
|
|
} catch (clientError) {
|
|
|
|
assert(clientError instanceof TypeError);
|
2024-07-01 20:09:47 -04:00
|
|
|
assert(clientError.message.includes("error sending request for url"));
|
2023-07-30 09:13:28 -04:00
|
|
|
} 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();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<number>();
|
2023-07-30 09:13:28 -04:00
|
|
|
|
|
|
|
const server = Deno.serve({
|
2023-08-01 14:24:42 -04:00
|
|
|
handler: async (req) => {
|
2023-07-30 09:13:28 -04:00
|
|
|
const _reader = req.body?.getReader();
|
|
|
|
|
2023-08-01 14:24:42 -04:00
|
|
|
try {
|
|
|
|
req.clone();
|
|
|
|
fail();
|
|
|
|
} catch (cloneError) {
|
|
|
|
assert(cloneError instanceof TypeError);
|
|
|
|
assert(
|
|
|
|
cloneError.message.endsWith("Body is unusable."),
|
|
|
|
);
|
2023-07-30 09:13:28 -04:00
|
|
|
|
2023-08-01 14:24:42 -04:00
|
|
|
ac.abort();
|
|
|
|
await server.finished;
|
|
|
|
}
|
2023-07-30 09:13:28 -04:00
|
|
|
return new Response("ok");
|
|
|
|
},
|
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: ({ port }: { port: number }) => resolve(port),
|
2023-07-30 09:13:28 -04:00
|
|
|
});
|
|
|
|
|
2023-08-01 14:24:42 -04:00
|
|
|
try {
|
2023-11-22 06:11:20 -05:00
|
|
|
const port = await promise;
|
2023-08-01 14:24:42 -04:00
|
|
|
await fetch(`http://localhost:${port}/`, {
|
|
|
|
headers: { connection: "close" },
|
|
|
|
method: "POST",
|
|
|
|
body: '{"bar":true}',
|
|
|
|
});
|
|
|
|
fail();
|
|
|
|
} catch (clientError) {
|
|
|
|
assert(clientError instanceof TypeError);
|
2024-07-01 20:09:47 -04:00
|
|
|
assert(clientError.message.includes("error sending request for url"));
|
2023-08-01 14:24:42 -04:00
|
|
|
} finally {
|
|
|
|
ac.abort();
|
|
|
|
await server.finished;
|
|
|
|
}
|
2023-07-30 09:13:28 -04:00
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2022-11-09 03:20:05 -05:00
|
|
|
// Checks large streaming response
|
|
|
|
// https://github.com/denoland/deno/issues/16567
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true } },
|
|
|
|
async function testIssue16567() {
|
|
|
|
const ac = new AbortController();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2023-04-26 08:54:03 -04:00
|
|
|
const server = Deno.serve({
|
|
|
|
async onListen({ port }) {
|
|
|
|
const res1 = await fetch(`http://localhost:${port}/`);
|
|
|
|
assertEquals((await res1.text()).length, 40 * 50_000);
|
|
|
|
|
2023-11-22 06:11:20 -05:00
|
|
|
resolve();
|
2023-04-26 08:54:03 -04:00
|
|
|
ac.abort();
|
|
|
|
},
|
|
|
|
signal: ac.signal,
|
|
|
|
}, () =>
|
2022-11-09 03:20:05 -05:00
|
|
|
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();
|
|
|
|
},
|
|
|
|
}),
|
2023-04-26 08:54:03 -04:00
|
|
|
));
|
2022-11-09 03:20:05 -05:00
|
|
|
|
|
|
|
await promise;
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2022-11-09 03:20:05 -05:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2022-08-18 08:05:02 -04:00
|
|
|
function chunkedBodyReader(h: Headers, r: BufReader): Deno.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);
|
2022-10-17 18:57:31 -04:00
|
|
|
const result = await tp.readMimeHeader();
|
2022-08-18 08:05:02 -04:00
|
|
|
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());
|
|
|
|
}
|
2023-04-24 17:24:40 -04:00
|
|
|
|
2023-11-17 21:08:16 -05:00
|
|
|
// TODO(mmastrac): curl on Windows CI stopped supporting --http2?
|
2023-04-24 17:24:40 -04:00
|
|
|
Deno.test(
|
2023-11-17 21:08:16 -05:00
|
|
|
{
|
|
|
|
permissions: { net: true, run: true },
|
|
|
|
ignore: Deno.build.os === "windows",
|
|
|
|
},
|
2023-04-24 17:24:40 -04:00
|
|
|
async function httpServeCurlH2C() {
|
|
|
|
const ac = new AbortController();
|
|
|
|
const server = Deno.serve(
|
2023-11-17 18:19:00 -05:00
|
|
|
{ port: servePort, signal: ac.signal },
|
2023-04-26 08:54:03 -04:00
|
|
|
() => new Response("hello world!"),
|
2023-04-24 17:24:40 -04:00
|
|
|
);
|
|
|
|
|
|
|
|
assertEquals(
|
|
|
|
"hello world!",
|
2023-11-17 18:19:00 -05:00
|
|
|
await curlRequest([`http://localhost:${servePort}/path`]),
|
2023-04-24 17:24:40 -04:00
|
|
|
);
|
|
|
|
assertEquals(
|
|
|
|
"hello world!",
|
2023-11-17 18:19:00 -05:00
|
|
|
await curlRequest([`http://localhost:${servePort}/path`, "--http2"]),
|
2023-04-24 17:24:40 -04:00
|
|
|
);
|
|
|
|
assertEquals(
|
|
|
|
"hello world!",
|
|
|
|
await curlRequest([
|
2023-11-17 18:19:00 -05:00
|
|
|
`http://localhost:${servePort}/path`,
|
2023-04-24 17:24:40 -04:00
|
|
|
"--http2",
|
|
|
|
"--http2-prior-knowledge",
|
|
|
|
]),
|
|
|
|
);
|
|
|
|
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2023-04-24 17:24:40 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-05-18 22:10:25 -04:00
|
|
|
// 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();
|
2023-11-22 06:11:20 -05:00
|
|
|
const { resolve } = Promise.withResolvers<void>();
|
2023-05-18 22:10:25 -04:00
|
|
|
|
|
|
|
const server = Deno.serve({
|
|
|
|
handler: () => {
|
|
|
|
const response = new Response("Hello World", {
|
|
|
|
headers: {
|
|
|
|
"trailer": "baz",
|
|
|
|
"transfer-encoding": "chunked",
|
|
|
|
"foo": "bar",
|
|
|
|
},
|
|
|
|
});
|
|
|
|
addTrailers(response, [["baz", "why"]]);
|
|
|
|
return response;
|
|
|
|
},
|
2023-05-22 15:35:59 -04:00
|
|
|
port: servePort,
|
2023-05-18 22:10:25 -04:00
|
|
|
signal: ac.signal,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(resolve),
|
2023-05-18 22:10:25 -04:00
|
|
|
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([
|
2023-05-22 15:35:59 -04:00
|
|
|
`http://localhost:${servePort}/path`,
|
2023-05-18 22:10:25 -04:00
|
|
|
"-v",
|
|
|
|
"--http2",
|
|
|
|
"--http2-prior-knowledge",
|
|
|
|
]);
|
|
|
|
assertMatch(stderr, /baz: why/);
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2023-05-18 22:10:25 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-11-17 21:08:16 -05:00
|
|
|
// TODO(mmastrac): curl on CI stopped supporting --http2?
|
2023-04-24 17:24:40 -04:00
|
|
|
Deno.test(
|
2023-11-17 21:08:16 -05:00
|
|
|
{
|
|
|
|
permissions: {
|
|
|
|
net: true,
|
|
|
|
run: true,
|
|
|
|
read: true,
|
|
|
|
},
|
|
|
|
ignore: Deno.build.os === "windows",
|
|
|
|
},
|
2023-04-24 17:24:40 -04:00
|
|
|
async function httpsServeCurlH2C() {
|
|
|
|
const ac = new AbortController();
|
|
|
|
const server = Deno.serve(
|
|
|
|
{
|
|
|
|
signal: ac.signal,
|
2023-11-17 18:19:00 -05:00
|
|
|
port: servePort,
|
chore: move cli/tests/ -> tests/ (#22369)
This looks like a massive PR, but it's only a move from cli/tests ->
tests, and updates of relative paths for files.
This is the first step towards aggregate all of the integration test
files under tests/, which will lead to a set of integration tests that
can run without the CLI binary being built.
While we could leave these tests under `cli`, it would require us to
keep a more complex directory structure for the various test runners. In
addition, we have a lot of complexity to ignore various test files in
the `cli` project itself (cargo publish exclusion rules, autotests =
false, etc).
And finally, the `tests/` folder will eventually house the `test_ffi`,
`test_napi` and other testing code, reducing the size of the root repo
directory.
For easier review, the extremely large and noisy "move" is in the first
commit (with no changes -- just a move), while the remainder of the
changes to actual files is in the second commit.
2024-02-10 15:22:13 -05:00
|
|
|
cert: Deno.readTextFileSync("tests/testdata/tls/localhost.crt"),
|
|
|
|
key: Deno.readTextFileSync("tests/testdata/tls/localhost.key"),
|
2023-04-24 17:24:40 -04:00
|
|
|
},
|
2023-04-26 08:54:03 -04:00
|
|
|
() => new Response("hello world!"),
|
2023-04-24 17:24:40 -04:00
|
|
|
);
|
|
|
|
|
|
|
|
assertEquals(
|
|
|
|
"hello world!",
|
2023-11-17 18:19:00 -05:00
|
|
|
await curlRequest([`https://localhost:${servePort}/path`, "-k"]),
|
2023-04-24 17:24:40 -04:00
|
|
|
);
|
|
|
|
assertEquals(
|
|
|
|
"hello world!",
|
2023-11-17 18:19:00 -05:00
|
|
|
await curlRequest([
|
|
|
|
`https://localhost:${servePort}/path`,
|
|
|
|
"-k",
|
|
|
|
"--http2",
|
|
|
|
]),
|
2023-04-24 17:24:40 -04:00
|
|
|
);
|
|
|
|
assertEquals(
|
|
|
|
"hello world!",
|
|
|
|
await curlRequest([
|
2023-11-17 18:19:00 -05:00
|
|
|
`https://localhost:${servePort}/path`,
|
2023-04-24 17:24:40 -04:00
|
|
|
"-k",
|
|
|
|
"--http2",
|
|
|
|
"--http2-prior-knowledge",
|
|
|
|
]),
|
|
|
|
);
|
|
|
|
|
|
|
|
ac.abort();
|
2023-06-13 14:05:23 -04:00
|
|
|
await server.finished;
|
2023-04-24 17:24:40 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2024-01-22 20:19:49 -05:00
|
|
|
Deno.test("Deno.HttpServer is not thenable", async () => {
|
2023-10-08 23:43:14 -04:00
|
|
|
// 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();
|
|
|
|
});
|
|
|
|
|
2023-10-03 22:37:39 -04:00
|
|
|
Deno.test(
|
|
|
|
{
|
|
|
|
ignore: Deno.build.os === "windows",
|
|
|
|
permissions: { run: true, read: true, write: true },
|
|
|
|
},
|
|
|
|
async function httpServerUnixDomainSocket() {
|
2024-04-19 20:09:50 -04:00
|
|
|
const { promise, resolve } = Promise.withResolvers<Deno.UnixAddr>();
|
2023-10-03 22:37:39 -04:00
|
|
|
const ac = new AbortController();
|
|
|
|
const filePath = tmpUnixSocketPath();
|
|
|
|
const server = Deno.serve(
|
|
|
|
{
|
|
|
|
signal: ac.signal,
|
|
|
|
path: filePath,
|
|
|
|
onListen(info) {
|
2023-11-22 06:11:20 -05:00
|
|
|
resolve(info);
|
2023-10-03 22:37:39 -04:00
|
|
|
},
|
|
|
|
onError: createOnErrorCb(ac),
|
|
|
|
},
|
|
|
|
(_req, { remoteAddr }) => {
|
|
|
|
assertEquals(remoteAddr, { path: filePath, transport: "unix" });
|
|
|
|
return new Response("hello world!");
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2024-04-19 20:09:50 -04:00
|
|
|
assertEquals((await promise).path, filePath);
|
2023-10-03 22:37:39 -04:00
|
|
|
assertEquals(
|
|
|
|
"hello world!",
|
|
|
|
await curlRequest(["--unix-socket", filePath, "http://localhost"]),
|
|
|
|
);
|
|
|
|
ac.abort();
|
|
|
|
await server.finished;
|
|
|
|
},
|
|
|
|
);
|
2023-11-07 17:52:44 -05:00
|
|
|
|
|
|
|
// serve Handler must return Response class or promise that resolves Response class
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true, run: true } },
|
|
|
|
async function handleServeCallbackReturn() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const deferred = Promise.withResolvers<void>();
|
|
|
|
const listeningDeferred = Promise.withResolvers<void>();
|
2023-11-07 17:52:44 -05:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
|
|
|
const server = Deno.serve(
|
|
|
|
{
|
|
|
|
port: servePort,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(listeningDeferred.resolve),
|
2023-11-07 17:52:44 -05:00
|
|
|
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",
|
|
|
|
);
|
2023-11-22 06:11:20 -05:00
|
|
|
deferred.resolve();
|
2023-11-07 17:52:44 -05:00
|
|
|
return new Response("Customized Internal Error from onError");
|
|
|
|
},
|
|
|
|
},
|
|
|
|
() => {
|
|
|
|
// Trick the typechecker
|
|
|
|
return <Response> <unknown> undefined;
|
|
|
|
},
|
|
|
|
);
|
2023-11-22 06:11:20 -05:00
|
|
|
await listeningDeferred.promise;
|
2023-11-07 17:52:44 -05:00
|
|
|
const respText = await curlRequest([`http://localhost:${servePort}`]);
|
2023-11-22 06:11:20 -05:00
|
|
|
await deferred.promise;
|
2023-11-07 17:52:44 -05:00
|
|
|
ac.abort();
|
|
|
|
await server.finished;
|
|
|
|
assert(respText === "Customized Internal Error from onError");
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
// onError Handler must return Response class or promise that resolves Response class
|
|
|
|
Deno.test(
|
|
|
|
{ permissions: { net: true, run: true } },
|
|
|
|
async function handleServeErrorCallbackReturn() {
|
2023-11-22 06:11:20 -05:00
|
|
|
const { promise, resolve } = Promise.withResolvers<void>();
|
2023-11-07 17:52:44 -05:00
|
|
|
const ac = new AbortController();
|
|
|
|
|
|
|
|
const server = Deno.serve(
|
|
|
|
{
|
|
|
|
port: servePort,
|
2023-11-22 06:11:20 -05:00
|
|
|
onListen: onListen(resolve),
|
2023-11-07 17:52:44 -05:00
|
|
|
signal: ac.signal,
|
|
|
|
onError: () => {
|
|
|
|
// Trick the typechecker
|
|
|
|
return <Response> <unknown> undefined;
|
|
|
|
},
|
|
|
|
},
|
|
|
|
() => {
|
|
|
|
// Trick the typechecker
|
|
|
|
return <Response> <unknown> undefined;
|
|
|
|
},
|
|
|
|
);
|
2023-11-22 06:11:20 -05:00
|
|
|
await promise;
|
2023-11-07 17:52:44 -05:00
|
|
|
const respText = await curlRequest([`http://localhost:${servePort}`]);
|
|
|
|
ac.abort();
|
|
|
|
await server.finished;
|
|
|
|
assert(respText === "Internal Server Error");
|
|
|
|
},
|
|
|
|
);
|
2024-05-28 16:46:04 -04:00
|
|
|
|
|
|
|
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: number | undefined = undefined;
|
|
|
|
let _controller;
|
|
|
|
|
|
|
|
const 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;
|
|
|
|
},
|
|
|
|
);
|