summaryrefslogtreecommitdiff
path: root/cli/tests/unit/http_test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'cli/tests/unit/http_test.ts')
-rw-r--r--cli/tests/unit/http_test.ts2801
1 files changed, 0 insertions, 2801 deletions
diff --git a/cli/tests/unit/http_test.ts b/cli/tests/unit/http_test.ts
deleted file mode 100644
index 66cc53113..000000000
--- a/cli/tests/unit/http_test.ts
+++ /dev/null
@@ -1,2801 +0,0 @@
-// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
-import { Buffer, BufReader, BufWriter } from "@test_util/std/io/mod.ts";
-import { TextProtoReader } from "../testdata/run/textproto.ts";
-import {
- assert,
- assertEquals,
- assertRejects,
- assertStrictEquals,
- assertThrows,
- delay,
- fail,
-} from "./test_util.ts";
-import { join } from "@test_util/std/path/mod.ts";
-
-const listenPort = 4507;
-const listenPort2 = 4508;
-
-const {
- buildCaseInsensitiveCommaValueFinder,
- // @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol
-} = Deno[Deno.internal];
-
-async function writeRequestAndReadResponse(conn: Deno.Conn): Promise<string> {
- const encoder = new TextEncoder();
- const decoder = new TextDecoder();
-
- const w = new BufWriter(conn);
- const r = new BufReader(conn);
- const body = `GET / HTTP/1.1\r\nHost: 127.0.0.1:${listenPort}\r\n\r\n`;
- const writeResult = await w.write(encoder.encode(body));
- assertEquals(body.length, writeResult);
- await w.flush();
- const tpr = new TextProtoReader(r);
- const statusLine = await tpr.readLine();
- assert(statusLine !== null);
- const headers = await tpr.readMimeHeader();
- assert(headers !== null);
-
- const chunkedReader = chunkedBodyReader(headers, r);
- const buf = new Uint8Array(5);
- const dest = new Buffer();
- let result: number | null;
- while ((result = await chunkedReader.read(buf)) !== null) {
- const len = Math.min(buf.byteLength, result);
- await dest.write(buf.subarray(0, len));
- }
- return decoder.decode(dest.bytes());
-}
-
-Deno.test({ permissions: { net: true } }, async function httpServerBasic() {
- let httpConn: Deno.HttpConn;
- const promise = (async () => {
- const listener = Deno.listen({ port: listenPort });
- const conn = await listener.accept();
- listener.close();
- httpConn = Deno.serveHttp(conn);
- const reqEvent = await httpConn.nextRequest();
- assert(reqEvent);
- const { request, respondWith } = reqEvent;
- assertEquals(new URL(request.url).href, `http://127.0.0.1:${listenPort}/`);
- assertEquals(await request.text(), "");
- await respondWith(
- new Response("Hello World", { headers: { "foo": "bar" } }),
- );
- })();
-
- const resp = await fetch(`http://127.0.0.1:${listenPort}/`, {
- headers: { "connection": "close" },
- });
- 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");
- await promise;
-
- httpConn!.close();
-});
-
-// https://github.com/denoland/deno/issues/15107
-Deno.test(
- { permissions: { net: true } },
- async function httpLazyHeadersIssue15107() {
- let headers: Headers;
- const promise = (async () => {
- const listener = Deno.listen({ port: 2333 });
- const conn = await listener.accept();
- listener.close();
- const httpConn = Deno.serveHttp(conn);
- const e = await httpConn.nextRequest();
- assert(e);
- const { request } = e;
- request.text();
- headers = request.headers;
- httpConn!.close();
- })();
-
- const conn = await Deno.connect({ port: 2333 });
- // Send GET request with a body + content-length.
- const encoder = new TextEncoder();
- const body =
- `GET / HTTP/1.1\r\nHost: 127.0.0.1:2333\r\nContent-Length: 5\r\n\r\n12345`;
- const writeResult = await conn.write(encoder.encode(body));
- assertEquals(body.length, writeResult);
- await promise;
- conn.close();
- assertEquals(headers!.get("content-length"), "5");
- },
-);
-
-Deno.test(
- { permissions: { net: true } },
- async function httpReadHeadersAfterClose() {
- const promise = (async () => {
- const listener = Deno.listen({ port: 2334 });
- const conn = await listener.accept();
- listener.close();
- const httpConn = Deno.serveHttp(conn);
- const e = await httpConn.nextRequest();
- assert(e);
- const { request, respondWith } = e;
-
- await request.text(); // Read body
- await respondWith(new Response("Hello World")); // Closes request
-
- assertThrows(() => request.headers, TypeError, "request closed");
- httpConn!.close();
- })();
-
- const conn = await Deno.connect({ port: 2334 });
- // Send GET request with a body + content-length.
- const encoder = new TextEncoder();
- const body =
- `GET / HTTP/1.1\r\nHost: 127.0.0.1:2333\r\nContent-Length: 5\r\n\r\n12345`;
- const writeResult = await conn.write(encoder.encode(body));
- assertEquals(body.length, writeResult);
- await promise;
- conn.close();
- },
-);
-
-Deno.test(
- { permissions: { net: true } },
- async function httpServerGetRequestBody() {
- let httpConn: Deno.HttpConn;
- const promise = (async () => {
- const listener = Deno.listen({ port: listenPort });
- const conn = await listener.accept();
- listener.close();
- httpConn = Deno.serveHttp(conn);
- const e = await httpConn.nextRequest();
- assert(e);
- const { request, respondWith } = e;
- assertEquals(request.body, null);
- await respondWith(new Response("", { headers: {} }));
- })();
-
- const conn = await Deno.connect({ port: listenPort });
- // Send GET request with a body + content-length.
- const encoder = new TextEncoder();
- const body =
- `GET / HTTP/1.1\r\nHost: 127.0.0.1:${listenPort}\r\nContent-Length: 5\r\n\r\n12345`;
- const writeResult = await conn.write(encoder.encode(body));
- assertEquals(body.length, writeResult);
-
- const resp = new Uint8Array(200);
- const readResult = await conn.read(resp);
- assertEquals(readResult, 138);
-
- conn.close();
-
- await promise;
- httpConn!.close();
- },
-);
-
-Deno.test(
- { permissions: { net: true } },
- async function httpServerStreamResponse() {
- const stream = new TransformStream();
- const writer = stream.writable.getWriter();
- writer.write(new TextEncoder().encode("hello "));
- writer.write(new TextEncoder().encode("world"));
- writer.close();
-
- let httpConn: Deno.HttpConn;
- const listener = Deno.listen({ port: listenPort });
- const promise = (async () => {
- const conn = await listener.accept();
- httpConn = Deno.serveHttp(conn);
- const evt = await httpConn.nextRequest();
- assert(evt);
- const { request, respondWith } = evt;
- assert(!request.body);
- await respondWith(new Response(stream.readable));
- })();
-
- const resp = await fetch(`http://127.0.0.1:${listenPort}/`);
- const respBody = await resp.text();
- assertEquals("hello world", respBody);
- await promise;
- httpConn!.close();
- listener.close();
- },
-);
-
-Deno.test(
- { permissions: { net: true } },
- async function httpServerStreamRequest() {
- const stream = new TransformStream();
- const writer = stream.writable.getWriter();
- writer.write(new TextEncoder().encode("hello "));
- writer.write(new TextEncoder().encode("world"));
- writer.close();
-
- const listener = Deno.listen({ port: listenPort });
- const promise = (async () => {
- const conn = await listener.accept();
- const httpConn = Deno.serveHttp(conn);
- const evt = await httpConn.nextRequest();
- assert(evt);
- const { request, respondWith } = evt;
- const reqBody = await request.text();
- assertEquals("hello world", reqBody);
- await respondWith(new Response(""));
-
- // TODO(ry) If we don't call httpConn.nextRequest() here we get "error sending
- // request for url (https://localhost:${listenPort}/): connection closed before
- // message completed".
- assertEquals(await httpConn.nextRequest(), null);
-
- listener.close();
- })();
-
- const resp = await fetch(`http://127.0.0.1:${listenPort}/`, {
- body: stream.readable,
- method: "POST",
- headers: { "connection": "close" },
- });
-
- await resp.arrayBuffer();
- await promise;
- },
-);
-
-Deno.test(
- { permissions: { net: true } },
- async function httpServerStreamDuplex() {
- let httpConn: Deno.HttpConn;
- const listener = Deno.listen({ port: listenPort });
- const promise = (async () => {
- const conn = await listener.accept();
- httpConn = Deno.serveHttp(conn);
- const evt = await httpConn.nextRequest();
- assert(evt);
- const { request, respondWith } = evt;
- assert(request.body);
- await respondWith(new Response(request.body));
- })();
-
- const ts = new TransformStream();
- const writable = ts.writable.getWriter();
- const resp = await fetch(`http://127.0.0.1:${listenPort}/`, {
- method: "POST",
- body: ts.readable,
- });
- assert(resp.body);
- const reader = resp.body.getReader();
- 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);
- await promise;
- httpConn!.close();
- listener.close();
- },
-);
-
-Deno.test({ permissions: { net: true } }, async function httpServerClose() {
- const listener = Deno.listen({ port: listenPort });
- const client = await Deno.connect({ port: listenPort });
- const httpConn = Deno.serveHttp(await listener.accept());
- client.close();
- const evt = await httpConn.nextRequest();
- assertEquals(evt, null);
- // Note httpConn is automatically closed when "done" is reached.
- listener.close();
-});
-
-Deno.test(
- { permissions: { net: true } },
- async function httpServerInvalidMethod() {
- const listener = Deno.listen({ port: listenPort });
- const client = await Deno.connect({ port: listenPort });
- const httpConn = Deno.serveHttp(await listener.accept());
- await client.write(new Uint8Array([1, 2, 3]));
- await assertRejects(
- async () => {
- await httpConn.nextRequest();
- },
- Deno.errors.Http,
- "invalid HTTP method parsed",
- );
- // Note httpConn is automatically closed when it errors.
- client.close();
- listener.close();
- },
-);
-
-Deno.test(
- { permissions: { read: true, net: true } },
- async function httpServerWithTls() {
- const hostname = "localhost";
- const port = listenPort;
-
- const promise = (async () => {
- const listener = Deno.listenTls({
- hostname,
- port,
- cert: Deno.readTextFileSync("cli/tests/testdata/tls/localhost.crt"),
- key: Deno.readTextFileSync("cli/tests/testdata/tls/localhost.key"),
- });
- const conn = await listener.accept();
- const httpConn = Deno.serveHttp(conn);
- const evt = await httpConn.nextRequest();
- assert(evt);
- const { respondWith } = evt;
- await respondWith(new Response("Hello World"));
-
- // TODO(ry) If we don't call httpConn.nextRequest() here we get "error sending
- // request for url (https://localhost:${listenPort}/): connection closed before
- // message completed".
- assertEquals(await httpConn.nextRequest(), null);
-
- listener.close();
- })();
-
- const caCert = Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem");
- const client = Deno.createHttpClient({ caCerts: [caCert] });
- const resp = await fetch(`https://${hostname}:${port}/`, {
- headers: { "connection": "close" },
- client,
- });
- client.close();
- const respBody = await resp.text();
- assertEquals("Hello World", respBody);
- await promise;
- },
-);
-
-Deno.test(
- { permissions: { net: true } },
- async function httpServerRegressionHang() {
- let httpConn: Deno.HttpConn;
- const listener = Deno.listen({ port: listenPort });
- const promise = (async () => {
- const conn = await listener.accept();
- httpConn = Deno.serveHttp(conn);
- const event = await httpConn.nextRequest();
- assert(event);
- const { request, respondWith } = event;
- const reqBody = await request.text();
- assertEquals("request", reqBody);
- await respondWith(new Response("response"));
- })();
-
- const resp = await fetch(`http://127.0.0.1:${listenPort}/`, {
- method: "POST",
- body: "request",
- });
- const respBody = await resp.text();
- assertEquals("response", respBody);
- await promise;
-
- httpConn!.close();
- listener.close();
- },
-);
-
-Deno.test(
- { permissions: { net: true } },
- async function httpServerCancelBodyOnResponseFailure() {
- const promise = (async () => {
- const listener = Deno.listen({ port: listenPort });
- const conn = await listener.accept();
- const httpConn = Deno.serveHttp(conn);
- const event = await httpConn.nextRequest();
- assert(event);
- const { respondWith } = event;
- let cancelReason: string;
- await assertRejects(
- async () => {
- let interval = 0;
- await respondWith(
- new Response(
- new ReadableStream({
- start(controller) {
- interval = setInterval(() => {
- const message = `data: ${Date.now()}\n\n`;
- controller.enqueue(new TextEncoder().encode(message));
- }, 200);
- },
- cancel(reason) {
- cancelReason = reason;
- clearInterval(interval);
- },
- }),
- ),
- );
- },
- Deno.errors.Http,
- cancelReason!,
- );
- assert(cancelReason!);
- httpConn!.close();
- listener.close();
- })();
-
- const resp = await fetch(`http://127.0.0.1:${listenPort}/`);
- await resp.body!.cancel();
- await promise;
- },
-);
-
-Deno.test(
- { permissions: { net: true } },
- async function httpServerNextRequestErrorExposedInResponse() {
- const promise = (async () => {
- const listener = Deno.listen({ port: listenPort });
- const conn = await listener.accept();
- const httpConn = Deno.serveHttp(conn);
- const event = await httpConn.nextRequest();
- assert(event);
- // Start polling for the next request before awaiting response.
- const nextRequestPromise = httpConn.nextRequest();
- const { respondWith } = event;
- await assertRejects(
- async () => {
- let interval = 0;
- await respondWith(
- new Response(
- new ReadableStream({
- start(controller) {
- interval = setInterval(() => {
- const message = `data: ${Date.now()}\n\n`;
- controller.enqueue(new TextEncoder().encode(message));
- }, 200);
- },
- cancel() {
- clearInterval(interval);
- },
- }),
- ),
- );
- },
- Deno.errors.Http,
- "connection closed",
- );
- // The error from `op_http_accept` reroutes to `respondWith()`.
- assertEquals(await nextRequestPromise, null);
- listener.close();
- })();
-
- const resp = await fetch(`http://127.0.0.1:${listenPort}/`);
- await resp.body!.cancel();
- await promise;
- },
-);
-
-Deno.test(
- { permissions: { net: true } },
- async function httpServerEmptyBlobResponse() {
- let httpConn: Deno.HttpConn;
- const listener = Deno.listen({ port: listenPort });
- const promise = (async () => {
- const conn = await listener.accept();
- httpConn = Deno.serveHttp(conn);
- const event = await httpConn.nextRequest();
- assert(event);
- const { respondWith } = event;
- await respondWith(new Response(new Blob([])));
- })();
-
- const resp = await fetch(`http://127.0.0.1:${listenPort}/`);
- const respBody = await resp.text();
- assertEquals("", respBody);
- await promise;
- httpConn!.close();
- listener.close();
- },
-);
-
-Deno.test(
- { permissions: { net: true } },
- async function httpServerNextRequestResolvesOnClose() {
- const httpConnList: Deno.HttpConn[] = [];
-
- async function serve(l: Deno.Listener) {
- for await (const conn of l) {
- (async () => {
- const c = Deno.serveHttp(conn);
- httpConnList.push(c);
- for await (const { respondWith } of c) {
- respondWith(new Response("hello"));
- }
- })();
- }
- }
-
- const l = Deno.listen({ port: listenPort });
- serve(l);
-
- await delay(300);
- const res = await fetch(`http://localhost:${listenPort}/`);
- const _text = await res.text();
-
- // Close connection and listener.
- httpConnList.forEach((conn) => conn.close());
- l.close();
-
- await delay(300);
- },
-);
-
-Deno.test(
- { permissions: { net: true } },
- // Issue: https://github.com/denoland/deno/issues/10870
- async function httpServerHang() {
- // 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!;
- }
-
- const httpConns: Deno.HttpConn[] = [];
- const promise = (async () => {
- let count = 0;
- const listener = Deno.listen({ port: listenPort });
- for await (const conn of listener) {
- (async () => {
- const httpConn = Deno.serveHttp(conn);
- httpConns.push(httpConn);
- for await (const { respondWith } of httpConn) {
- respondWith(new Response(stream("hello")));
-
- count++;
- if (count >= 2) {
- listener.close();
- }
- }
- })();
- }
- })();
-
- const clientConn = await Deno.connect({ port: listenPort });
-
- const r1 = await writeRequestAndReadResponse(clientConn);
- assertEquals(r1, "hello");
-
- const r2 = await writeRequestAndReadResponse(clientConn);
- assertEquals(r2, "hello");
-
- clientConn.close();
- await promise;
- for (const conn of httpConns) {
- conn.close();
- }
- },
-);
-
-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.
-
- let counter = 0;
-
- const deferreds = [
- Promise.withResolvers<void>(),
- Promise.withResolvers<void>(),
- Promise.withResolvers<void>(),
- ];
-
- async function writeRequest(conn: Deno.Conn) {
- const encoder = new TextEncoder();
- const decoder = new TextDecoder();
-
- const w = new BufWriter(conn);
- const r = new BufReader(conn);
- const body = `GET / HTTP/1.1\r\nHost: 127.0.0.1:${listenPort}\r\n\r\n`;
- const writeResult = await w.write(encoder.encode(body));
- assertEquals(body.length, writeResult);
- await w.flush();
- const tpr = new TextProtoReader(r);
- const statusLine = await tpr.readLine();
- assert(statusLine !== null);
- const headers = await tpr.readMimeHeader();
- assert(headers !== null);
-
- const chunkedReader = chunkedBodyReader(headers, r);
- const buf = new Uint8Array(5);
- const dest = new Buffer();
- let result: number | null;
- 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());
- }
-
- function periodicStream() {
- return new ReadableStream({
- start(controller) {
- controller.enqueue(`${counter}\n`);
- counter++;
- },
-
- async pull(controller) {
- if (counter >= 3) {
- return controller.close();
- }
-
- await deferreds[counter - 1].promise;
-
- controller.enqueue(`${counter}\n`);
- counter++;
- },
- }).pipeThrough(new TextEncoderStream());
- }
-
- let httpConn: Deno.HttpConn;
- const listener = Deno.listen({ port: listenPort });
- const finished = (async () => {
- const conn = await listener.accept();
- httpConn = Deno.serveHttp(conn);
- const requestEvent = await httpConn.nextRequest();
- const { respondWith } = requestEvent!;
- await respondWith(new Response(periodicStream()));
- })();
-
- // start a client
- const clientConn = await Deno.connect({ port: listenPort });
-
- const r1 = await writeRequest(clientConn);
- assertEquals(r1, "0\n1\n2\n");
-
- await finished;
- clientConn.close();
-
- httpConn!.close();
- listener.close();
- },
-);
-
-Deno.test(
- { permissions: { net: true } },
- async function httpRequestLatin1Headers() {
- let httpConn: Deno.HttpConn;
- const promise = (async () => {
- const listener = Deno.listen({ port: listenPort });
- const conn = await listener.accept();
- listener.close();
- httpConn = Deno.serveHttp(conn);
- const reqEvent = await httpConn.nextRequest();
- assert(reqEvent);
- const { request, respondWith } = reqEvent;
- assertEquals(request.headers.get("X-Header-Test"), "á");
- await respondWith(
- new Response("", { headers: { "X-Header-Test": "Æ" } }),
- );
- })();
-
- const clientConn = await Deno.connect({ port: listenPort });
- const requestText =
- `GET / HTTP/1.1\r\nHost: 127.0.0.1:${listenPort}\r\nX-Header-Test: á\r\n\r\n`;
- const requestBytes = new Uint8Array(requestText.length);
- for (let i = 0; i < requestText.length; i++) {
- requestBytes[i] = requestText.charCodeAt(i);
- }
- let written = 0;
- while (written < requestBytes.byteLength) {
- written += await clientConn.write(requestBytes.slice(written));
- }
-
- let responseText = "";
- const buf = new Uint8Array(1024);
- let read;
-
- while ((read = await clientConn.read(buf)) !== null) {
- httpConn!.close();
- for (let i = 0; i < read; i++) {
- responseText += String.fromCharCode(buf[i]);
- }
- }
-
- clientConn.close();
-
- assert(/\r\n[Xx]-[Hh]eader-[Tt]est: Æ\r\n/.test(responseText));
-
- await promise;
- },
-);
-
-Deno.test(
- { permissions: { net: true } },
- async function httpServerRequestWithoutPath() {
- let httpConn: Deno.HttpConn;
- const listener = Deno.listen({ port: listenPort });
- const promise = (async () => {
- const conn = await listener.accept();
- listener.close();
- httpConn = Deno.serveHttp(conn);
- const reqEvent = await httpConn.nextRequest();
- assert(reqEvent);
- const { request, respondWith } = reqEvent;
- assertEquals(
- new URL(request.url).href,
- `http://127.0.0.1:${listenPort}/`,
- );
- assertEquals(await request.text(), "");
- await respondWith(new Response());
- })();
-
- const clientConn = await Deno.connect({ port: listenPort });
-
- async function writeRequest(conn: Deno.Conn) {
- const encoder = new TextEncoder();
-
- const w = new BufWriter(conn);
- const r = new BufReader(conn);
- const body =
- `CONNECT 127.0.0.1:${listenPort} HTTP/1.1\r\nHost: 127.0.0.1:${listenPort}\r\n\r\n`;
- const writeResult = await w.write(encoder.encode(body));
- assertEquals(body.length, writeResult);
- await w.flush();
- const tpr = new TextProtoReader(r);
- const statusLine = await tpr.readLine();
- assert(statusLine !== null);
- const m = statusLine.match(/^(.+?) (.+?) (.+?)$/);
- assert(m !== null, "must be matched");
- const [_, _proto, status, _ok] = m;
- assertEquals(status, "200");
- const headers = await tpr.readMimeHeader();
- assert(headers !== null);
- }
-
- await writeRequest(clientConn);
- clientConn.close();
- await promise;
- httpConn!.close();
- },
-);
-
-Deno.test({ permissions: { net: true } }, async function httpServerWebSocket() {
- const promise = (async () => {
- const listener = Deno.listen({ port: listenPort });
- const conn = await listener.accept();
- listener.close();
- const httpConn = Deno.serveHttp(conn);
- const reqEvent = await httpConn.nextRequest();
- assert(reqEvent);
- const { request, respondWith } = reqEvent;
- const {
- response,
- socket,
- } = Deno.upgradeWebSocket(request);
- socket.onerror = () => fail();
- socket.onmessage = (m) => {
- socket.send(m.data);
- socket.close(1001);
- };
- const close = new Promise<void>((resolve) => {
- socket.onclose = () => resolve();
- });
- await respondWith(response);
- await close;
- })();
-
- const def = Promise.withResolvers<void>();
- const ws = new WebSocket(`ws://localhost:${listenPort}`);
- ws.onmessage = (m) => assertEquals(m.data, "foo");
- ws.onerror = () => fail();
- ws.onclose = () => def.resolve();
- ws.onopen = () => ws.send("foo");
- await def.promise;
- await promise;
-});
-
-Deno.test(function httpUpgradeWebSocket() {
- const request = new Request("https://deno.land/", {
- headers: {
- connection: "Upgrade",
- upgrade: "websocket",
- "sec-websocket-key": "dGhlIHNhbXBsZSBub25jZQ==",
- },
- });
- const { response } = Deno.upgradeWebSocket(request);
- assertEquals(response.status, 101);
- assertEquals(response.headers.get("connection"), "Upgrade");
- assertEquals(response.headers.get("upgrade"), "websocket");
- assertEquals(
- response.headers.get("sec-websocket-accept"),
- "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",
- );
-});
-
-Deno.test(function httpUpgradeWebSocketMultipleConnectionOptions() {
- const request = new Request("https://deno.land/", {
- headers: {
- connection: "keep-alive, upgrade",
- upgrade: "websocket",
- "sec-websocket-key": "dGhlIHNhbXBsZSBub25jZQ==",
- },
- });
- const { response } = Deno.upgradeWebSocket(request);
- assertEquals(response.status, 101);
-});
-
-Deno.test(function httpUpgradeWebSocketMultipleUpgradeOptions() {
- const request = new Request("https://deno.land/", {
- headers: {
- connection: "upgrade",
- upgrade: "websocket, foo",
- "sec-websocket-key": "dGhlIHNhbXBsZSBub25jZQ==",
- },
- });
- const { response } = Deno.upgradeWebSocket(request);
- assertEquals(response.status, 101);
-});
-
-Deno.test(function httpUpgradeWebSocketCaseInsensitiveUpgradeHeader() {
- const request = new Request("https://deno.land/", {
- headers: {
- connection: "upgrade",
- upgrade: "Websocket",
- "sec-websocket-key": "dGhlIHNhbXBsZSBub25jZQ==",
- },
- });
- const { response } = Deno.upgradeWebSocket(request);
- assertEquals(response.status, 101);
-});
-
-Deno.test(function httpUpgradeWebSocketInvalidUpgradeHeader() {
- assertThrows(
- () => {
- const request = new Request("https://deno.land/", {
- headers: {
- connection: "upgrade",
- upgrade: "invalid",
- "sec-websocket-key": "dGhlIHNhbXBsZSBub25jZQ==",
- },
- });
- Deno.upgradeWebSocket(request);
- },
- TypeError,
- "Invalid Header: 'upgrade' header must contain 'websocket'",
- );
-});
-
-Deno.test(function httpUpgradeWebSocketWithoutUpgradeHeader() {
- assertThrows(
- () => {
- const request = new Request("https://deno.land/", {
- headers: {
- connection: "upgrade",
- "sec-websocket-key": "dGhlIHNhbXBsZSBub25jZQ==",
- },
- });
- Deno.upgradeWebSocket(request);
- },
- TypeError,
- "Invalid Header: 'upgrade' header must contain 'websocket'",
- );
-});
-
-Deno.test(
- { permissions: { net: true } },
- async function httpCookieConcatenation() {
- let httpConn: Deno.HttpConn;
- const promise = (async () => {
- const listener = Deno.listen({ port: listenPort });
- const conn = await listener.accept();
- listener.close();
- httpConn = Deno.serveHttp(conn);
- const reqEvent = await httpConn.nextRequest();
- assert(reqEvent);
- const { request, respondWith } = reqEvent;
- assertEquals(
- new URL(request.url).href,
- `http://127.0.0.1:${listenPort}/`,
- );
- assertEquals(await request.text(), "");
- assertEquals(request.headers.get("cookie"), "foo=bar; bar=foo");
- await respondWith(new Response("ok"));
- })();
-
- const resp = await fetch(`http://127.0.0.1:${listenPort}/`, {
- headers: [
- ["connection", "close"],
- ["cookie", "foo=bar"],
- ["cookie", "bar=foo"],
- ],
- });
- const text = await resp.text();
- assertEquals(text, "ok");
- await promise;
- httpConn!.close();
- },
-);
-
-// https://github.com/denoland/deno/issues/11651
-Deno.test({ permissions: { net: true } }, async function httpServerPanic() {
- const listener = Deno.listen({ port: listenPort });
- const client = await Deno.connect({ port: listenPort });
- const conn = await listener.accept();
- const httpConn = Deno.serveHttp(conn);
-
- // This message is incomplete on purpose, we'll forcefully close client connection
- // after it's flushed to cause connection to error out on the server side.
- const encoder = new TextEncoder();
- await client.write(encoder.encode("GET / HTTP/1.1"));
-
- httpConn.nextRequest();
- await client.write(encoder.encode("\r\n\r\n"));
- httpConn!.close();
-
- client.close();
- listener.close();
-});
-
-Deno.test(
- { permissions: { net: true, write: true, read: true } },
- async function httpServerCorrectSizeResponse() {
- const tmpFile = await Deno.makeTempFile();
- using file = await Deno.open(tmpFile, { write: true, read: true });
- await file.write(new Uint8Array(70 * 1024).fill(1)); // 70kb sent in 64kb + 6kb chunks
-
- let httpConn: Deno.HttpConn;
- const listener = Deno.listen({ port: listenPort });
- const promise = (async () => {
- const conn = await listener.accept();
- httpConn = Deno.serveHttp(conn);
- const ev = await httpConn.nextRequest();
- const { respondWith } = ev!;
- const f = await Deno.open(tmpFile, { read: true });
- await respondWith(new Response(f.readable, { status: 200 }));
- })();
- const resp = await fetch(`http://127.0.0.1:${listenPort}/`);
- const body = await resp.arrayBuffer();
- assertEquals(body.byteLength, 70 * 1024);
- await promise;
- httpConn!.close();
- listener.close();
- },
-);
-
-Deno.test(
- { permissions: { net: true, write: true, read: true } },
- async function httpServerClosedStream() {
- const listener = Deno.listen({ port: listenPort });
-
- const client = await Deno.connect({ port: listenPort });
- await client.write(new TextEncoder().encode(
- `GET / HTTP/1.0\r\n\r\n`,
- ));
-
- const conn = await listener.accept();
- const httpConn = Deno.serveHttp(conn);
- const ev = await httpConn.nextRequest();
- const { respondWith } = ev!;
-
- const tmpFile = await Deno.makeTempFile();
- const file = await Deno.open(tmpFile, { write: true, read: true });
- await file.write(new TextEncoder().encode("hello"));
-
- const reader = await file.readable.getReader();
- while (true) {
- const { done, value } = await reader.read();
- if (done) break;
- assert(value);
- }
-
- let didThrow = false;
- try {
- await respondWith(new Response(file.readable));
- } catch {
- // pass
- didThrow = true;
- }
-
- assert(didThrow);
- httpConn!.close();
- listener.close();
- client.close();
- },
-);
-
-// https://github.com/denoland/deno/issues/11595
-Deno.test(
- { permissions: { net: true } },
- async function httpServerIncompleteMessage() {
- const listener = Deno.listen({ port: listenPort });
-
- const client = await Deno.connect({ port: listenPort });
- await client.write(new TextEncoder().encode(
- `GET / HTTP/1.0\r\n\r\n`,
- ));
-
- const conn = await listener.accept();
- const httpConn = Deno.serveHttp(conn);
- const ev = await httpConn.nextRequest();
- const { respondWith } = ev!;
-
- const errors: Error[] = [];
-
- const readable = new ReadableStream({
- async pull(controller) {
- client.close();
- await delay(1000);
- controller.enqueue(new TextEncoder().encode(
- "written to the writable side of a TransformStream",
- ));
- controller.close();
- },
- cancel(error) {
- errors.push(error);
- },
- });
-
- const res = new Response(readable);
-
- await respondWith(res).catch((error: Error) => errors.push(error));
-
- httpConn!.close();
- listener.close();
-
- assert(errors.length >= 1);
- for (const error of errors) {
- assertEquals(error.name, "Http");
- assert(error.message.includes("connection"));
- }
- },
-);
-
-// https://github.com/denoland/deno/issues/11743
-Deno.test(
- { permissions: { net: true } },
- async function httpServerDoesntLeakResources() {
- const listener = Deno.listen({ port: listenPort });
- const [conn, clientConn] = await Promise.all([
- listener.accept(),
- Deno.connect({ port: listenPort }),
- ]);
- const httpConn = Deno.serveHttp(conn);
-
- await Promise.all([
- httpConn.nextRequest(),
- clientConn.write(new TextEncoder().encode(
- `GET / HTTP/1.1\r\nHost: 127.0.0.1:${listenPort}\r\n\r\n`,
- )),
- ]);
-
- httpConn!.close();
- listener.close();
- clientConn.close();
- },
-);
-
-// https://github.com/denoland/deno/issues/11926
-// verify that the only new resource is "httpConnection", to make
-// sure "request" resource is closed even if its body was not read
-// by server handler
-Deno.test(
- { permissions: { net: true } },
- async function httpServerDoesntLeakResources2() {
- let listener: Deno.Listener;
- let httpConn: Deno.HttpConn;
-
- const promise = (async () => {
- listener = Deno.listen({ port: listenPort });
- for await (const conn of listener) {
- httpConn = Deno.serveHttp(conn);
- for await (const { request, respondWith } of httpConn) {
- assertEquals(
- new URL(request.url).href,
- `http://127.0.0.1:${listenPort}/`,
- );
- // not reading request body on purpose
- respondWith(new Response("ok"));
- }
- }
- })();
-
- const response = await fetch(`http://127.0.0.1:${listenPort}`, {
- method: "POST",
- body: "hello world",
- });
- await response.text();
-
- listener!.close();
- httpConn!.close();
- await promise;
- },
-);
-
-// https://github.com/denoland/deno/pull/12216
-Deno.test(
- { permissions: { net: true } },
- async function droppedConnSenderNoPanic() {
- async function server() {
- const listener = Deno.listen({ port: listenPort });
- const conn = await listener.accept();
- const http = Deno.serveHttp(conn);
- const evt = await http.nextRequest();
- http.close();
- try {
- await evt!.respondWith(new Response("boom"));
- } catch {
- // Ignore error.
- }
- listener.close();
- }
-
- async function client() {
- try {
- const resp = await fetch(`http://127.0.0.1:${listenPort}/`);
- await resp.body?.cancel();
- } catch {
- // Ignore error
- }
- }
-
- await Promise.all([server(), client()]);
- },
-);
-
-// https://github.com/denoland/deno/issues/12193
-Deno.test(
- { permissions: { net: true } },
- async function httpConnConcurrentNextRequestCalls() {
- const hostname = "localhost";
- const port = listenPort;
-
- let httpConn: Deno.HttpConn;
- const listener = Deno.listen({ hostname, port });
- async function server() {
- const tcpConn = await listener.accept();
- httpConn = Deno.serveHttp(tcpConn);
- const promises = new Array(10).fill(null).map(async (_, i) => {
- const event = await httpConn.nextRequest();
- assert(event);
- const { pathname } = new URL(event.request.url);
- assertStrictEquals(pathname, `/${i}`);
- const response = new Response(`Response #${i}`);
- await event.respondWith(response);
- });
- await Promise.all(promises);
- }
-
- async function client() {
- for (let i = 0; i < 10; i++) {
- const response = await fetch(`http://${hostname}:${port}/${i}`);
- const body = await response.text();
- assertStrictEquals(body, `Response #${i}`);
- }
- }
-
- await Promise.all([server(), delay(100).then(client)]);
- httpConn!.close();
- listener.close();
- },
-);
-
-// https://github.com/denoland/deno/pull/12704
-// https://github.com/denoland/deno/pull/12732
-Deno.test(
- { permissions: { net: true } },
- async function httpConnAutoCloseDelayedOnUpgrade() {
- const hostname = "localhost";
- const port = listenPort;
-
- async function server() {
- const listener = Deno.listen({ hostname, port });
- const tcpConn = await listener.accept();
- const httpConn = Deno.serveHttp(tcpConn);
-
- const event1 = await httpConn.nextRequest() as Deno.RequestEvent;
- const event2Promise = httpConn.nextRequest();
-
- const { socket, response } = Deno.upgradeWebSocket(event1.request);
- socket.onmessage = (event) => socket.send(event.data);
- const socketClosed = new Promise<void>((resolve) => {
- socket.onclose = () => resolve();
- });
- event1.respondWith(response);
-
- const event2 = await event2Promise;
- assertStrictEquals(event2, null);
-
- listener.close();
- await socketClosed;
- }
-
- async function client() {
- const socket = new WebSocket(`ws://${hostname}:${port}/`);
- socket.onopen = () => socket.send("bla bla");
- const closed = new Promise<void>((resolve) => {
- socket.onclose = () => resolve();
- });
- const { data } = await new Promise<MessageEvent<string>>((res) =>
- socket.onmessage = res
- );
- assertStrictEquals(data, "bla bla");
- socket.close();
- await closed;
- }
-
- await Promise.all([server(), client()]);
- },
-);
-
-// https://github.com/denoland/deno/issues/12741
-// https://github.com/denoland/deno/pull/12746
-// https://github.com/denoland/deno/pull/12798
-Deno.test(
- { permissions: { net: true, run: true } },
- async function httpServerDeleteRequestHasBody() {
- const hostname = "localhost";
- const port = listenPort;
-
- let httpConn: Deno.HttpConn;
- const listener = Deno.listen({ hostname, port });
- async function server() {
- const tcpConn = await listener.accept();
- httpConn = Deno.serveHttp(tcpConn);
- const event = await httpConn.nextRequest() as Deno.RequestEvent;
- assert(event.request.body);
- const response = new Response();
- await event.respondWith(response);
- }
-
- async function client() {
- const url = `http://${hostname}:${port}/`;
- const args = ["-X", "DELETE", url];
- const { success } = await new Deno.Command("curl", {
- args,
- stdout: "null",
- stderr: "null",
- }).output();
- assert(success);
- }
-
- await Promise.all([server(), client()]);
- httpConn!.close();
- listener.close();
- },
-);
-
-Deno.test(
- { permissions: { net: true } },
- async function httpServerRespondNonAsciiUint8Array() {
- let httpConn: Deno.HttpConn;
- const listener = Deno.listen({ port: listenPort });
- const promise = (async () => {
- const conn = await listener.accept();
- listener.close();
- httpConn = Deno.serveHttp(conn);
- const e = await httpConn.nextRequest();
- assert(e);
- const { request, respondWith } = e;
- assertEquals(request.body, null);
- await respondWith(
- new Response(new Uint8Array([128]), {}),
- );
- })();
-
- const resp = await fetch(`http://localhost:${listenPort}/`);
- assertEquals(resp.status, 200);
- const body = await resp.arrayBuffer();
- assertEquals(new Uint8Array(body), new Uint8Array([128]));
-
- await promise;
- httpConn!.close();
- },
-);
-
-function tmpUnixSocketPath(): string {
- const folder = Deno.makeTempDirSync();
- return join(folder, "socket");
-}
-
-// https://github.com/denoland/deno/pull/13628
-Deno.test(
- {
- ignore: Deno.build.os === "windows",
- permissions: { read: true, write: true },
- },
- async function httpServerOnUnixSocket() {
- const filePath = tmpUnixSocketPath();
-
- let httpConn: Deno.HttpConn;
- const promise = (async () => {
- const listener = Deno.listen({ path: filePath, transport: "unix" });
- const conn = await listener.accept();
- listener.close();
- httpConn = Deno.serveHttp(conn);
- const reqEvent = await httpConn.nextRequest();
- assert(reqEvent);
- const { request, respondWith } = reqEvent;
- const url = new URL(request.url);
- assertEquals(url.protocol, "http+unix:");
- assertEquals(decodeURIComponent(url.host), filePath);
- assertEquals(url.pathname, "/path/name");
- await respondWith(new Response("", { headers: {} }));
- })();
-
- // fetch() does not supports unix domain sockets yet https://github.com/denoland/deno/issues/8821
- const conn = await Deno.connect({ path: filePath, transport: "unix" });
- const encoder = new TextEncoder();
- // The Host header must be present and empty if it is not a Internet host name (RFC2616, Section 14.23)
- const body = `GET /path/name HTTP/1.1\r\nHost:\r\n\r\n`;
- const writeResult = await conn.write(encoder.encode(body));
- assertEquals(body.length, writeResult);
-
- const resp = new Uint8Array(200);
- const readResult = await conn.read(resp);
- assertEquals(readResult, 138);
-
- conn.close();
-
- await promise;
- httpConn!.close();
- },
-);
-
-/* Automatic Body Compression */
-
-const decoder = new TextDecoder();
-
-Deno.test({
- name: "http server compresses body - check headers",
- permissions: { net: true, run: true },
- async fn() {
- const hostname = "localhost";
- const port = listenPort;
- const listener = Deno.listen({ hostname, port });
-
- const data = { hello: "deno", now: "with", compressed: "body" };
-
- let httpConn: Deno.HttpConn;
- async function server() {
- const tcpConn = await listener.accept();
- httpConn = Deno.serveHttp(tcpConn);
- const e = await httpConn.nextRequest();
- assert(e);
- const { request, respondWith } = e;
- assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br");
- const response = new Response(JSON.stringify(data), {
- headers: { "content-type": "application/json" },
- });
- await respondWith(response);
- listener.close();
- }
-
- async function client() {
- const url = `http://${hostname}:${port}/`;
- const args = [
- "-i",
- "--request",
- "GET",
- "--url",
- url,
- "--header",
- "Accept-Encoding: gzip, deflate, br",
- ];
- const { success, stdout } = await new Deno.Command("curl", {
- args,
- stderr: "null",
- stdout: "piped",
- }).output();
- assert(success);
- const output = decoder.decode(stdout);
- assert(output.includes("vary: Accept-Encoding\r\n"));
- assert(output.includes("content-encoding: gzip\r\n"));
- }
-
- await Promise.all([server(), client()]);
- httpConn!.close();
- },
-});
-
-Deno.test({
- name: "http server compresses body - check body",
- permissions: { net: true, run: true },
- async fn() {
- const hostname = "localhost";
- const port = listenPort;
- const listener = Deno.listen({ hostname, port });
-
- const data = { hello: "deno", now: "with", compressed: "body" };
-
- let httpConn: Deno.HttpConn;
- async function server() {
- const tcpConn = await listener.accept();
- httpConn = Deno.serveHttp(tcpConn);
- const e = await httpConn.nextRequest();
- assert(e);
- const { request, respondWith } = e;
- assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br");
- const response = new Response(JSON.stringify(data), {
- headers: { "content-type": "application/json" },
- });
- await respondWith(response);
- listener.close();
- }
-
- async function client() {
- const url = `http://${hostname}:${port}/`;
- const args = [
- "--request",
- "GET",
- "--url",
- url,
- "--header",
- "Accept-Encoding: gzip, deflate, br",
- ];
- const proc = new Deno.Command("curl", {
- args,
- stderr: "null",
- stdout: "piped",
- }).spawn();
- const status = await proc.status;
- assert(status.success);
- const stdout = proc.stdout
- .pipeThrough(new DecompressionStream("gzip"))
- .pipeThrough(new TextDecoderStream());
- let body = "";
- for await (const chunk of stdout) {
- body += chunk;
- }
- assertEquals(JSON.parse(body), data);
- }
-
- await Promise.all([server(), client()]);
- httpConn!.close();
- },
-});
-
-Deno.test({
- name: "http server doesn't compress small body",
- permissions: { net: true, run: true },
- async fn() {
- const hostname = "localhost";
- const port = listenPort;
-
- let httpConn: Deno.HttpConn;
- async function server() {
- const listener = Deno.listen({ hostname, port });
- const tcpConn = await listener.accept();
- httpConn = Deno.serveHttp(tcpConn);
- const e = await httpConn.nextRequest();
- assert(e);
- const { request, respondWith } = e;
- assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br");
- const response = new Response(
- JSON.stringify({ hello: "deno" }),
- {
- headers: { "content-type": "application/json" },
- },
- );
- await respondWith(response);
- listener.close();
- }
-
- async function client() {
- const url = `http://${hostname}:${port}/`;
- const args = [
- "-i",
- "--request",
- "GET",
- "--url",
- url,
- "--header",
- "Accept-Encoding: gzip, deflate, br",
- ];
- const { success, stdout } = await new Deno.Command("curl", {
- args,
- stderr: "null",
- stdout: "piped",
- }).output();
- assert(success);
- const output = decoder.decode(stdout).toLocaleLowerCase();
- assert(output.includes("vary: accept-encoding\r\n"));
- assert(!output.includes("content-encoding: "));
- }
-
- await Promise.all([server(), client()]);
- httpConn!.close();
- },
-});
-
-Deno.test({
- name: "http server respects accept-encoding weights",
- permissions: { net: true, run: true },
- async fn() {
- const hostname = "localhost";
- const port = listenPort;
-
- let httpConn: Deno.HttpConn;
- async function server() {
- const listener = Deno.listen({ hostname, port });
- const tcpConn = await listener.accept();
- httpConn = Deno.serveHttp(tcpConn);
- const e = await httpConn.nextRequest();
- assert(e);
- const { request, respondWith } = e;
- assertEquals(
- request.headers.get("Accept-Encoding"),
- "gzip;q=0.8, br;q=1.0, *;q=0.1",
- );
- const response = new Response(
- JSON.stringify({ hello: "deno", now: "with", compressed: "body" }),
- {
- headers: { "content-type": "application/json" },
- },
- );
- await respondWith(response);
- listener.close();
- }
-
- async function client() {
- const url = `http://${hostname}:${port}/`;
- const args = [
- "-i",
- "--request",
- "GET",
- "--url",
- url,
- "--header",
- "Accept-Encoding: gzip;q=0.8, br;q=1.0, *;q=0.1",
- ];
- const { success, stdout } = await new Deno.Command("curl", {
- args,
- stderr: "null",
- stdout: "piped",
- }).output();
- assert(success);
- const output = decoder.decode(stdout);
- assert(output.includes("vary: Accept-Encoding\r\n"));
- assert(output.includes("content-encoding: br\r\n"));
- }
-
- await Promise.all([server(), client()]);
- httpConn!.close();
- },
-});
-
-Deno.test({
- name: "http server augments vary header",
- permissions: { net: true, run: true },
- async fn() {
- const hostname = "localhost";
- const port = listenPort;
-
- let httpConn: Deno.HttpConn;
- async function server() {
- const listener = Deno.listen({ hostname, port });
- const tcpConn = await listener.accept();
- httpConn = Deno.serveHttp(tcpConn);
- const e = await httpConn.nextRequest();
- assert(e);
- const { request, respondWith } = e;
- assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br");
- const response = new Response(
- JSON.stringify({ hello: "deno", now: "with", compressed: "body" }),
- {
- headers: { "content-type": "application/json", vary: "Accept" },
- },
- );
- await respondWith(response);
- listener.close();
- }
-
- async function client() {
- const url = `http://${hostname}:${port}/`;
- const args = [
- "-i",
- "--request",
- "GET",
- "--url",
- url,
- "--header",
- "Accept-Encoding: gzip, deflate, br",
- ];
- const { success, stdout } = await new Deno.Command("curl", {
- args,
- stderr: "null",
- stdout: "piped",
- }).output();
- assert(success);
- const output = decoder.decode(stdout);
- assert(output.includes("vary: Accept-Encoding, Accept\r\n"));
- assert(output.includes("content-encoding: gzip\r\n"));
- }
-
- await Promise.all([server(), client()]);
- httpConn!.close();
- },
-});
-
-Deno.test({
- name: "http server weakens etag header",
- permissions: { net: true, run: true },
- async fn() {
- const hostname = "localhost";
- const port = listenPort;
-
- let httpConn: Deno.HttpConn;
- async function server() {
- const listener = Deno.listen({ hostname, port });
- const tcpConn = await listener.accept();
- httpConn = Deno.serveHttp(tcpConn);
- const e = await httpConn.nextRequest();
- assert(e);
- const { request, respondWith } = e;
- assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br");
- const response = new Response(
- JSON.stringify({ hello: "deno", now: "with", compressed: "body" }),
- {
- headers: {
- "content-type": "application/json",
- etag: "33a64df551425fcc55e4d42a148795d9f25f89d4",
- },
- },
- );
- await respondWith(response);
- listener.close();
- }
-
- async function client() {
- const url = `http://${hostname}:${port}/`;
- const args = [
- "curl",
- "-i",
- "--request",
- "GET",
- "--url",
- url,
- "--header",
- "Accept-Encoding: gzip, deflate, br",
- ];
- const { success, stdout } = await new Deno.Command("curl", {
- args,
- stderr: "null",
- stdout: "piped",
- }).output();
- assert(success);
- const output = decoder.decode(stdout);
- assert(output.includes("vary: Accept-Encoding\r\n"));
- assert(
- output.includes("etag: W/33a64df551425fcc55e4d42a148795d9f25f89d4\r\n"),
- );
- assert(output.includes("content-encoding: gzip\r\n"));
- }
-
- await Promise.all([server(), client()]);
- httpConn!.close();
- },
-});
-
-Deno.test({
- name: "http server passes through weak etag header",
- permissions: { net: true, run: true },
- async fn() {
- const hostname = "localhost";
- const port = listenPort;
-
- let httpConn: Deno.HttpConn;
- async function server() {
- const listener = Deno.listen({ hostname, port });
- const tcpConn = await listener.accept();
- httpConn = Deno.serveHttp(tcpConn);
- const e = await httpConn.nextRequest();
- assert(e);
- const { request, respondWith } = e;
- assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br");
- const response = new Response(
- JSON.stringify({ hello: "deno", now: "with", compressed: "body" }),
- {
- headers: {
- "content-type": "application/json",
- etag: "W/33a64df551425fcc55e4d42a148795d9f25f89d4",
- },
- },
- );
- await respondWith(response);
- listener.close();
- }
-
- async function client() {
- const url = `http://${hostname}:${port}/`;
- const args = [
- "-i",
- "--request",
- "GET",
- "--url",
- url,
- "--header",
- "Accept-Encoding: gzip, deflate, br",
- ];
- const { success, stdout } = await new Deno.Command("curl", {
- args,
- stderr: "null",
- stdout: "piped",
- }).output();
- assert(success);
- const output = decoder.decode(stdout);
- assert(output.includes("vary: Accept-Encoding\r\n"));
- assert(
- output.includes("etag: W/33a64df551425fcc55e4d42a148795d9f25f89d4\r\n"),
- );
- assert(output.includes("content-encoding: gzip\r\n"));
- }
-
- await Promise.all([server(), client()]);
- httpConn!.close();
- },
-});
-
-Deno.test({
- name: "http server doesn't compress body when no-transform is set",
- permissions: { net: true, run: true },
- async fn() {
- const hostname = "localhost";
- const port = listenPort;
-
- let httpConn: Deno.HttpConn;
- async function server() {
- const listener = Deno.listen({ hostname, port });
- const tcpConn = await listener.accept();
- httpConn = Deno.serveHttp(tcpConn);
- const e = await httpConn.nextRequest();
- assert(e);
- const { request, respondWith } = e;
- assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br");
- const response = new Response(
- JSON.stringify({ hello: "deno", now: "with", compressed: "body" }),
- {
- headers: {
- "content-type": "application/json",
- "cache-control": "no-transform",
- },
- },
- );
- await respondWith(response);
- listener.close();
- }
-
- async function client() {
- const url = `http://${hostname}:${port}/`;
- const args = [
- "-i",
- "--request",
- "GET",
- "--url",
- url,
- "--header",
- "Accept-Encoding: gzip, deflate, br",
- ];
- const { success, stdout } = await new Deno.Command("curl", {
- args,
- stderr: "null",
- stdout: "piped",
- }).output();
- assert(success);
- const output = decoder.decode(stdout);
- assert(output.includes("vary: Accept-Encoding\r\n"));
- assert(!output.includes("content-encoding: "));
- }
-
- await Promise.all([server(), client()]);
- httpConn!.close();
- },
-});
-
-Deno.test({
- name: "http server doesn't compress body when content-range is set",
- permissions: { net: true, run: true },
- async fn() {
- const hostname = "localhost";
- const port = listenPort;
-
- let httpConn: Deno.HttpConn;
- async function server() {
- const listener = Deno.listen({ hostname, port });
- const tcpConn = await listener.accept();
- httpConn = Deno.serveHttp(tcpConn);
- const e = await httpConn.nextRequest();
- assert(e);
- const { request, respondWith } = e;
- assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br");
- const response = new Response(
- JSON.stringify({ hello: "deno", now: "with", compressed: "body" }),
- {
- headers: {
- "content-type": "application/json",
- "content-range": "bytes 200-100/67589",
- },
- },
- );
- await respondWith(response);
- listener.close();
- }
-
- async function client() {
- const url = `http://${hostname}:${port}/`;
- const args = [
- "-i",
- "--request",
- "GET",
- "--url",
- url,
- "--header",
- "Accept-Encoding: gzip, deflate, br",
- ];
- const { success, stdout } = await new Deno.Command("curl", {
- args,
- stderr: "null",
- stdout: "piped",
- }).output();
- assert(success);
- const output = decoder.decode(stdout);
- assert(output.includes("vary: Accept-Encoding\r\n"));
- assert(!output.includes("content-encoding: "));
- }
-
- await Promise.all([server(), client()]);
- httpConn!.close();
- },
-});
-
-Deno.test({
- name: "http server compresses streamed bodies - check headers",
- permissions: { net: true, run: true },
- async fn() {
- const hostname = "localhost";
- const port = listenPort;
-
- const encoder = new TextEncoder();
- const listener = Deno.listen({ hostname, port });
-
- const data = { hello: "deno", now: "with", compressed: "body" };
-
- let httpConn: Deno.HttpConn;
- async function server() {
- const tcpConn = await listener.accept();
- httpConn = Deno.serveHttp(tcpConn);
- const e = await httpConn.nextRequest();
- assert(e);
- const { request, respondWith } = e;
- assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br");
- const bodyInit = new ReadableStream({
- start(controller) {
- controller.enqueue(encoder.encode(JSON.stringify(data)));
- controller.close();
- },
- });
- const response = new Response(
- bodyInit,
- { headers: { "content-type": "application/json" } },
- );
- await respondWith(response);
- listener.close();
- }
-
- async function client() {
- const url = `http://${hostname}:${port}/`;
- const args = [
- "curl",
- "-i",
- "--request",
- "GET",
- "--url",
- url,
- "--header",
- "Accept-Encoding: gzip, deflate, br",
- ];
- const { success, stdout } = await new Deno.Command("curl", {
- args,
- stderr: "null",
- stdout: "piped",
- }).output();
- assert(success);
- const output = decoder.decode(stdout);
- assert(output.includes("vary: Accept-Encoding\r\n"));
- assert(output.includes("content-encoding: gzip\r\n"));
- }
-
- await Promise.all([server(), client()]);
- httpConn!.close();
- },
-});
-
-Deno.test({
- name: "http server compresses streamed bodies - check body",
- permissions: { net: true, run: true },
- async fn() {
- const hostname = "localhost";
- const port = listenPort;
-
- const encoder = new TextEncoder();
- const listener = Deno.listen({ hostname, port });
-
- const data = { hello: "deno", now: "with", compressed: "body" };
-
- let httpConn: Deno.HttpConn;
- async function server() {
- const tcpConn = await listener.accept();
- httpConn = Deno.serveHttp(tcpConn);
- const e = await httpConn.nextRequest();
- assert(e);
- const { request, respondWith } = e;
- assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br");
- const bodyInit = new ReadableStream({
- start(controller) {
- controller.enqueue(encoder.encode(JSON.stringify(data)));
- controller.close();
- },
- });
- const response = new Response(
- bodyInit,
- { headers: { "content-type": "application/json" } },
- );
- await respondWith(response);
- listener.close();
- }
-
- async function client() {
- const url = `http://${hostname}:${port}/`;
- const args = [
- "--request",
- "GET",
- "--url",
- url,
- "--header",
- "Accept-Encoding: gzip, deflate, br",
- ];
- const proc = new Deno.Command("curl", {
- args,
- stderr: "null",
- stdout: "piped",
- }).spawn();
- const status = await proc.status;
- assert(status.success);
- const stdout = proc.stdout
- .pipeThrough(new DecompressionStream("gzip"))
- .pipeThrough(new TextDecoderStream());
- let body = "";
- for await (const chunk of stdout) {
- body += chunk;
- }
- assertEquals(JSON.parse(body), data);
- }
-
- await Promise.all([server(), client()]);
- httpConn!.close();
- },
-});
-
-Deno.test({
- name: "http server updates content-length header if compression is applied",
- permissions: { net: true, run: true },
- async fn() {
- const hostname = "localhost";
- const port = listenPort;
- let contentLength: string;
-
- let httpConn: Deno.HttpConn;
- async function server() {
- const listener = Deno.listen({ hostname, port });
- const tcpConn = await listener.accept();
- httpConn = Deno.serveHttp(tcpConn);
- const e = await httpConn.nextRequest();
- assert(e);
- const { request, respondWith } = e;
- assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br");
- const body = JSON.stringify({
- hello: "deno",
- now: "with",
- compressed: "body",
- });
- contentLength = String(body.length);
- const response = new Response(
- body,
- {
- headers: {
- "content-type": "application/json",
- "content-length": contentLength,
- },
- },
- );
- await respondWith(response);
- listener.close();
- }
-
- async function client() {
- const url = `http://${hostname}:${port}/`;
- const args = [
- "-i",
- "--request",
- "GET",
- "--url",
- url,
- "--header",
- "Accept-Encoding: gzip, deflate, br",
- ];
- const { success, stdout } = await new Deno.Command("curl", {
- args,
- stderr: "null",
- stdout: "piped",
- }).output();
- assert(success);
- const output = decoder.decode(stdout);
- assert(output.includes("vary: Accept-Encoding\r\n"));
- assert(output.includes("content-encoding: gzip\r\n"));
- // Ensure the content-length header is updated (but don't check the exact length).
- assert(!output.includes(`content-length: ${contentLength}\r\n`));
- assert(output.includes("content-length: "));
- }
-
- await Promise.all([server(), client()]);
- httpConn!.close();
- },
-});
-
-Deno.test({
- name: "http server compresses when accept-encoding is deflate, gzip",
- permissions: { net: true, run: true },
- async fn() {
- const hostname = "localhost";
- const port = listenPort;
- let contentLength: string;
-
- let httpConn: Deno.HttpConn;
- async function server() {
- const listener = Deno.listen({ hostname, port });
- const tcpConn = await listener.accept();
- httpConn = Deno.serveHttp(tcpConn);
- const e = await httpConn.nextRequest();
- assert(e);
- const { request, respondWith } = e;
- assertEquals(request.headers.get("Accept-Encoding"), "deflate, gzip");
- const body = "x".repeat(10000);
- contentLength = String(body.length);
- const response = new Response(
- body,
- {
- headers: {
- "content-length": contentLength,
- },
- },
- );
- await respondWith(response);
- listener.close();
- }
-
- async function client() {
- const url = `http://${hostname}:${port}/`;
- const cmd = [
- "curl",
- "-i",
- "--request",
- "GET",
- "--url",
- url,
- // "--compressed", // Windows curl does not support --compressed
- "--header",
- "Accept-Encoding: deflate, gzip",
- ];
- // deno-lint-ignore no-deprecated-deno-api
- const proc = Deno.run({ cmd, stdout: "piped", stderr: "null" });
- const status = await proc.status();
- assert(status.success);
- const output = decoder.decode(await proc.output());
- assert(output.includes("vary: Accept-Encoding\r\n"));
- assert(output.includes("content-encoding: gzip\r\n"));
- // Ensure the content-length header is updated.
- assert(!output.includes(`content-length: ${contentLength}\r\n`));
- assert(output.includes("content-length: "));
- proc.close();
- }
-
- await Promise.all([server(), client()]);
- httpConn!.close();
- },
-});
-
-Deno.test({
- name: "http server custom content-encoding is left untouched",
- permissions: { net: true, run: true },
- async fn() {
- const hostname = "localhost";
- const port = listenPort;
- let contentLength: string;
-
- let httpConn: Deno.HttpConn;
- async function server() {
- const listener = Deno.listen({ hostname, port });
- const tcpConn = await listener.accept();
- httpConn = Deno.serveHttp(tcpConn);
- const e = await httpConn.nextRequest();
- assert(e);
- const { request, respondWith } = e;
- assertEquals(request.headers.get("Accept-Encoding"), "deflate, gzip");
- const body = new Uint8Array([3, 1, 4, 1]);
- contentLength = String(body.length);
- const response = new Response(
- body,
- {
- headers: {
- "content-length": contentLength,
- "content-encoding": "arbitrary",
- },
- },
- );
- await respondWith(response);
- listener.close();
- }
-
- async function client() {
- const url = `http://${hostname}:${port}/`;
- const cmd = [
- "curl",
- "-i",
- "--request",
- "GET",
- "--url",
- url,
- // "--compressed", // Windows curl does not support --compressed
- "--header",
- "Accept-Encoding: deflate, gzip",
- ];
- // deno-lint-ignore no-deprecated-deno-api
- const proc = Deno.run({ cmd, stdout: "piped", stderr: "null" });
- const status = await proc.status();
- assert(status.success);
- const output = decoder.decode(await proc.output());
- assert(output.includes("vary: Accept-Encoding\r\n"));
- assert(output.includes("content-encoding: arbitrary\r\n"));
- proc.close();
- }
-
- await Promise.all([server(), client()]);
- httpConn!.close();
- },
-});
-
-Deno.test(
- { permissions: { net: true } },
- async function httpServerReadLargeBodyWithContentLength() {
- const TLS_PACKET_SIZE = 16 * 1024 + 256;
- // We want the body to be read in multiple packets
- const body = "aa\n" + "deno.land large body\n".repeat(TLS_PACKET_SIZE) +
- "zz";
-
- let httpConn: Deno.HttpConn;
- const promise = (async () => {
- const listener = Deno.listen({ port: listenPort });
- const conn = await listener.accept();
- listener.close();
- httpConn = Deno.serveHttp(conn);
- const reqEvent = await httpConn.nextRequest();
- assert(reqEvent);
- const { request, respondWith } = reqEvent;
- assertEquals(await request.text(), body);
- await respondWith(new Response(body));
- })();
-
- const resp = await fetch(`http://127.0.0.1:${listenPort}/`, {
- method: "POST",
- headers: { "connection": "close" },
- body,
- });
- const text = await resp.text();
- assertEquals(text, body);
- await promise;
-
- httpConn!.close();
- },
-);
-
-Deno.test(
- { permissions: { net: true } },
- async function httpServerReadLargeBodyWithTransferChunked() {
- const TLS_PACKET_SIZE = 16 * 1024 + 256;
-
- // We want the body to be read in multiple packets
- const chunks = [
- "aa\n",
- "deno.land large body\n".repeat(TLS_PACKET_SIZE),
- "zz",
- ];
-
- const body = chunks.join("");
-
- const stream = new TransformStream();
- const writer = stream.writable.getWriter();
- for (const chunk of chunks) {
- writer.write(new TextEncoder().encode(chunk));
- }
- writer.close();
-
- let httpConn: Deno.HttpConn;
- const promise = (async () => {
- const listener = Deno.listen({ port: listenPort });
- const conn = await listener.accept();
- listener.close();
- httpConn = Deno.serveHttp(conn);
- const reqEvent = await httpConn.nextRequest();
- assert(reqEvent);
- const { request, respondWith } = reqEvent;
- assertEquals(await request.text(), body);
- await respondWith(new Response(body));
- })();
-
- const resp = await fetch(`http://127.0.0.1:${listenPort}/`, {
- method: "POST",
- headers: { "connection": "close" },
- body: stream.readable,
- });
- const text = await resp.text();
- assertEquals(text, body);
- await promise;
-
- httpConn!.close();
- },
-);
-
-Deno.test(
- {
- permissions: { net: true },
- },
- async function httpServerWithoutExclusiveAccessToTcp() {
- const port = listenPort;
- const listener = Deno.listen({ port });
-
- const [clientConn, serverConn] = await Promise.all([
- Deno.connect({ port }),
- listener.accept(),
- ]);
-
- const buf = new Uint8Array(128);
- const readPromise = serverConn.read(buf);
- assertThrows(() => Deno.serveHttp(serverConn), Deno.errors.BadResource);
-
- clientConn.close();
- listener.close();
- await readPromise;
- },
-);
-
-Deno.test(
- {
- permissions: { net: true, read: true },
- },
- async function httpServerWithoutExclusiveAccessToTls() {
- const hostname = "localhost";
- const port = listenPort;
- const listener = Deno.listenTls({
- hostname,
- port,
- cert: await Deno.readTextFile("cli/tests/testdata/tls/localhost.crt"),
- key: await Deno.readTextFile("cli/tests/testdata/tls/localhost.key"),
- });
-
- const caCerts = [
- await Deno.readTextFile("cli/tests/testdata/tls/RootCA.pem"),
- ];
- const [clientConn, serverConn] = await Promise.all([
- Deno.connectTls({ hostname, port, caCerts }),
- listener.accept(),
- ]);
- await Promise.all([clientConn.handshake(), serverConn.handshake()]);
-
- const buf = new Uint8Array(128);
- const readPromise = serverConn.read(buf);
- assertThrows(() => Deno.serveHttp(serverConn), Deno.errors.BadResource);
-
- clientConn.close();
- listener.close();
- await readPromise;
- },
-);
-
-Deno.test(
- {
- ignore: Deno.build.os === "windows",
- permissions: { read: true, write: true },
- },
- async function httpServerWithoutExclusiveAccessToUnixSocket() {
- const filePath = tmpUnixSocketPath();
- const listener = Deno.listen({ path: filePath, transport: "unix" });
-
- const [clientConn, serverConn] = await Promise.all([
- Deno.connect({ path: filePath, transport: "unix" }),
- listener.accept(),
- ]);
-
- const buf = new Uint8Array(128);
- const readPromise = serverConn.read(buf);
- assertThrows(() => Deno.serveHttp(serverConn), Deno.errors.BadResource);
-
- clientConn.close();
- listener.close();
- await readPromise;
- },
-);
-
-Deno.test(
- { permissions: { net: true } },
- async function httpServerRequestResponseClone() {
- const body = "deno".repeat(64 * 1024);
- let httpConn: Deno.HttpConn;
- const listener = Deno.listen({ port: listenPort });
- const promise = (async () => {
- const conn = await listener.accept();
- listener.close();
- httpConn = Deno.serveHttp(conn);
- const reqEvent = await httpConn.nextRequest();
- assert(reqEvent);
- const { request, respondWith } = reqEvent;
- const clone = request.clone();
- const reader = clone.body!.getReader();
-
- // get first chunk from branch2
- const clonedChunks = [];
- const { value, done } = await reader.read();
- assert(!done);
- clonedChunks.push(value);
-
- // consume request after first chunk single read
- // readAll should read correctly the rest of the body.
- // firstChunk should be in the stream internal buffer
- const body1 = await request.text();
-
- while (true) {
- const { value, done } = await reader.read();
- if (done) break;
- clonedChunks.push(value);
- }
- let offset = 0;
- const body2 = new Uint8Array(body.length);
- for (const chunk of clonedChunks) {
- body2.set(chunk, offset);
- offset += chunk.byteLength;
- }
-
- assertEquals(body1, body);
- assertEquals(body1, new TextDecoder().decode(body2));
- await respondWith(new Response(body));
- })();
-
- const response = await fetch(`http://localhost:${listenPort}`, {
- body,
- method: "POST",
- });
- const clone = response.clone();
- assertEquals(await response.text(), await clone.text());
-
- await promise;
- httpConn!.close();
- },
-);
-
-Deno.test({
- name: "http server compresses and flushes each chunk of a streamed resource",
- permissions: { net: true, run: true },
- async fn() {
- const hostname = "localhost";
- const port = listenPort;
- const port2 = listenPort2;
-
- const encoder = new TextEncoder();
- const listener = Deno.listen({ hostname, port });
- const listener2 = Deno.listen({ hostname, port: port2 });
-
- let httpConn: Deno.HttpConn;
- async function server() {
- const tcpConn = await listener.accept();
- httpConn = Deno.serveHttp(tcpConn);
- const e = await httpConn.nextRequest();
- assert(e);
- const { request, respondWith } = e;
- assertEquals(request.headers.get("Accept-Encoding"), "gzip, deflate, br");
- const resp = await fetch(`http://${hostname}:${port2}/`);
- await respondWith(resp);
- listener.close();
- }
-
- const ts = new TransformStream();
- const writer = ts.writable.getWriter();
- writer.write(encoder.encode("hello"));
-
- let httpConn2: Deno.HttpConn;
- async function server2() {
- const tcpConn = await listener2.accept();
- httpConn2 = Deno.serveHttp(tcpConn);
- const e = await httpConn2.nextRequest();
- assert(e);
- await e.respondWith(
- new Response(ts.readable, {
- headers: { "Content-Type": "text/plain" },
- }),
- );
- listener2.close();
- }
-
- async function client() {
- const url = `http://${hostname}:${port}/`;
- const args = [
- "--request",
- "GET",
- "--url",
- url,
- "--header",
- "Accept-Encoding: gzip, deflate, br",
- "--no-buffer",
- ];
- const proc = new Deno.Command("curl", {
- args,
- stderr: "null",
- stdout: "piped",
- }).spawn();
- const stdout = proc.stdout
- .pipeThrough(new DecompressionStream("gzip"))
- .pipeThrough(new TextDecoderStream());
- let body = "";
- for await (const chunk of stdout) {
- body += chunk;
- if (body === "hello") {
- writer.write(encoder.encode(" world"));
- writer.close();
- }
- }
- assertEquals(body, "hello world");
- const status = await proc.status;
- assert(status.success);
- }
-
- await Promise.all([server(), server2(), client()]);
- httpConn!.close();
- httpConn2!.close();
- },
-});
-
-Deno.test("case insensitive comma value finder", async (t) => {
- const cases = /** @type {[string, boolean][]} */ ([
- ["websocket", true],
- ["wEbSOcKET", true],
- [",wEbSOcKET", true],
- [",wEbSOcKET,", true],
- [", wEbSOcKET ,", true],
- ["test, wEbSOcKET ,", true],
- ["test ,\twEbSOcKET\t\t ,", true],
- ["test , wEbSOcKET", true],
- ["test, asdf,web,wEbSOcKET", true],
- ["test, asdf,web,wEbSOcKETs", false],
- ["test, asdf,awebsocket,wEbSOcKETs", false],
- ]);
-
- const findValue = buildCaseInsensitiveCommaValueFinder("websocket");
- for (const [input, expected] of cases) {
- await t.step(input.toString(), () => {
- const actual = findValue(input);
- assertEquals(actual, expected);
- });
- }
-});
-
-async function httpServerWithErrorBody(
- listener: Deno.Listener,
- compression: boolean,
-): Promise<Deno.HttpConn> {
- const conn = await listener.accept();
- listener.close();
- const httpConn = Deno.serveHttp(conn);
- const e = await httpConn.nextRequest();
- assert(e);
- const { respondWith } = e;
- const originalErr = new Error("boom");
- const rs = new ReadableStream({
- async start(controller) {
- controller.enqueue(new Uint8Array([65]));
- await delay(1000);
- controller.error(originalErr);
- },
- });
- const init = compression ? { headers: { "content-type": "text/plain" } } : {};
- const response = new Response(rs, init);
- const err = await assertRejects(() => respondWith(response));
- assert(err === originalErr);
- return httpConn;
-}
-
-for (const compression of [true, false]) {
- Deno.test({
- name: `http server errors stream if response body errors (http/1.1${
- compression ? " + compression" : ""
- })`,
- permissions: { net: true },
- async fn() {
- const hostname = "localhost";
- const port = listenPort;
-
- const listener = Deno.listen({ hostname, port });
- const server = httpServerWithErrorBody(listener, compression);
-
- const conn = await Deno.connect({ hostname, port });
- const msg = new TextEncoder().encode(
- `GET / HTTP/1.1\r\nHost: ${hostname}:${port}\r\n\r\n`,
- );
- const nwritten = await conn.write(msg);
- assertEquals(nwritten, msg.byteLength);
-
- const buf = new Uint8Array(1024);
- const nread = await conn.read(buf);
- assert(nread);
- const data = new TextDecoder().decode(buf.subarray(0, nread));
- assert(data.endsWith("1\r\nA\r\n"));
- const nread2 = await conn.read(buf); // connection should be closed now because the stream errored
- assertEquals(nread2, null);
- conn.close();
-
- const httpConn = await server;
- httpConn.close();
- },
- });
-
- Deno.test({
- name: `http server errors stream if response body errors (http/1.1 + fetch${
- compression ? " + compression" : ""
- })`,
- permissions: { net: true },
- async fn() {
- const hostname = "localhost";
- const port = listenPort;
-
- const listener = Deno.listen({ hostname, port });
- const server = httpServerWithErrorBody(listener, compression);
-
- const resp = await fetch(`http://${hostname}:${port}/`);
- assert(resp.body);
- const reader = resp.body.getReader();
- const result = await reader.read();
- assert(!result.done);
- assertEquals(result.value, new Uint8Array([65]));
- const err = await assertRejects(() => reader.read());
- assert(err instanceof TypeError);
- assert(err.message.includes("unexpected EOF"));
-
- const httpConn = await server;
- httpConn.close();
- },
- });
-
- Deno.test({
- name: `http server errors stream if response body errors (http/2 + fetch${
- compression ? " + compression" : ""
- }))`,
- permissions: { net: true, read: true },
- async fn() {
- const hostname = "localhost";
- const port = listenPort;
-
- const listener = Deno.listenTls({
- hostname,
- port,
- cert: await Deno.readTextFile("cli/tests/testdata/tls/localhost.crt"),
- key: await Deno.readTextFile("cli/tests/testdata/tls/localhost.key"),
- alpnProtocols: ["h2"],
- });
- const server = httpServerWithErrorBody(listener, compression);
-
- const caCert = Deno.readTextFileSync("cli/tests/testdata/tls/RootCA.pem");
- const client = Deno.createHttpClient({ caCerts: [caCert] });
- const resp = await fetch(`https://${hostname}:${port}/`, { client });
- client.close();
- assert(resp.body);
- const reader = resp.body.getReader();
- const result = await reader.read();
- assert(!result.done);
- assertEquals(result.value, new Uint8Array([65]));
- const err = await assertRejects(() => reader.read());
- assert(err instanceof TypeError);
- assert(err.message.includes("unexpected internal error encountered"));
-
- const httpConn = await server;
- httpConn.close();
- },
- });
-}
-
-Deno.test({
- name: "request signal is aborted when response errors",
- permissions: { net: true },
- async fn() {
- let httpConn: Deno.HttpConn;
- const promise = (async () => {
- const listener = Deno.listen({ port: listenPort });
- const conn = await listener.accept();
- listener.close();
- httpConn = Deno.serveHttp(conn);
- const ev = await httpConn.nextRequest();
- const { request, respondWith } = ev!;
-
- await delay(300);
- await assertRejects(() => respondWith(new Response("Hello World")));
- assert(request.signal.aborted);
- })();
-
- const abortController = new AbortController();
-
- fetch(`http://127.0.0.1:${listenPort}/`, {
- signal: abortController.signal,
- }).catch(() => {
- // ignore
- });
-
- await delay(100);
- abortController.abort();
- await promise;
- httpConn!.close();
- },
-});
-
-Deno.test(
- async function httpConnExplicitResourceManagement() {
- let promise;
-
- {
- const listen = Deno.listen({ port: listenPort });
- promise = fetch(`http://localhost:${listenPort}/`).catch(() => null);
- const serverConn = await listen.accept();
- listen.close();
-
- using _httpConn = Deno.serveHttp(serverConn);
- }
-
- const response = await promise;
- assertEquals(response, null);
- },
-);
-
-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);
- const result = await tp.readMimeHeader();
- if (result == null) {
- throw new Deno.errors.InvalidData("Missing trailer header.");
- }
- const undeclared = [...result.keys()].filter(
- (k) => !trailerNames.includes(k),
- );
- if (undeclared.length > 0) {
- throw new Deno.errors.InvalidData(
- `Undeclared trailers: ${Deno.inspect(undeclared)}.`,
- );
- }
- for (const [k, v] of result) {
- headers.append(k, v);
- }
- const missingTrailers = trailerNames.filter((k) => !result.has(k));
- if (missingTrailers.length > 0) {
- throw new Deno.errors.InvalidData(
- `Missing trailers: ${Deno.inspect(missingTrailers)}.`,
- );
- }
- headers.delete("trailer");
-}
-
-function parseTrailer(field: string | null): Headers | undefined {
- if (field == null) {
- return undefined;
- }
- const trailerNames = field.split(",").map((v) => v.trim().toLowerCase());
- if (trailerNames.length === 0) {
- throw new Deno.errors.InvalidData("Empty trailer header.");
- }
- const prohibited = trailerNames.filter((k) => isProhibitedForTrailer(k));
- if (prohibited.length > 0) {
- throw new Deno.errors.InvalidData(
- `Prohibited trailer names: ${Deno.inspect(prohibited)}.`,
- );
- }
- return new Headers(trailerNames.map((key) => [key, ""]));
-}
-
-function isProhibitedForTrailer(key: string): boolean {
- const s = new Set(["transfer-encoding", "content-length", "trailer"]);
- return s.has(key.toLowerCase());
-}