summaryrefslogtreecommitdiff
path: root/std/http
diff options
context:
space:
mode:
authorCasper Beyer <caspervonb@pm.me>2021-02-02 19:05:46 +0800
committerGitHub <noreply@github.com>2021-02-02 12:05:46 +0100
commit6abf126c2a7a451cded8c6b5e6ddf1b69c84055d (patch)
treefd94c013a19fcb38954844085821ec1601c20e18 /std/http
parenta2b5d44f1aa9d64f448a2a3cc2001272e2f60b98 (diff)
chore: remove std directory (#9361)
This removes the std folder from the tree. Various parts of the tests are pretty tightly dependent on std (47 direct imports and 75 indirect imports, not counting the cli tests that use them as fixtures) so I've added std as a submodule for now.
Diffstat (limited to 'std/http')
-rw-r--r--std/http/README.md71
-rw-r--r--std/http/_io.ts380
-rw-r--r--std/http/_io_test.ts494
-rw-r--r--std/http/_mock_conn.ts29
-rw-r--r--std/http/bench.ts17
-rw-r--r--std/http/cookie.ts204
-rw-r--r--std/http/cookie_test.ts316
-rw-r--r--std/http/file_server.ts454
-rw-r--r--std/http/file_server_test.ts465
-rw-r--r--std/http/http_status.ts197
-rw-r--r--std/http/mod.ts4
-rw-r--r--std/http/racing_server.ts68
-rw-r--r--std/http/racing_server_test.ts81
-rw-r--r--std/http/server.ts399
-rw-r--r--std/http/server_test.ts784
-rw-r--r--std/http/test.ts2
-rw-r--r--std/http/testdata/%0
-rw-r--r--std/http/testdata/file_server_as_library.ts12
-rw-r--r--std/http/testdata/hello.html0
-rw-r--r--std/http/testdata/simple_https_server.ts18
-rw-r--r--std/http/testdata/simple_server.ts9
-rw-r--r--std/http/testdata/test file.txt0
l---------std/http/testdata/tls1
23 files changed, 0 insertions, 4005 deletions
diff --git a/std/http/README.md b/std/http/README.md
deleted file mode 100644
index c2c4c8ce6..000000000
--- a/std/http/README.md
+++ /dev/null
@@ -1,71 +0,0 @@
-# http
-
-```typescript
-import { serve } from "https://deno.land/std@$STD_VERSION/http/server.ts";
-const server = serve({ port: 8000 });
-console.log("http://localhost:8000/");
-for await (const req of server) {
- req.respond({ body: "Hello World\n" });
-}
-```
-
-### File Server
-
-A small program for serving local files over HTTP.
-
-```sh
-deno run --allow-net --allow-read https://deno.land/std/http/file_server.ts
-> HTTP server listening on http://0.0.0.0:4507/
-```
-
-## Cookie
-
-Helper to manipulate `Cookie` through `ServerRequest` and `Response`.
-
-```ts
-import { ServerRequest } from "https://deno.land/std@$STD_VERSION/http/server.ts";
-import { getCookies } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
-
-let request = new ServerRequest();
-request.headers = new Headers();
-request.headers.set("Cookie", "full=of; tasty=chocolate");
-
-const cookies = getCookies(request);
-console.log("cookies:", cookies);
-// cookies: { full: "of", tasty: "chocolate" }
-```
-
-To set a `Cookie` you can add `CookieOptions` to properly set your `Cookie`:
-
-```ts
-import { Response } from "https://deno.land/std@$STD_VERSION/http/server.ts";
-import {
- Cookie,
- setCookie,
-} from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
-
-let response: Response = {};
-const cookie: Cookie = { name: "Space", value: "Cat" };
-setCookie(response, cookie);
-
-const cookieHeader = response.headers.get("set-cookie");
-console.log("Set-Cookie:", cookieHeader);
-// Set-Cookie: Space=Cat
-```
-
-Deleting a `Cookie` will set its expiration date before now. Forcing the browser
-to delete it.
-
-```ts
-import { Response } from "https://deno.land/std@$STD_VERSION/http/server.ts";
-import { deleteCookie } from "https://deno.land/std@$STD_VERSION/http/cookie.ts";
-
-let response: Response = {};
-deleteCookie(response, "deno");
-
-const cookieHeader = response.headers.get("set-cookie");
-console.log("Set-Cookie:", cookieHeader);
-// Set-Cookie: deno=; Expires=Thus, 01 Jan 1970 00:00:00 GMT
-```
-
-**Note**: At the moment multiple `Set-Cookie` in a `Response` is not handled.
diff --git a/std/http/_io.ts b/std/http/_io.ts
deleted file mode 100644
index 529f59cb5..000000000
--- a/std/http/_io.ts
+++ /dev/null
@@ -1,380 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-import { BufReader, BufWriter } from "../io/bufio.ts";
-import { TextProtoReader } from "../textproto/mod.ts";
-import { assert } from "../_util/assert.ts";
-import { encoder } from "../encoding/utf8.ts";
-import { Response, ServerRequest } from "./server.ts";
-import { STATUS_TEXT } from "./http_status.ts";
-
-export function emptyReader(): Deno.Reader {
- return {
- read(_: Uint8Array): Promise<number | null> {
- return Promise.resolve(null);
- },
- };
-}
-
-export function bodyReader(contentLength: number, r: BufReader): Deno.Reader {
- let totalRead = 0;
- let finished = false;
- async function read(buf: Uint8Array): Promise<number | null> {
- if (finished) return null;
- let result: number | null;
- const remaining = contentLength - totalRead;
- if (remaining >= buf.byteLength) {
- result = await r.read(buf);
- } else {
- const readBuf = buf.subarray(0, remaining);
- result = await r.read(readBuf);
- }
- if (result !== null) {
- totalRead += result;
- }
- finished = totalRead === contentLength;
- return result;
- }
- return { read };
-}
-
-export 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 };
-}
-
-function isProhibidedForTrailer(key: string): boolean {
- const s = new Set(["transfer-encoding", "content-length", "trailer"]);
- return s.has(key.toLowerCase());
-}
-
-/** Read trailer headers from reader and append values to headers. "trailer"
- * field will be deleted. */
-export async function readTrailers(
- headers: Headers,
- r: BufReader,
-): Promise<void> {
- 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) => isProhibidedForTrailer(k));
- if (prohibited.length > 0) {
- throw new Deno.errors.InvalidData(
- `Prohibited trailer names: ${Deno.inspect(prohibited)}.`,
- );
- }
- return new Headers(trailerNames.map((key) => [key, ""]));
-}
-
-export async function writeChunkedBody(
- w: BufWriter,
- r: Deno.Reader,
-): Promise<void> {
- for await (const chunk of Deno.iter(r)) {
- if (chunk.byteLength <= 0) continue;
- const start = encoder.encode(`${chunk.byteLength.toString(16)}\r\n`);
- const end = encoder.encode("\r\n");
- await w.write(start);
- await w.write(chunk);
- await w.write(end);
- await w.flush();
- }
-
- const endChunk = encoder.encode("0\r\n\r\n");
- await w.write(endChunk);
-}
-
-/** Write trailer headers to writer. It should mostly should be called after
- * `writeResponse()`. */
-export async function writeTrailers(
- w: Deno.Writer,
- headers: Headers,
- trailers: Headers,
-): Promise<void> {
- const trailer = headers.get("trailer");
- if (trailer === null) {
- throw new TypeError("Missing trailer header.");
- }
- const transferEncoding = headers.get("transfer-encoding");
- if (transferEncoding === null || !transferEncoding.match(/^chunked/)) {
- throw new TypeError(
- `Trailers are only allowed for "transfer-encoding: chunked", got "transfer-encoding: ${transferEncoding}".`,
- );
- }
- const writer = BufWriter.create(w);
- const trailerNames = trailer.split(",").map((s) => s.trim().toLowerCase());
- const prohibitedTrailers = trailerNames.filter((k) =>
- isProhibidedForTrailer(k)
- );
- if (prohibitedTrailers.length > 0) {
- throw new TypeError(
- `Prohibited trailer names: ${Deno.inspect(prohibitedTrailers)}.`,
- );
- }
- const undeclared = [...trailers.keys()].filter(
- (k) => !trailerNames.includes(k),
- );
- if (undeclared.length > 0) {
- throw new TypeError(`Undeclared trailers: ${Deno.inspect(undeclared)}.`);
- }
- for (const [key, value] of trailers) {
- await writer.write(encoder.encode(`${key}: ${value}\r\n`));
- }
- await writer.write(encoder.encode("\r\n"));
- await writer.flush();
-}
-
-export async function writeResponse(
- w: Deno.Writer,
- r: Response,
-): Promise<void> {
- const protoMajor = 1;
- const protoMinor = 1;
- const statusCode = r.status || 200;
- const statusText = STATUS_TEXT.get(statusCode);
- const writer = BufWriter.create(w);
- if (!statusText) {
- throw new Deno.errors.InvalidData("Bad status code");
- }
- if (!r.body) {
- r.body = new Uint8Array();
- }
- if (typeof r.body === "string") {
- r.body = encoder.encode(r.body);
- }
-
- let out = `HTTP/${protoMajor}.${protoMinor} ${statusCode} ${statusText}\r\n`;
-
- const headers = r.headers ?? new Headers();
-
- if (r.body && !headers.get("content-length")) {
- if (r.body instanceof Uint8Array) {
- out += `content-length: ${r.body.byteLength}\r\n`;
- } else if (!headers.get("transfer-encoding")) {
- out += "transfer-encoding: chunked\r\n";
- }
- }
-
- for (const [key, value] of headers) {
- out += `${key}: ${value}\r\n`;
- }
-
- out += `\r\n`;
-
- const header = encoder.encode(out);
- const n = await writer.write(header);
- assert(n === header.byteLength);
-
- if (r.body instanceof Uint8Array) {
- const n = await writer.write(r.body);
- assert(n === r.body.byteLength);
- } else if (headers.has("content-length")) {
- const contentLength = headers.get("content-length");
- assert(contentLength != null);
- const bodyLength = parseInt(contentLength);
- const n = await Deno.copy(r.body, writer);
- assert(n === bodyLength);
- } else {
- await writeChunkedBody(writer, r.body);
- }
- if (r.trailers) {
- const t = await r.trailers();
- await writeTrailers(writer, headers, t);
- }
- await writer.flush();
-}
-
-/**
- * ParseHTTPVersion parses a HTTP version string.
- * "HTTP/1.0" returns (1, 0).
- * Ported from https://github.com/golang/go/blob/f5c43b9/src/net/http/request.go#L766-L792
- */
-export function parseHTTPVersion(vers: string): [number, number] {
- switch (vers) {
- case "HTTP/1.1":
- return [1, 1];
-
- case "HTTP/1.0":
- return [1, 0];
-
- default: {
- const Big = 1000000; // arbitrary upper bound
-
- if (!vers.startsWith("HTTP/")) {
- break;
- }
-
- const dot = vers.indexOf(".");
- if (dot < 0) {
- break;
- }
-
- const majorStr = vers.substring(vers.indexOf("/") + 1, dot);
- const major = Number(majorStr);
- if (!Number.isInteger(major) || major < 0 || major > Big) {
- break;
- }
-
- const minorStr = vers.substring(dot + 1);
- const minor = Number(minorStr);
- if (!Number.isInteger(minor) || minor < 0 || minor > Big) {
- break;
- }
-
- return [major, minor];
- }
- }
-
- throw new Error(`malformed HTTP version ${vers}`);
-}
-
-export async function readRequest(
- conn: Deno.Conn,
- bufr: BufReader,
-): Promise<ServerRequest | null> {
- const tp = new TextProtoReader(bufr);
- const firstLine = await tp.readLine(); // e.g. GET /index.html HTTP/1.0
- if (firstLine === null) return null;
- const headers = await tp.readMIMEHeader();
- if (headers === null) throw new Deno.errors.UnexpectedEof();
-
- const req = new ServerRequest();
- req.conn = conn;
- req.r = bufr;
- [req.method, req.url, req.proto] = firstLine.split(" ", 3);
- [req.protoMajor, req.protoMinor] = parseHTTPVersion(req.proto);
- req.headers = headers;
- fixLength(req);
- return req;
-}
-
-function fixLength(req: ServerRequest): void {
- const contentLength = req.headers.get("Content-Length");
- if (contentLength) {
- const arrClen = contentLength.split(",");
- if (arrClen.length > 1) {
- const distinct = [...new Set(arrClen.map((e): string => e.trim()))];
- if (distinct.length > 1) {
- throw Error("cannot contain multiple Content-Length headers");
- } else {
- req.headers.set("Content-Length", distinct[0]);
- }
- }
- const c = req.headers.get("Content-Length");
- if (req.method === "HEAD" && c && c !== "0") {
- throw Error("http: method cannot contain a Content-Length");
- }
- if (c && req.headers.has("transfer-encoding")) {
- // A sender MUST NOT send a Content-Length header field in any message
- // that contains a Transfer-Encoding header field.
- // rfc: https://tools.ietf.org/html/rfc7230#section-3.3.2
- throw new Error(
- "http: Transfer-Encoding and Content-Length cannot be send together",
- );
- }
- }
-}
diff --git a/std/http/_io_test.ts b/std/http/_io_test.ts
deleted file mode 100644
index ea3d282b8..000000000
--- a/std/http/_io_test.ts
+++ /dev/null
@@ -1,494 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-import {
- assert,
- assertEquals,
- assertNotEquals,
- assertThrowsAsync,
-} from "../testing/asserts.ts";
-import {
- bodyReader,
- chunkedBodyReader,
- parseHTTPVersion,
- readRequest,
- readTrailers,
- writeResponse,
- writeTrailers,
-} from "./_io.ts";
-import { decode, encode } from "../encoding/utf8.ts";
-import { BufReader, ReadLineResult } from "../io/bufio.ts";
-import { Response, ServerRequest } from "./server.ts";
-import { StringReader } from "../io/readers.ts";
-import { mockConn } from "./_mock_conn.ts";
-
-Deno.test("bodyReader", async () => {
- const text = "Hello, Deno";
- const r = bodyReader(
- text.length,
- new BufReader(new Deno.Buffer(encode(text))),
- );
- assertEquals(decode(await Deno.readAll(r)), text);
-});
-function chunkify(n: number, char: string): string {
- const v = Array.from({ length: n })
- .map(() => `${char}`)
- .join("");
- return `${n.toString(16)}\r\n${v}\r\n`;
-}
-Deno.test("chunkedBodyReader", async () => {
- const body = [
- chunkify(3, "a"),
- chunkify(5, "b"),
- chunkify(11, "c"),
- chunkify(22, "d"),
- chunkify(0, ""),
- ].join("");
- const h = new Headers();
- const r = chunkedBodyReader(h, new BufReader(new Deno.Buffer(encode(body))));
- let result: number | null;
- // Use small buffer as some chunks exceed buffer size
- const buf = new Uint8Array(5);
- const dest = new Deno.Buffer();
- while ((result = await r.read(buf)) !== null) {
- const len = Math.min(buf.byteLength, result);
- await dest.write(buf.subarray(0, len));
- }
- const exp = "aaabbbbbcccccccccccdddddddddddddddddddddd";
- assertEquals(new TextDecoder().decode(dest.bytes()), exp);
-});
-
-Deno.test("chunkedBodyReader with trailers", async () => {
- const body = [
- chunkify(3, "a"),
- chunkify(5, "b"),
- chunkify(11, "c"),
- chunkify(22, "d"),
- chunkify(0, ""),
- "deno: land\r\n",
- "node: js\r\n",
- "\r\n",
- ].join("");
- const h = new Headers({
- trailer: "deno,node",
- });
- const r = chunkedBodyReader(h, new BufReader(new Deno.Buffer(encode(body))));
- assertEquals(h.has("trailer"), true);
- assertEquals(h.has("deno"), false);
- assertEquals(h.has("node"), false);
- const act = decode(await Deno.readAll(r));
- const exp = "aaabbbbbcccccccccccdddddddddddddddddddddd";
- assertEquals(act, exp);
- assertEquals(h.has("trailer"), false);
- assertEquals(h.get("deno"), "land");
- assertEquals(h.get("node"), "js");
-});
-
-Deno.test("readTrailers", async () => {
- const h = new Headers({
- trailer: "Deno, Node",
- });
- const trailer = ["deno: land", "node: js", "", ""].join("\r\n");
- await readTrailers(h, new BufReader(new Deno.Buffer(encode(trailer))));
- assertEquals(h.has("trailer"), false);
- assertEquals(h.get("deno"), "land");
- assertEquals(h.get("node"), "js");
-});
-
-Deno.test(
- "readTrailer should throw if undeclared headers found in trailer",
- async () => {
- const patterns = [
- ["deno,node", "deno: land\r\nnode: js\r\ngo: lang\r\n\r\n"],
- ["deno", "node: js\r\n\r\n"],
- ["deno", "node:js\r\ngo: lang\r\n\r\n"],
- ];
- for (const [header, trailer] of patterns) {
- const h = new Headers({
- trailer: header,
- });
- await assertThrowsAsync(
- async () => {
- await readTrailers(
- h,
- new BufReader(new Deno.Buffer(encode(trailer))),
- );
- },
- Deno.errors.InvalidData,
- `Undeclared trailers: [ "`,
- );
- }
- },
-);
-
-Deno.test(
- "readTrailer should throw if trailer contains prohibited fields",
- async () => {
- for (const f of ["Content-Length", "Trailer", "Transfer-Encoding"]) {
- const h = new Headers({
- trailer: f,
- });
- await assertThrowsAsync(
- async () => {
- await readTrailers(h, new BufReader(new Deno.Buffer()));
- },
- Deno.errors.InvalidData,
- `Prohibited trailer names: [ "`,
- );
- }
- },
-);
-
-Deno.test("writeTrailer", async () => {
- const w = new Deno.Buffer();
- await writeTrailers(
- w,
- new Headers({ "transfer-encoding": "chunked", trailer: "deno,node" }),
- new Headers({ deno: "land", node: "js" }),
- );
- assertEquals(
- new TextDecoder().decode(w.bytes()),
- "deno: land\r\nnode: js\r\n\r\n",
- );
-});
-
-Deno.test("writeTrailer should throw", async () => {
- const w = new Deno.Buffer();
- await assertThrowsAsync(
- () => {
- return writeTrailers(w, new Headers(), new Headers());
- },
- TypeError,
- "Missing trailer header.",
- );
- await assertThrowsAsync(
- () => {
- return writeTrailers(w, new Headers({ trailer: "deno" }), new Headers());
- },
- TypeError,
- `Trailers are only allowed for "transfer-encoding: chunked", got "transfer-encoding: null".`,
- );
- for (const f of ["content-length", "trailer", "transfer-encoding"]) {
- await assertThrowsAsync(
- () => {
- return writeTrailers(
- w,
- new Headers({ "transfer-encoding": "chunked", trailer: f }),
- new Headers({ [f]: "1" }),
- );
- },
- TypeError,
- `Prohibited trailer names: [ "`,
- );
- }
- await assertThrowsAsync(
- () => {
- return writeTrailers(
- w,
- new Headers({ "transfer-encoding": "chunked", trailer: "deno" }),
- new Headers({ node: "js" }),
- );
- },
- TypeError,
- `Undeclared trailers: [ "node" ].`,
- );
-});
-
-// Ported from https://github.com/golang/go/blob/f5c43b9/src/net/http/request_test.go#L535-L565
-Deno.test("parseHttpVersion", (): void => {
- const testCases = [
- { in: "HTTP/0.9", want: [0, 9] },
- { in: "HTTP/1.0", want: [1, 0] },
- { in: "HTTP/1.1", want: [1, 1] },
- { in: "HTTP/3.14", want: [3, 14] },
- { in: "HTTP", err: true },
- { in: "HTTP/one.one", err: true },
- { in: "HTTP/1.1/", err: true },
- { in: "HTTP/-1.0", err: true },
- { in: "HTTP/0.-1", err: true },
- { in: "HTTP/", err: true },
- { in: "HTTP/1,0", err: true },
- { in: "HTTP/1.1000001", err: true },
- ];
- for (const t of testCases) {
- let r, err;
- try {
- r = parseHTTPVersion(t.in);
- } catch (e) {
- err = e;
- }
- if (t.err) {
- assert(err instanceof Error, t.in);
- } else {
- assertEquals(err, undefined);
- assertEquals(r, t.want, t.in);
- }
- }
-});
-
-Deno.test("writeUint8ArrayResponse", async function (): Promise<void> {
- const shortText = "Hello";
-
- const body = new TextEncoder().encode(shortText);
- const res: Response = { body };
-
- const buf = new Deno.Buffer();
- await writeResponse(buf, res);
-
- const decoder = new TextDecoder("utf-8");
- const reader = new BufReader(buf);
-
- let r: ReadLineResult | null = await reader.readLine();
- assert(r !== null);
- assertEquals(decoder.decode(r.line), "HTTP/1.1 200 OK");
- assertEquals(r.more, false);
-
- r = await reader.readLine();
- assert(r !== null);
- assertEquals(decoder.decode(r.line), `content-length: ${shortText.length}`);
- assertEquals(r.more, false);
-
- r = await reader.readLine();
- assert(r !== null);
- assertEquals(r.line.byteLength, 0);
- assertEquals(r.more, false);
-
- r = await reader.readLine();
- assert(r !== null);
- assertEquals(decoder.decode(r.line), shortText);
- assertEquals(r.more, false);
-
- const eof = await reader.readLine();
- assertEquals(eof, null);
-});
-
-Deno.test("writeStringResponse", async function (): Promise<void> {
- const body = "Hello";
-
- const res: Response = { body };
-
- const buf = new Deno.Buffer();
- await writeResponse(buf, res);
-
- const decoder = new TextDecoder("utf-8");
- const reader = new BufReader(buf);
-
- let r: ReadLineResult | null = await reader.readLine();
- assert(r !== null);
- assertEquals(decoder.decode(r.line), "HTTP/1.1 200 OK");
- assertEquals(r.more, false);
-
- r = await reader.readLine();
- assert(r !== null);
- assertEquals(decoder.decode(r.line), `content-length: ${body.length}`);
- assertEquals(r.more, false);
-
- r = await reader.readLine();
- assert(r !== null);
- assertEquals(r.line.byteLength, 0);
- assertEquals(r.more, false);
-
- r = await reader.readLine();
- assert(r !== null);
- assertEquals(decoder.decode(r.line), body);
- assertEquals(r.more, false);
-
- const eof = await reader.readLine();
- assertEquals(eof, null);
-});
-
-Deno.test("writeStringReaderResponse", async function (): Promise<void> {
- const shortText = "Hello";
-
- const body = new StringReader(shortText);
- const res: Response = { body };
-
- const buf = new Deno.Buffer();
- await writeResponse(buf, res);
-
- const decoder = new TextDecoder("utf-8");
- const reader = new BufReader(buf);
-
- let r: ReadLineResult | null = await reader.readLine();
- assert(r !== null);
- assertEquals(decoder.decode(r.line), "HTTP/1.1 200 OK");
- assertEquals(r.more, false);
-
- r = await reader.readLine();
- assert(r !== null);
- assertEquals(decoder.decode(r.line), "transfer-encoding: chunked");
- assertEquals(r.more, false);
-
- r = await reader.readLine();
- assert(r !== null);
- assertEquals(r.line.byteLength, 0);
- assertEquals(r.more, false);
-
- r = await reader.readLine();
- assert(r !== null);
- assertEquals(decoder.decode(r.line), shortText.length.toString());
- assertEquals(r.more, false);
-
- r = await reader.readLine();
- assert(r !== null);
- assertEquals(decoder.decode(r.line), shortText);
- assertEquals(r.more, false);
-
- r = await reader.readLine();
- assert(r !== null);
- assertEquals(decoder.decode(r.line), "0");
- assertEquals(r.more, false);
-});
-
-Deno.test("writeResponse with trailer", async () => {
- const w = new Deno.Buffer();
- const body = new StringReader("Hello");
- await writeResponse(w, {
- status: 200,
- headers: new Headers({
- "transfer-encoding": "chunked",
- trailer: "deno,node",
- }),
- body,
- trailers: () => new Headers({ deno: "land", node: "js" }),
- });
- const ret = new TextDecoder().decode(w.bytes());
- const exp = [
- "HTTP/1.1 200 OK",
- "transfer-encoding: chunked",
- "trailer: deno,node",
- "",
- "5",
- "Hello",
- "0",
- "",
- "deno: land",
- "node: js",
- "",
- "",
- ].join("\r\n");
- assertEquals(ret, exp);
-});
-
-Deno.test("writeResponseShouldNotModifyOriginHeaders", async () => {
- const headers = new Headers();
- const buf = new Deno.Buffer();
-
- await writeResponse(buf, { body: "foo", headers });
- assert(decode(await Deno.readAll(buf)).includes("content-length: 3"));
-
- await writeResponse(buf, { body: "hello", headers });
- assert(decode(await Deno.readAll(buf)).includes("content-length: 5"));
-});
-
-Deno.test("readRequestError", async function (): Promise<void> {
- const input = `GET / HTTP/1.1
-malformedHeader
-`;
- const reader = new BufReader(new StringReader(input));
- let err;
- try {
- await readRequest(mockConn(), reader);
- } catch (e) {
- err = e;
- }
- assert(err instanceof Error);
- assertEquals(err.message, "malformed MIME header line: malformedHeader");
-});
-
-// Ported from Go
-// https://github.com/golang/go/blob/go1.12.5/src/net/http/request_test.go#L377-L443
-// TODO(zekth) fix tests
-Deno.test("testReadRequestError", async function (): Promise<void> {
- const testCases = [
- {
- in: "GET / HTTP/1.1\r\nheader: foo\r\n\r\n",
- headers: [{ key: "header", value: "foo" }],
- },
- {
- in: "GET / HTTP/1.1\r\nheader:foo\r\n",
- err: Deno.errors.UnexpectedEof,
- },
- {
- in: "POST / HTTP/1.0\r\n\r\n",
- headers: [],
- version: true,
- },
- { in: "", eof: true },
- {
- in: "HEAD / HTTP/1.1\r\nContent-Length:4\r\n\r\n",
- err: "http: method cannot contain a Content-Length",
- },
- {
- in: "HEAD / HTTP/1.1\r\n\r\n",
- headers: [],
- },
- // Multiple Content-Length values should either be
- // deduplicated if same or reject otherwise
- // See Issue 16490.
- {
- in: "POST / HTTP/1.1\r\nContent-Length: 10\r\nContent-Length: 0\r\n\r\n" +
- "Gopher hey\r\n",
- err: "cannot contain multiple Content-Length headers",
- },
- {
- in: "POST / HTTP/1.1\r\nContent-Length: 10\r\nContent-Length: 6\r\n\r\n" +
- "Gopher\r\n",
- err: "cannot contain multiple Content-Length headers",
- },
- {
- in: "PUT / HTTP/1.1\r\nContent-Length: 6 \r\nContent-Length: 6\r\n" +
- "Content-Length:6\r\n\r\nGopher\r\n",
- headers: [{ key: "Content-Length", value: "6" }],
- },
- {
- in: "PUT / HTTP/1.1\r\nContent-Length: 1\r\nContent-Length: 6 \r\n\r\n",
- err: "cannot contain multiple Content-Length headers",
- },
- // Setting an empty header is swallowed by textproto
- // see: readMIMEHeader()
- // {
- // in: "POST / HTTP/1.1\r\nContent-Length:\r\nContent-Length: 3\r\n\r\n",
- // err: "cannot contain multiple Content-Length headers"
- // },
- {
- in: "HEAD / HTTP/1.1\r\nContent-Length:0\r\nContent-Length: 0\r\n\r\n",
- headers: [{ key: "Content-Length", value: "0" }],
- },
- {
- in: "POST / HTTP/1.1\r\nContent-Length:0\r\ntransfer-encoding: " +
- "chunked\r\n\r\n",
- headers: [],
- err: "http: Transfer-Encoding and Content-Length cannot be send together",
- },
- ];
- for (const test of testCases) {
- const reader = new BufReader(new StringReader(test.in));
- let err;
- let req: ServerRequest | null = null;
- try {
- req = await readRequest(mockConn(), reader);
- } catch (e) {
- err = e;
- }
- if (test.eof) {
- assertEquals(req, null);
- } else if (typeof test.err === "string") {
- assertEquals(err.message, test.err);
- } else if (test.err) {
- assert(err instanceof (test.err as typeof Deno.errors.UnexpectedEof));
- } else {
- assert(req instanceof ServerRequest);
- if (test.version) {
- // return value order of parseHTTPVersion() function have to match with [req.protoMajor, req.protoMinor];
- const version = parseHTTPVersion(test.in.split(" ", 3)[2]);
- assertEquals(req.protoMajor, version[0]);
- assertEquals(req.protoMinor, version[1]);
- }
- assert(test.headers);
- assertEquals(err, undefined);
- assertNotEquals(req, null);
- for (const h of test.headers) {
- assertEquals(req.headers.get(h.key), h.value);
- }
- }
- }
-});
diff --git a/std/http/_mock_conn.ts b/std/http/_mock_conn.ts
deleted file mode 100644
index 8f3396dbd..000000000
--- a/std/http/_mock_conn.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-
-/** Create dummy Deno.Conn object with given base properties */
-export function mockConn(base: Partial<Deno.Conn> = {}): Deno.Conn {
- return {
- localAddr: {
- transport: "tcp",
- hostname: "",
- port: 0,
- },
- remoteAddr: {
- transport: "tcp",
- hostname: "",
- port: 0,
- },
- rid: -1,
- closeWrite: (): Promise<void> => {
- return Promise.resolve();
- },
- read: (): Promise<number | null> => {
- return Promise.resolve(0);
- },
- write: (): Promise<number> => {
- return Promise.resolve(-1);
- },
- close: (): void => {},
- ...base,
- };
-}
diff --git a/std/http/bench.ts b/std/http/bench.ts
deleted file mode 100644
index 5ba95ef0a..000000000
--- a/std/http/bench.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-import { serve } from "./server.ts";
-
-const addr = Deno.args[0] || "127.0.0.1:4500";
-const server = serve(addr);
-const body = new TextEncoder().encode("Hello World");
-
-console.log(`http://${addr}/`);
-for await (const req of server) {
- const res = {
- body,
- headers: new Headers(),
- };
- res.headers.set("Date", new Date().toUTCString());
- res.headers.set("Connection", "keep-alive");
- req.respond(res).catch(() => {});
-}
diff --git a/std/http/cookie.ts b/std/http/cookie.ts
deleted file mode 100644
index 486afd6c6..000000000
--- a/std/http/cookie.ts
+++ /dev/null
@@ -1,204 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-// Structured similarly to Go's cookie.go
-// https://github.com/golang/go/blob/master/src/net/http/cookie.go
-import { assert } from "../_util/assert.ts";
-import { toIMF } from "../datetime/mod.ts";
-
-export type Cookies = Record<string, string>;
-
-export interface Cookie {
- /** Name of the cookie. */
- name: string;
- /** Value of the cookie. */
- value: string;
- /** Expiration date of the cookie. */
- expires?: Date;
- /** Max-Age of the Cookie. Must be integer superior to 0. */
- maxAge?: number;
- /** Specifies those hosts to which the cookie will be sent. */
- domain?: string;
- /** Indicates a URL path that must exist in the request. */
- path?: string;
- /** Indicates if the cookie is made using SSL & HTTPS. */
- secure?: boolean;
- /** Indicates that cookie is not accessible via JavaScript. **/
- httpOnly?: boolean;
- /** Allows servers to assert that a cookie ought not to
- * be sent along with cross-site requests. */
- sameSite?: SameSite;
- /** Additional key value pairs with the form "key=value" */
- unparsed?: string[];
-}
-
-export type SameSite = "Strict" | "Lax" | "None";
-
-const FIELD_CONTENT_REGEXP = /^(?=[\x20-\x7E]*$)[^()@<>,;:\\"\[\]?={}\s]+$/;
-
-function toString(cookie: Cookie): string {
- if (!cookie.name) {
- return "";
- }
- const out: string[] = [];
- validateCookieName(cookie.name);
- validateCookieValue(cookie.name, cookie.value);
- out.push(`${cookie.name}=${cookie.value}`);
-
- // Fallback for invalid Set-Cookie
- // ref: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00#section-3.1
- if (cookie.name.startsWith("__Secure")) {
- cookie.secure = true;
- }
- if (cookie.name.startsWith("__Host")) {
- cookie.path = "/";
- cookie.secure = true;
- delete cookie.domain;
- }
-
- if (cookie.secure) {
- out.push("Secure");
- }
- if (cookie.httpOnly) {
- out.push("HttpOnly");
- }
- if (typeof cookie.maxAge === "number" && Number.isInteger(cookie.maxAge)) {
- assert(cookie.maxAge > 0, "Max-Age must be an integer superior to 0");
- out.push(`Max-Age=${cookie.maxAge}`);
- }
- if (cookie.domain) {
- out.push(`Domain=${cookie.domain}`);
- }
- if (cookie.sameSite) {
- out.push(`SameSite=${cookie.sameSite}`);
- }
- if (cookie.path) {
- validatePath(cookie.path);
- out.push(`Path=${cookie.path}`);
- }
- if (cookie.expires) {
- const dateString = toIMF(cookie.expires);
- out.push(`Expires=${dateString}`);
- }
- if (cookie.unparsed) {
- out.push(cookie.unparsed.join("; "));
- }
- return out.join("; ");
-}
-
-/**
- * Validate Cookie Name.
- * @param name Cookie name.
- */
-function validateCookieName(name: string | undefined | null): void {
- if (name && !FIELD_CONTENT_REGEXP.test(name)) {
- throw new TypeError(`Invalid cookie name: "${name}".`);
- }
-}
-
-/**
- * Validate Path Value.
- * @see https://tools.ietf.org/html/rfc6265#section-4.1.2.4
- * @param path Path value.
- */
-function validatePath(path: string | null): void {
- if (path == null) {
- return;
- }
- for (let i = 0; i < path.length; i++) {
- const c = path.charAt(i);
- if (
- c < String.fromCharCode(0x20) || c > String.fromCharCode(0x7E) || c == ";"
- ) {
- throw new Error(
- path + ": Invalid cookie path char '" + c + "'",
- );
- }
- }
-}
-
-/**
- *Validate Cookie Value.
- * @see https://tools.ietf.org/html/rfc6265#section-4.1
- * @param value Cookie value.
- */
-function validateCookieValue(name: string, value: string | null): void {
- if (value == null || name == null) return;
- for (let i = 0; i < value.length; i++) {
- const c = value.charAt(i);
- if (
- c < String.fromCharCode(0x21) || c == String.fromCharCode(0x22) ||
- c == String.fromCharCode(0x2c) || c == String.fromCharCode(0x3b) ||
- c == String.fromCharCode(0x5c) || c == String.fromCharCode(0x7f)
- ) {
- throw new Error(
- "RFC2616 cookie '" + name + "' cannot have '" + c + "' as value",
- );
- }
- if (c > String.fromCharCode(0x80)) {
- throw new Error(
- "RFC2616 cookie '" + name + "' can only have US-ASCII chars as value" +
- c.charCodeAt(0).toString(16),
- );
- }
- }
-}
-
-/**
- * Parse the cookies of the Server Request
- * @param req An object which has a `headers` property
- */
-export function getCookies(req: { headers: Headers }): Cookies {
- const cookie = req.headers.get("Cookie");
- if (cookie != null) {
- const out: Cookies = {};
- const c = cookie.split(";");
- for (const kv of c) {
- const [cookieKey, ...cookieVal] = kv.split("=");
- assert(cookieKey != null);
- const key = cookieKey.trim();
- out[key] = cookieVal.join("=");
- }
- return out;
- }
- return {};
-}
-
-/**
- * Set the cookie header properly in the Response
- * @param res An object which has a headers property
- * @param cookie Cookie to set
- *
- * Example:
- *
- * ```ts
- * setCookie(response, { name: 'deno', value: 'runtime',
- * httpOnly: true, secure: true, maxAge: 2, domain: "deno.land" });
- * ```
- */
-export function setCookie(res: { headers?: Headers }, cookie: Cookie): void {
- if (!res.headers) {
- res.headers = new Headers();
- }
- // TODO(zekth) : Add proper parsing of Set-Cookie headers
- // Parsing cookie headers to make consistent set-cookie header
- // ref: https://tools.ietf.org/html/rfc6265#section-4.1.1
- const v = toString(cookie);
- if (v) {
- res.headers.append("Set-Cookie", v);
- }
-}
-
-/**
- * Set the cookie header properly in the Response to delete it
- * @param res Server Response
- * @param name Name of the cookie to Delete
- * Example:
- *
- * deleteCookie(res,'foo');
- */
-export function deleteCookie(res: { headers?: Headers }, name: string): void {
- setCookie(res, {
- name: name,
- value: "",
- expires: new Date(0),
- });
-}
diff --git a/std/http/cookie_test.ts b/std/http/cookie_test.ts
deleted file mode 100644
index 1973eed01..000000000
--- a/std/http/cookie_test.ts
+++ /dev/null
@@ -1,316 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-import { Response, ServerRequest } from "./server.ts";
-import { deleteCookie, getCookies, setCookie } from "./cookie.ts";
-import { assert, assertEquals, assertThrows } from "../testing/asserts.ts";
-
-Deno.test({
- name: "Cookie parser",
- fn(): void {
- const req = new ServerRequest();
- req.headers = new Headers();
- assertEquals(getCookies(req), {});
- req.headers = new Headers();
- req.headers.set("Cookie", "foo=bar");
- assertEquals(getCookies(req), { foo: "bar" });
-
- req.headers = new Headers();
- req.headers.set("Cookie", "full=of ; tasty=chocolate");
- assertEquals(getCookies(req), { full: "of ", tasty: "chocolate" });
-
- req.headers = new Headers();
- req.headers.set("Cookie", "igot=99; problems=but...");
- assertEquals(getCookies(req), { igot: "99", problems: "but..." });
-
- req.headers = new Headers();
- req.headers.set("Cookie", "PREF=al=en-GB&f1=123; wide=1; SID=123");
- assertEquals(getCookies(req), {
- PREF: "al=en-GB&f1=123",
- wide: "1",
- SID: "123",
- });
- },
-});
-
-Deno.test({
- name: "Cookie Name Validation",
- fn(): void {
- const res: Response = {};
- const tokens = [
- '"id"',
- "id\t",
- "i\td",
- "i d",
- "i;d",
- "{id}",
- "[id]",
- '"',
- "id\u0091",
- ];
- res.headers = new Headers();
- tokens.forEach((name) => {
- assertThrows(
- (): void => {
- setCookie(res, {
- name,
- value: "Cat",
- httpOnly: true,
- secure: true,
- maxAge: 3,
- });
- },
- Error,
- 'Invalid cookie name: "' + name + '".',
- );
- });
- },
-});
-
-Deno.test({
- name: "Cookie Value Validation",
- fn(): void {
- const res: Response = {};
- const tokens = [
- "1f\tWa",
- "\t",
- "1f Wa",
- "1f;Wa",
- '"1fWa',
- "1f\\Wa",
- '1f"Wa',
- '"',
- "1fWa\u0005",
- "1f\u0091Wa",
- ];
- res.headers = new Headers();
- tokens.forEach((value) => {
- assertThrows(
- (): void => {
- setCookie(
- res,
- {
- name: "Space",
- value,
- httpOnly: true,
- secure: true,
- maxAge: 3,
- },
- );
- },
- Error,
- "RFC2616 cookie 'Space'",
- );
- });
- },
-});
-
-Deno.test({
- name: "Cookie Path Validation",
- fn(): void {
- const res: Response = {};
- const path = "/;domain=sub.domain.com";
- res.headers = new Headers();
- assertThrows(
- (): void => {
- setCookie(res, {
- name: "Space",
- value: "Cat",
- httpOnly: true,
- secure: true,
- path,
- maxAge: 3,
- });
- },
- Error,
- path + ": Invalid cookie path char ';'",
- );
- },
-});
-
-Deno.test({
- name: "Cookie Delete",
- fn(): void {
- const res: Response = {};
- deleteCookie(res, "deno");
- assertEquals(
- res.headers?.get("Set-Cookie"),
- "deno=; Expires=Thu, 01 Jan 1970 00:00:00 GMT",
- );
- },
-});
-
-Deno.test({
- name: "Cookie Set",
- fn(): void {
- const res: Response = {};
-
- res.headers = new Headers();
- setCookie(res, { name: "Space", value: "Cat" });
- assertEquals(res.headers.get("Set-Cookie"), "Space=Cat");
-
- res.headers = new Headers();
- setCookie(res, { name: "Space", value: "Cat", secure: true });
- assertEquals(res.headers.get("Set-Cookie"), "Space=Cat; Secure");
-
- res.headers = new Headers();
- setCookie(res, { name: "Space", value: "Cat", httpOnly: true });
- assertEquals(res.headers.get("Set-Cookie"), "Space=Cat; HttpOnly");
-
- res.headers = new Headers();
- setCookie(res, {
- name: "Space",
- value: "Cat",
- httpOnly: true,
- secure: true,
- });
- assertEquals(res.headers.get("Set-Cookie"), "Space=Cat; Secure; HttpOnly");
-
- res.headers = new Headers();
- setCookie(res, {
- name: "Space",
- value: "Cat",
- httpOnly: true,
- secure: true,
- maxAge: 2,
- });
- assertEquals(
- res.headers.get("Set-Cookie"),
- "Space=Cat; Secure; HttpOnly; Max-Age=2",
- );
-
- let error = false;
- res.headers = new Headers();
- try {
- setCookie(res, {
- name: "Space",
- value: "Cat",
- httpOnly: true,
- secure: true,
- maxAge: 0,
- });
- } catch (e) {
- error = true;
- }
- assert(error);
-
- res.headers = new Headers();
- setCookie(res, {
- name: "Space",
- value: "Cat",
- httpOnly: true,
- secure: true,
- maxAge: 2,
- domain: "deno.land",
- });
- assertEquals(
- res.headers.get("Set-Cookie"),
- "Space=Cat; Secure; HttpOnly; Max-Age=2; Domain=deno.land",
- );
-
- res.headers = new Headers();
- setCookie(res, {
- name: "Space",
- value: "Cat",
- httpOnly: true,
- secure: true,
- maxAge: 2,
- domain: "deno.land",
- sameSite: "Strict",
- });
- assertEquals(
- res.headers.get("Set-Cookie"),
- "Space=Cat; Secure; HttpOnly; Max-Age=2; Domain=deno.land; " +
- "SameSite=Strict",
- );
-
- res.headers = new Headers();
- setCookie(res, {
- name: "Space",
- value: "Cat",
- httpOnly: true,
- secure: true,
- maxAge: 2,
- domain: "deno.land",
- sameSite: "Lax",
- });
- assertEquals(
- res.headers.get("Set-Cookie"),
- "Space=Cat; Secure; HttpOnly; Max-Age=2; Domain=deno.land; SameSite=Lax",
- );
-
- res.headers = new Headers();
- setCookie(res, {
- name: "Space",
- value: "Cat",
- httpOnly: true,
- secure: true,
- maxAge: 2,
- domain: "deno.land",
- path: "/",
- });
- assertEquals(
- res.headers.get("Set-Cookie"),
- "Space=Cat; Secure; HttpOnly; Max-Age=2; Domain=deno.land; Path=/",
- );
-
- res.headers = new Headers();
- setCookie(res, {
- name: "Space",
- value: "Cat",
- httpOnly: true,
- secure: true,
- maxAge: 2,
- domain: "deno.land",
- path: "/",
- unparsed: ["unparsed=keyvalue", "batman=Bruce"],
- });
- assertEquals(
- res.headers.get("Set-Cookie"),
- "Space=Cat; Secure; HttpOnly; Max-Age=2; Domain=deno.land; Path=/; " +
- "unparsed=keyvalue; batman=Bruce",
- );
-
- res.headers = new Headers();
- setCookie(res, {
- name: "Space",
- value: "Cat",
- httpOnly: true,
- secure: true,
- maxAge: 2,
- domain: "deno.land",
- path: "/",
- expires: new Date(Date.UTC(1983, 0, 7, 15, 32)),
- });
- assertEquals(
- res.headers.get("Set-Cookie"),
- "Space=Cat; Secure; HttpOnly; Max-Age=2; Domain=deno.land; Path=/; " +
- "Expires=Fri, 07 Jan 1983 15:32:00 GMT",
- );
-
- res.headers = new Headers();
- setCookie(res, { name: "__Secure-Kitty", value: "Meow" });
- assertEquals(res.headers.get("Set-Cookie"), "__Secure-Kitty=Meow; Secure");
-
- res.headers = new Headers();
- setCookie(res, {
- name: "__Host-Kitty",
- value: "Meow",
- domain: "deno.land",
- });
- assertEquals(
- res.headers.get("Set-Cookie"),
- "__Host-Kitty=Meow; Secure; Path=/",
- );
-
- res.headers = new Headers();
- setCookie(res, { name: "cookie-1", value: "value-1", secure: true });
- setCookie(res, { name: "cookie-2", value: "value-2", maxAge: 3600 });
- assertEquals(
- res.headers.get("Set-Cookie"),
- "cookie-1=value-1; Secure, cookie-2=value-2; Max-Age=3600",
- );
-
- res.headers = new Headers();
- setCookie(res, { name: "", value: "" });
- assertEquals(res.headers.get("Set-Cookie"), null);
- },
-});
diff --git a/std/http/file_server.ts b/std/http/file_server.ts
deleted file mode 100644
index 8fd2e7484..000000000
--- a/std/http/file_server.ts
+++ /dev/null
@@ -1,454 +0,0 @@
-#!/usr/bin/env -S deno run --allow-net --allow-read
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-
-// This program serves files in the current directory over HTTP.
-// TODO(bartlomieju): Stream responses instead of reading them into memory.
-// TODO(bartlomieju): Add tests like these:
-// https://github.com/indexzero/http-server/blob/master/test/http-server-test.js
-
-import { extname, posix } from "../path/mod.ts";
-import {
- HTTPSOptions,
- listenAndServe,
- listenAndServeTLS,
- Response,
- ServerRequest,
-} from "./server.ts";
-import { parse } from "../flags/mod.ts";
-import { assert } from "../_util/assert.ts";
-
-interface EntryInfo {
- mode: string;
- size: string;
- url: string;
- name: string;
-}
-
-export interface FileServerArgs {
- _: string[];
- // -p --port
- p?: number;
- port?: number;
- // --cors
- cors?: boolean;
- // --no-dir-listing
- "dir-listing"?: boolean;
- // --host
- host?: string;
- // -c --cert
- c?: string;
- cert?: string;
- // -k --key
- k?: string;
- key?: string;
- // -h --help
- h?: boolean;
- help?: boolean;
-}
-
-const encoder = new TextEncoder();
-
-const serverArgs = parse(Deno.args) as FileServerArgs;
-const target = posix.resolve(serverArgs._[0] ?? "");
-
-const MEDIA_TYPES: Record<string, string> = {
- ".md": "text/markdown",
- ".html": "text/html",
- ".htm": "text/html",
- ".json": "application/json",
- ".map": "application/json",
- ".txt": "text/plain",
- ".ts": "text/typescript",
- ".tsx": "text/tsx",
- ".js": "application/javascript",
- ".jsx": "text/jsx",
- ".gz": "application/gzip",
- ".css": "text/css",
- ".wasm": "application/wasm",
- ".mjs": "application/javascript",
-};
-
-/** Returns the content-type based on the extension of a path. */
-function contentType(path: string): string | undefined {
- return MEDIA_TYPES[extname(path)];
-}
-
-function modeToString(isDir: boolean, maybeMode: number | null): string {
- const modeMap = ["---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx"];
-
- if (maybeMode === null) {
- return "(unknown mode)";
- }
- const mode = maybeMode.toString(8);
- if (mode.length < 3) {
- return "(unknown mode)";
- }
- let output = "";
- mode
- .split("")
- .reverse()
- .slice(0, 3)
- .forEach((v): void => {
- output = modeMap[+v] + output;
- });
- output = `(${isDir ? "d" : "-"}${output})`;
- return output;
-}
-
-function fileLenToString(len: number): string {
- const multiplier = 1024;
- let base = 1;
- const suffix = ["B", "K", "M", "G", "T"];
- let suffixIndex = 0;
-
- while (base * multiplier < len) {
- if (suffixIndex >= suffix.length - 1) {
- break;
- }
- base *= multiplier;
- suffixIndex++;
- }
-
- return `${(len / base).toFixed(2)}${suffix[suffixIndex]}`;
-}
-
-/**
- * Returns an HTTP Response with the requested file as the body
- * @param req The server request context used to cleanup the file handle
- * @param filePath Path of the file to serve
- */
-export async function serveFile(
- req: ServerRequest,
- filePath: string,
-): Promise<Response> {
- const [file, fileInfo] = await Promise.all([
- Deno.open(filePath),
- Deno.stat(filePath),
- ]);
- const headers = new Headers();
- headers.set("content-length", fileInfo.size.toString());
- const contentTypeValue = contentType(filePath);
- if (contentTypeValue) {
- headers.set("content-type", contentTypeValue);
- }
- req.done.then(() => {
- file.close();
- });
- return {
- status: 200,
- body: file,
- headers,
- };
-}
-
-// TODO(bartlomieju): simplify this after deno.stat and deno.readDir are fixed
-async function serveDir(
- req: ServerRequest,
- dirPath: string,
-): Promise<Response> {
- const dirUrl = `/${posix.relative(target, dirPath)}`;
- const listEntry: EntryInfo[] = [];
- for await (const entry of Deno.readDir(dirPath)) {
- const filePath = posix.join(dirPath, entry.name);
- const fileUrl = posix.join(dirUrl, entry.name);
- if (entry.name === "index.html" && entry.isFile) {
- // in case index.html as dir...
- return serveFile(req, filePath);
- }
- // Yuck!
- let fileInfo = null;
- try {
- fileInfo = await Deno.stat(filePath);
- } catch (e) {
- // Pass
- }
- listEntry.push({
- mode: modeToString(entry.isDirectory, fileInfo?.mode ?? null),
- size: entry.isFile ? fileLenToString(fileInfo?.size ?? 0) : "",
- name: entry.name,
- url: fileUrl,
- });
- }
- listEntry.sort((a, b) =>
- a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1
- );
- const formattedDirUrl = `${dirUrl.replace(/\/$/, "")}/`;
- const page = encoder.encode(dirViewerTemplate(formattedDirUrl, listEntry));
-
- const headers = new Headers();
- headers.set("content-type", "text/html");
-
- const res = {
- status: 200,
- body: page,
- headers,
- };
- return res;
-}
-
-function serveFallback(req: ServerRequest, e: Error): Promise<Response> {
- if (e instanceof URIError) {
- return Promise.resolve({
- status: 400,
- body: encoder.encode("Bad Request"),
- });
- } else if (e instanceof Deno.errors.NotFound) {
- return Promise.resolve({
- status: 404,
- body: encoder.encode("Not Found"),
- });
- } else {
- return Promise.resolve({
- status: 500,
- body: encoder.encode("Internal server error"),
- });
- }
-}
-
-function serverLog(req: ServerRequest, res: Response): void {
- const d = new Date().toISOString();
- const dateFmt = `[${d.slice(0, 10)} ${d.slice(11, 19)}]`;
- const s = `${dateFmt} "${req.method} ${req.url} ${req.proto}" ${res.status}`;
- console.log(s);
-}
-
-function setCORS(res: Response): void {
- if (!res.headers) {
- res.headers = new Headers();
- }
- res.headers.append("access-control-allow-origin", "*");
- res.headers.append(
- "access-control-allow-headers",
- "Origin, X-Requested-With, Content-Type, Accept, Range",
- );
-}
-
-function dirViewerTemplate(dirname: string, entries: EntryInfo[]): string {
- return html`
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <meta http-equiv="X-UA-Compatible" content="ie=edge" />
- <title>Deno File Server</title>
- <style>
- :root {
- --background-color: #fafafa;
- --color: rgba(0, 0, 0, 0.87);
- }
- @media (prefers-color-scheme: dark) {
- :root {
- --background-color: #303030;
- --color: #fff;
- }
- }
- @media (min-width: 960px) {
- main {
- max-width: 960px;
- }
- body {
- padding-left: 32px;
- padding-right: 32px;
- }
- }
- @media (min-width: 600px) {
- main {
- padding-left: 24px;
- padding-right: 24px;
- }
- }
- body {
- background: var(--background-color);
- color: var(--color);
- font-family: "Roboto", "Helvetica", "Arial", sans-serif;
- font-weight: 400;
- line-height: 1.43;
- font-size: 0.875rem;
- }
- a {
- color: #2196f3;
- text-decoration: none;
- }
- a:hover {
- text-decoration: underline;
- }
- table th {
- text-align: left;
- }
- table td {
- padding: 12px 24px 0 0;
- }
- </style>
- </head>
- <body>
- <main>
- <h1>Index of ${dirname}</h1>
- <table>
- <tr>
- <th>Mode</th>
- <th>Size</th>
- <th>Name</th>
- </tr>
- ${
- entries.map(
- (entry) =>
- html`
- <tr>
- <td class="mode">
- ${entry.mode}
- </td>
- <td>
- ${entry.size}
- </td>
- <td>
- <a href="${entry.url}">${entry.name}</a>
- </td>
- </tr>
- `,
- )
- }
- </table>
- </main>
- </body>
- </html>
- `;
-}
-
-function html(strings: TemplateStringsArray, ...values: unknown[]): string {
- const l = strings.length - 1;
- let html = "";
-
- for (let i = 0; i < l; i++) {
- let v = values[i];
- if (v instanceof Array) {
- v = v.join("");
- }
- const s = strings[i] + v;
- html += s;
- }
- html += strings[l];
- return html;
-}
-
-function normalizeURL(url: string): string {
- let normalizedUrl = url;
- try {
- normalizedUrl = decodeURI(normalizedUrl);
- } catch (e) {
- if (!(e instanceof URIError)) {
- throw e;
- }
- }
-
- try {
- //allowed per https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
- const absoluteURI = new URL(normalizedUrl);
- normalizedUrl = absoluteURI.pathname;
- } catch (e) { //wasn't an absoluteURI
- if (!(e instanceof TypeError)) {
- throw e;
- }
- }
-
- if (normalizedUrl[0] !== "/") {
- throw new URIError("The request URI is malformed.");
- }
-
- normalizedUrl = posix.normalize(normalizedUrl);
- const startOfParams = normalizedUrl.indexOf("?");
- return startOfParams > -1
- ? normalizedUrl.slice(0, startOfParams)
- : normalizedUrl;
-}
-
-function main(): void {
- const CORSEnabled = serverArgs.cors ? true : false;
- const port = serverArgs.port ?? serverArgs.p ?? 4507;
- const host = serverArgs.host ?? "0.0.0.0";
- const addr = `${host}:${port}`;
- const tlsOpts = {} as HTTPSOptions;
- tlsOpts.certFile = serverArgs.cert ?? serverArgs.c ?? "";
- tlsOpts.keyFile = serverArgs.key ?? serverArgs.k ?? "";
- const dirListingEnabled = serverArgs["dir-listing"] ?? true;
-
- if (tlsOpts.keyFile || tlsOpts.certFile) {
- if (tlsOpts.keyFile === "" || tlsOpts.certFile === "") {
- console.log("--key and --cert are required for TLS");
- serverArgs.h = true;
- }
- }
-
- if (serverArgs.h ?? serverArgs.help) {
- console.log(`Deno File Server
- Serves a local directory in HTTP.
-
- INSTALL:
- deno install --allow-net --allow-read https://deno.land/std/http/file_server.ts
-
- USAGE:
- file_server [path] [options]
-
- OPTIONS:
- -h, --help Prints help information
- -p, --port <PORT> Set port
- --cors Enable CORS via the "Access-Control-Allow-Origin" header
- --host <HOST> Hostname (default is 0.0.0.0)
- -c, --cert <FILE> TLS certificate file (enables TLS)
- -k, --key <FILE> TLS key file (enables TLS)
- --no-dir-listing Disable directory listing
-
- All TLS options are required when one is provided.`);
- Deno.exit();
- }
-
- const handler = async (req: ServerRequest): Promise<void> => {
- let response: Response | undefined;
- try {
- const normalizedUrl = normalizeURL(req.url);
- let fsPath = posix.join(target, normalizedUrl);
- if (fsPath.indexOf(target) !== 0) {
- fsPath = target;
- }
- const fileInfo = await Deno.stat(fsPath);
- if (fileInfo.isDirectory) {
- if (dirListingEnabled) {
- response = await serveDir(req, fsPath);
- } else {
- throw new Deno.errors.NotFound();
- }
- } else {
- response = await serveFile(req, fsPath);
- }
- } catch (e) {
- console.error(e.message);
- response = await serveFallback(req, e);
- } finally {
- if (CORSEnabled) {
- assert(response);
- setCORS(response);
- }
- serverLog(req, response!);
- try {
- await req.respond(response!);
- } catch (e) {
- console.error(e.message);
- }
- }
- };
-
- let proto = "http";
- if (tlsOpts.keyFile || tlsOpts.certFile) {
- proto += "s";
- tlsOpts.hostname = host;
- tlsOpts.port = port;
- listenAndServeTLS(tlsOpts, handler);
- } else {
- listenAndServe(addr, handler);
- }
- console.log(`${proto.toUpperCase()} server listening on ${proto}://${addr}/`);
-}
-
-if (import.meta.main) {
- main();
-}
diff --git a/std/http/file_server_test.ts b/std/http/file_server_test.ts
deleted file mode 100644
index 638121b45..000000000
--- a/std/http/file_server_test.ts
+++ /dev/null
@@ -1,465 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-import {
- assert,
- assertEquals,
- assertNotEquals,
- assertStringIncludes,
-} from "../testing/asserts.ts";
-import { BufReader } from "../io/bufio.ts";
-import { TextProtoReader } from "../textproto/mod.ts";
-import { Response, ServerRequest } from "./server.ts";
-import { FileServerArgs, serveFile } from "./file_server.ts";
-import { dirname, fromFileUrl, join, resolve } from "../path/mod.ts";
-let fileServer: Deno.Process<Deno.RunOptions & { stdout: "piped" }>;
-
-type FileServerCfg = Omit<FileServerArgs, "_"> & { target?: string };
-
-const moduleDir = dirname(fromFileUrl(import.meta.url));
-const testdataDir = resolve(moduleDir, "testdata");
-
-async function startFileServer({
- target = ".",
- port = 4507,
- "dir-listing": dirListing = true,
-}: FileServerCfg = {}): Promise<void> {
- fileServer = Deno.run({
- cmd: [
- Deno.execPath(),
- "run",
- "--quiet",
- "--allow-read",
- "--allow-net",
- "file_server.ts",
- target,
- "--cors",
- "-p",
- `${port}`,
- `${dirListing ? "" : "--no-dir-listing"}`,
- ],
- cwd: moduleDir,
- stdout: "piped",
- stderr: "null",
- });
- // Once fileServer is ready it will write to its stdout.
- assert(fileServer.stdout != null);
- const r = new TextProtoReader(new BufReader(fileServer.stdout));
- const s = await r.readLine();
- assert(s !== null && s.includes("server listening"));
-}
-
-async function startFileServerAsLibrary({}: FileServerCfg = {}): Promise<void> {
- fileServer = await Deno.run({
- cmd: [
- Deno.execPath(),
- "run",
- "--quiet",
- "--allow-read",
- "--allow-net",
- "testdata/file_server_as_library.ts",
- ],
- cwd: moduleDir,
- stdout: "piped",
- stderr: "null",
- });
- assert(fileServer.stdout != null);
- const r = new TextProtoReader(new BufReader(fileServer.stdout));
- const s = await r.readLine();
- assert(s !== null && s.includes("Server running..."));
-}
-
-async function killFileServer(): Promise<void> {
- fileServer.close();
- // Process.close() kills the file server process. However this termination
- // happens asynchronously, and since we've just closed the process resource,
- // we can't use `await fileServer.status()` to wait for the process to have
- // exited. As a workaround, wait for its stdout to close instead.
- // TODO(piscisaureus): when `Process.kill()` is stable and works on Windows,
- // switch to calling `kill()` followed by `await fileServer.status()`.
- await Deno.readAll(fileServer.stdout!);
- fileServer.stdout!.close();
-}
-
-interface StringResponse extends Response {
- body: string;
-}
-
-/* HTTP GET request allowing arbitrary paths */
-async function fetchExactPath(
- hostname: string,
- port: number,
- path: string,
-): Promise<StringResponse> {
- const encoder = new TextEncoder();
- const decoder = new TextDecoder();
- const request = encoder.encode("GET " + path + " HTTP/1.1\r\n\r\n");
- let conn: void | Deno.Conn;
- try {
- conn = await Deno.connect(
- { hostname: hostname, port: port, transport: "tcp" },
- );
- await Deno.writeAll(conn, request);
- let currentResult = "";
- let contentLength = -1;
- let startOfBody = -1;
- for await (const chunk of Deno.iter(conn)) {
- currentResult += decoder.decode(chunk);
- if (contentLength === -1) {
- const match = /^content-length: (.*)$/m.exec(currentResult);
- if (match && match[1]) {
- contentLength = Number(match[1]);
- }
- }
- if (startOfBody === -1) {
- const ind = currentResult.indexOf("\r\n\r\n");
- if (ind !== -1) {
- startOfBody = ind + 4;
- }
- }
- if (startOfBody !== -1 && contentLength !== -1) {
- const byteLen = encoder.encode(currentResult).length;
- if (byteLen >= contentLength + startOfBody) {
- break;
- }
- }
- }
- const status = /^HTTP\/1.1 (...)/.exec(currentResult);
- let statusCode = 0;
- if (status && status[1]) {
- statusCode = Number(status[1]);
- }
-
- const body = currentResult.slice(startOfBody);
- const headersStr = currentResult.slice(0, startOfBody);
- const headersReg = /^(.*): (.*)$/mg;
- const headersObj: { [i: string]: string } = {};
- let match = headersReg.exec(headersStr);
- while (match !== null) {
- if (match[1] && match[2]) {
- headersObj[match[1]] = match[2];
- }
- match = headersReg.exec(headersStr);
- }
- return {
- status: statusCode,
- headers: new Headers(headersObj),
- body: body,
- };
- } finally {
- if (conn) {
- Deno.close(conn.rid);
- }
- }
-}
-
-Deno.test(
- "file_server serveFile",
- async (): Promise<void> => {
- await startFileServer();
- try {
- const res = await fetch("http://localhost:4507/README.md");
- assert(res.headers.has("access-control-allow-origin"));
- assert(res.headers.has("access-control-allow-headers"));
- assertEquals(res.headers.get("content-type"), "text/markdown");
- const downloadedFile = await res.text();
- const localFile = new TextDecoder().decode(
- await Deno.readFile(join(moduleDir, "README.md")),
- );
- assertEquals(downloadedFile, localFile);
- } finally {
- await killFileServer();
- }
- },
-);
-
-Deno.test(
- "file_server serveFile in testdata",
- async (): Promise<void> => {
- await startFileServer({ target: "./testdata" });
- try {
- const res = await fetch("http://localhost:4507/hello.html");
- assert(res.headers.has("access-control-allow-origin"));
- assert(res.headers.has("access-control-allow-headers"));
- assertEquals(res.headers.get("content-type"), "text/html");
- const downloadedFile = await res.text();
- const localFile = new TextDecoder().decode(
- await Deno.readFile(join(testdataDir, "hello.html")),
- );
- assertEquals(downloadedFile, localFile);
- } finally {
- await killFileServer();
- }
- },
-);
-
-Deno.test("serveDirectory", async function (): Promise<void> {
- await startFileServer();
- try {
- const res = await fetch("http://localhost:4507/");
- assert(res.headers.has("access-control-allow-origin"));
- assert(res.headers.has("access-control-allow-headers"));
- const page = await res.text();
- assert(page.includes("README.md"));
-
- // `Deno.FileInfo` is not completely compatible with Windows yet
- // TODO(bartlomieju): `mode` should work correctly in the future.
- // Correct this test case accordingly.
- Deno.build.os !== "windows" &&
- assert(/<td class="mode">(\s)*\([a-zA-Z-]{10}\)(\s)*<\/td>/.test(page));
- Deno.build.os === "windows" &&
- assert(/<td class="mode">(\s)*\(unknown mode\)(\s)*<\/td>/.test(page));
- assert(page.includes(`<a href="/README.md">README.md</a>`));
- } finally {
- await killFileServer();
- }
-});
-
-Deno.test("serveFallback", async function (): Promise<void> {
- await startFileServer();
- try {
- const res = await fetch("http://localhost:4507/badfile.txt");
- assert(res.headers.has("access-control-allow-origin"));
- assert(res.headers.has("access-control-allow-headers"));
- assertEquals(res.status, 404);
- const _ = await res.text();
- } finally {
- await killFileServer();
- }
-});
-
-Deno.test("checkPathTraversal", async function (): Promise<void> {
- await startFileServer();
- try {
- const res = await fetch(
- "http://localhost:4507/../../../../../../../..",
- );
- assert(res.headers.has("access-control-allow-origin"));
- assert(res.headers.has("access-control-allow-headers"));
- assertEquals(res.status, 200);
- const listing = await res.text();
- assertStringIncludes(listing, "README.md");
- } finally {
- await killFileServer();
- }
-});
-
-Deno.test("checkPathTraversalNoLeadingSlash", async function (): Promise<void> {
- await startFileServer();
- try {
- const res = await fetchExactPath("127.0.0.1", 4507, "../../../..");
- assertEquals(res.status, 400);
- } finally {
- await killFileServer();
- }
-});
-
-Deno.test("checkPathTraversalAbsoluteURI", async function (): Promise<void> {
- await startFileServer();
- try {
- //allowed per https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
- const res = await fetchExactPath(
- "127.0.0.1",
- 4507,
- "http://localhost/../../../..",
- );
- assertEquals(res.status, 200);
- assertStringIncludes(res.body, "README.md");
- } finally {
- await killFileServer();
- }
-});
-
-Deno.test("checkURIEncodedPathTraversal", async function (): Promise<void> {
- await startFileServer();
- try {
- const res = await fetch(
- "http://localhost:4507/%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..",
- );
- assert(res.headers.has("access-control-allow-origin"));
- assert(res.headers.has("access-control-allow-headers"));
- assertEquals(res.status, 404);
- const _ = await res.text();
- } finally {
- await killFileServer();
- }
-});
-
-Deno.test("serveWithUnorthodoxFilename", async function (): Promise<void> {
- await startFileServer();
- try {
- let res = await fetch("http://localhost:4507/testdata/%");
- assert(res.headers.has("access-control-allow-origin"));
- assert(res.headers.has("access-control-allow-headers"));
- assertEquals(res.status, 200);
- let _ = await res.text();
- res = await fetch("http://localhost:4507/testdata/test%20file.txt");
- assert(res.headers.has("access-control-allow-origin"));
- assert(res.headers.has("access-control-allow-headers"));
- assertEquals(res.status, 200);
- _ = await res.text();
- } finally {
- await killFileServer();
- }
-});
-
-Deno.test("printHelp", async function (): Promise<void> {
- const helpProcess = Deno.run({
- cmd: [
- Deno.execPath(),
- "run",
- "--quiet",
- // TODO(ry) It ought to be possible to get the help output without
- // --allow-read.
- "--allow-read",
- "file_server.ts",
- "--help",
- ],
- cwd: moduleDir,
- stdout: "piped",
- });
- assert(helpProcess.stdout != null);
- const r = new TextProtoReader(new BufReader(helpProcess.stdout));
- const s = await r.readLine();
- assert(s !== null && s.includes("Deno File Server"));
- helpProcess.close();
- helpProcess.stdout.close();
-});
-
-Deno.test("contentType", async () => {
- const request = new ServerRequest();
- const response = await serveFile(request, join(testdataDir, "hello.html"));
- const contentType = response.headers!.get("content-type");
- assertEquals(contentType, "text/html");
- (response.body as Deno.File).close();
-});
-
-Deno.test("file_server running as library", async function (): Promise<void> {
- await startFileServerAsLibrary();
- try {
- const res = await fetch("http://localhost:8000");
- assertEquals(res.status, 200);
- const _ = await res.text();
- } finally {
- await killFileServer();
- }
-});
-
-Deno.test("file_server should ignore query params", async () => {
- await startFileServer();
- try {
- const res = await fetch("http://localhost:4507/README.md?key=value");
- assertEquals(res.status, 200);
- const downloadedFile = await res.text();
- const localFile = new TextDecoder().decode(
- await Deno.readFile(join(moduleDir, "README.md")),
- );
- assertEquals(downloadedFile, localFile);
- } finally {
- await killFileServer();
- }
-});
-
-async function startTlsFileServer({
- target = ".",
- port = 4577,
-}: FileServerCfg = {}): Promise<void> {
- fileServer = Deno.run({
- cmd: [
- Deno.execPath(),
- "run",
- "--quiet",
- "--allow-read",
- "--allow-net",
- "file_server.ts",
- target,
- "--host",
- "localhost",
- "--cert",
- "./testdata/tls/localhost.crt",
- "--key",
- "./testdata/tls/localhost.key",
- "--cors",
- "-p",
- `${port}`,
- ],
- cwd: moduleDir,
- stdout: "piped",
- stderr: "null",
- });
- // Once fileServer is ready it will write to its stdout.
- assert(fileServer.stdout != null);
- const r = new TextProtoReader(new BufReader(fileServer.stdout));
- const s = await r.readLine();
- assert(s !== null && s.includes("server listening"));
-}
-
-Deno.test("serveDirectory TLS", async function (): Promise<void> {
- await startTlsFileServer();
- try {
- // Valid request after invalid
- const conn = await Deno.connectTls({
- hostname: "localhost",
- port: 4577,
- certFile: join(testdataDir, "tls/RootCA.pem"),
- });
-
- await Deno.writeAll(
- conn,
- new TextEncoder().encode("GET / HTTP/1.0\r\n\r\n"),
- );
- const res = new Uint8Array(128 * 1024);
- const nread = await conn.read(res);
- assert(nread !== null);
- conn.close();
- const page = new TextDecoder().decode(res.subarray(0, nread));
- assert(page.includes("<title>Deno File Server</title>"));
- } finally {
- await killFileServer();
- }
-});
-
-Deno.test("partial TLS arguments fail", async function (): Promise<void> {
- fileServer = Deno.run({
- cmd: [
- Deno.execPath(),
- "run",
- "--quiet",
- "--allow-read",
- "--allow-net",
- "file_server.ts",
- ".",
- "--host",
- "localhost",
- "--cert",
- "./testdata/tls/localhost.crt",
- "-p",
- `4578`,
- ],
- cwd: moduleDir,
- stdout: "piped",
- stderr: "null",
- });
- try {
- // Once fileServer is ready it will write to its stdout.
- assert(fileServer.stdout != null);
- const r = new TextProtoReader(new BufReader(fileServer.stdout));
- const s = await r.readLine();
- assert(
- s !== null && s.includes("--key and --cert are required for TLS"),
- );
- } finally {
- await killFileServer();
- }
-});
-
-Deno.test("file_server disable dir listings", async function (): Promise<void> {
- await startFileServer({ "dir-listing": false });
- try {
- const res = await fetch("http://localhost:4507/");
- assert(res.headers.has("access-control-allow-origin"));
- assert(res.headers.has("access-control-allow-headers"));
- assertEquals(res.status, 404);
- const _ = await res.text();
- } finally {
- await killFileServer();
- }
-});
diff --git a/std/http/http_status.ts b/std/http/http_status.ts
deleted file mode 100644
index a6148b2f7..000000000
--- a/std/http/http_status.ts
+++ /dev/null
@@ -1,197 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-
-/** HTTP status codes */
-export enum Status {
- /** RFC 7231, 6.2.1 */
- Continue = 100,
- /** RFC 7231, 6.2.2 */
- SwitchingProtocols = 101,
- /** RFC 2518, 10.1 */
- Processing = 102,
- /** RFC 8297 **/
- EarlyHints = 103,
- /** RFC 7231, 6.3.1 */
- OK = 200,
- /** RFC 7231, 6.3.2 */
- Created = 201,
- /** RFC 7231, 6.3.3 */
- Accepted = 202,
- /** RFC 7231, 6.3.4 */
- NonAuthoritativeInfo = 203,
- /** RFC 7231, 6.3.5 */
- NoContent = 204,
- /** RFC 7231, 6.3.6 */
- ResetContent = 205,
- /** RFC 7233, 4.1 */
- PartialContent = 206,
- /** RFC 4918, 11.1 */
- MultiStatus = 207,
- /** RFC 5842, 7.1 */
- AlreadyReported = 208,
- /** RFC 3229, 10.4.1 */
- IMUsed = 226,
-
- /** RFC 7231, 6.4.1 */
- MultipleChoices = 300,
- /** RFC 7231, 6.4.2 */
- MovedPermanently = 301,
- /** RFC 7231, 6.4.3 */
- Found = 302,
- /** RFC 7231, 6.4.4 */
- SeeOther = 303,
- /** RFC 7232, 4.1 */
- NotModified = 304,
- /** RFC 7231, 6.4.5 */
- UseProxy = 305,
- /** RFC 7231, 6.4.7 */
- TemporaryRedirect = 307,
- /** RFC 7538, 3 */
- PermanentRedirect = 308,
-
- /** RFC 7231, 6.5.1 */
- BadRequest = 400,
- /** RFC 7235, 3.1 */
- Unauthorized = 401,
- /** RFC 7231, 6.5.2 */
- PaymentRequired = 402,
- /** RFC 7231, 6.5.3 */
- Forbidden = 403,
- /** RFC 7231, 6.5.4 */
- NotFound = 404,
- /** RFC 7231, 6.5.5 */
- MethodNotAllowed = 405,
- /** RFC 7231, 6.5.6 */
- NotAcceptable = 406,
- /** RFC 7235, 3.2 */
- ProxyAuthRequired = 407,
- /** RFC 7231, 6.5.7 */
- RequestTimeout = 408,
- /** RFC 7231, 6.5.8 */
- Conflict = 409,
- /** RFC 7231, 6.5.9 */
- Gone = 410,
- /** RFC 7231, 6.5.10 */
- LengthRequired = 411,
- /** RFC 7232, 4.2 */
- PreconditionFailed = 412,
- /** RFC 7231, 6.5.11 */
- RequestEntityTooLarge = 413,
- /** RFC 7231, 6.5.12 */
- RequestURITooLong = 414,
- /** RFC 7231, 6.5.13 */
- UnsupportedMediaType = 415,
- /** RFC 7233, 4.4 */
- RequestedRangeNotSatisfiable = 416,
- /** RFC 7231, 6.5.14 */
- ExpectationFailed = 417,
- /** RFC 7168, 2.3.3 */
- Teapot = 418,
- /** RFC 7540, 9.1.2 */
- MisdirectedRequest = 421,
- /** RFC 4918, 11.2 */
- UnprocessableEntity = 422,
- /** RFC 4918, 11.3 */
- Locked = 423,
- /** RFC 4918, 11.4 */
- FailedDependency = 424,
- /** RFC 8470, 5.2 */
- TooEarly = 425,
- /** RFC 7231, 6.5.15 */
- UpgradeRequired = 426,
- /** RFC 6585, 3 */
- PreconditionRequired = 428,
- /** RFC 6585, 4 */
- TooManyRequests = 429,
- /** RFC 6585, 5 */
- RequestHeaderFieldsTooLarge = 431,
- /** RFC 7725, 3 */
- UnavailableForLegalReasons = 451,
-
- /** RFC 7231, 6.6.1 */
- InternalServerError = 500,
- /** RFC 7231, 6.6.2 */
- NotImplemented = 501,
- /** RFC 7231, 6.6.3 */
- BadGateway = 502,
- /** RFC 7231, 6.6.4 */
- ServiceUnavailable = 503,
- /** RFC 7231, 6.6.5 */
- GatewayTimeout = 504,
- /** RFC 7231, 6.6.6 */
- HTTPVersionNotSupported = 505,
- /** RFC 2295, 8.1 */
- VariantAlsoNegotiates = 506,
- /** RFC 4918, 11.5 */
- InsufficientStorage = 507,
- /** RFC 5842, 7.2 */
- LoopDetected = 508,
- /** RFC 2774, 7 */
- NotExtended = 510,
- /** RFC 6585, 6 */
- NetworkAuthenticationRequired = 511,
-}
-
-export const STATUS_TEXT = new Map<Status, string>([
- [Status.Continue, "Continue"],
- [Status.SwitchingProtocols, "Switching Protocols"],
- [Status.Processing, "Processing"],
- [Status.EarlyHints, "Early Hints"],
- [Status.OK, "OK"],
- [Status.Created, "Created"],
- [Status.Accepted, "Accepted"],
- [Status.NonAuthoritativeInfo, "Non-Authoritative Information"],
- [Status.NoContent, "No Content"],
- [Status.ResetContent, "Reset Content"],
- [Status.PartialContent, "Partial Content"],
- [Status.MultiStatus, "Multi-Status"],
- [Status.AlreadyReported, "Already Reported"],
- [Status.IMUsed, "IM Used"],
- [Status.MultipleChoices, "Multiple Choices"],
- [Status.MovedPermanently, "Moved Permanently"],
- [Status.Found, "Found"],
- [Status.SeeOther, "See Other"],
- [Status.NotModified, "Not Modified"],
- [Status.UseProxy, "Use Proxy"],
- [Status.TemporaryRedirect, "Temporary Redirect"],
- [Status.PermanentRedirect, "Permanent Redirect"],
- [Status.BadRequest, "Bad Request"],
- [Status.Unauthorized, "Unauthorized"],
- [Status.PaymentRequired, "Payment Required"],
- [Status.Forbidden, "Forbidden"],
- [Status.NotFound, "Not Found"],
- [Status.MethodNotAllowed, "Method Not Allowed"],
- [Status.NotAcceptable, "Not Acceptable"],
- [Status.ProxyAuthRequired, "Proxy Authentication Required"],
- [Status.RequestTimeout, "Request Timeout"],
- [Status.Conflict, "Conflict"],
- [Status.Gone, "Gone"],
- [Status.LengthRequired, "Length Required"],
- [Status.PreconditionFailed, "Precondition Failed"],
- [Status.RequestEntityTooLarge, "Request Entity Too Large"],
- [Status.RequestURITooLong, "Request URI Too Long"],
- [Status.UnsupportedMediaType, "Unsupported Media Type"],
- [Status.RequestedRangeNotSatisfiable, "Requested Range Not Satisfiable"],
- [Status.ExpectationFailed, "Expectation Failed"],
- [Status.Teapot, "I'm a teapot"],
- [Status.MisdirectedRequest, "Misdirected Request"],
- [Status.UnprocessableEntity, "Unprocessable Entity"],
- [Status.Locked, "Locked"],
- [Status.FailedDependency, "Failed Dependency"],
- [Status.TooEarly, "Too Early"],
- [Status.UpgradeRequired, "Upgrade Required"],
- [Status.PreconditionRequired, "Precondition Required"],
- [Status.TooManyRequests, "Too Many Requests"],
- [Status.RequestHeaderFieldsTooLarge, "Request Header Fields Too Large"],
- [Status.UnavailableForLegalReasons, "Unavailable For Legal Reasons"],
- [Status.InternalServerError, "Internal Server Error"],
- [Status.NotImplemented, "Not Implemented"],
- [Status.BadGateway, "Bad Gateway"],
- [Status.ServiceUnavailable, "Service Unavailable"],
- [Status.GatewayTimeout, "Gateway Timeout"],
- [Status.HTTPVersionNotSupported, "HTTP Version Not Supported"],
- [Status.VariantAlsoNegotiates, "Variant Also Negotiates"],
- [Status.InsufficientStorage, "Insufficient Storage"],
- [Status.LoopDetected, "Loop Detected"],
- [Status.NotExtended, "Not Extended"],
- [Status.NetworkAuthenticationRequired, "Network Authentication Required"],
-]);
diff --git a/std/http/mod.ts b/std/http/mod.ts
deleted file mode 100644
index 827eaebf8..000000000
--- a/std/http/mod.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-export * from "./cookie.ts";
-export * from "./http_status.ts";
-export * from "./server.ts";
diff --git a/std/http/racing_server.ts b/std/http/racing_server.ts
deleted file mode 100644
index b5cf69298..000000000
--- a/std/http/racing_server.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-import { serve, ServerRequest } from "./server.ts";
-import { delay } from "../async/delay.ts";
-
-const addr = Deno.args[1] || "127.0.0.1:4501";
-const server = serve(addr);
-
-function body(i: number): string {
- return `Step${i}\n`;
-}
-async function delayedRespond(
- request: ServerRequest,
- step: number,
-): Promise<void> {
- await delay(3000);
- await request.respond({ status: 200, body: body(step) });
-}
-
-async function largeRespond(request: ServerRequest, c: string): Promise<void> {
- const b = new Uint8Array(1024 * 1024);
- b.fill(c.charCodeAt(0));
- await request.respond({ status: 200, body: b });
-}
-
-async function ignoreToConsume(
- request: ServerRequest,
- step: number,
-): Promise<void> {
- await request.respond({ status: 200, body: body(step) });
-}
-
-console.log("Racing server listening...\n");
-
-let step = 1;
-for await (const request of server) {
- switch (step) {
- case 1:
- // Try to wait long enough.
- // For pipelining, this should cause all the following response
- // to block.
- delayedRespond(request, step);
- break;
- case 2:
- // HUGE body.
- largeRespond(request, "a");
- break;
- case 3:
- // HUGE body.
- largeRespond(request, "b");
- break;
- case 4:
- // Ignore to consume body (content-length)
- ignoreToConsume(request, step);
- break;
- case 5:
- // Ignore to consume body (chunked)
- ignoreToConsume(request, step);
- break;
- case 6:
- // Ignore to consume body (chunked + trailers)
- ignoreToConsume(request, step);
- break;
- default:
- request.respond({ status: 200, body: body(step) });
- break;
- }
- step++;
-}
diff --git a/std/http/racing_server_test.ts b/std/http/racing_server_test.ts
deleted file mode 100644
index 8018a4312..000000000
--- a/std/http/racing_server_test.ts
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-import { assert, assertEquals } from "../testing/asserts.ts";
-import { BufReader, BufWriter } from "../io/bufio.ts";
-import { TextProtoReader } from "../textproto/mod.ts";
-import { dirname, fromFileUrl } from "../path/mod.ts";
-
-const moduleDir = dirname(fromFileUrl(import.meta.url));
-
-let server: Deno.Process<Deno.RunOptions & { stdout: "piped" }>;
-async function startServer(): Promise<void> {
- server = Deno.run({
- cmd: [Deno.execPath(), "run", "--quiet", "-A", "racing_server.ts"],
- cwd: moduleDir,
- stdout: "piped",
- });
- // Once racing server is ready it will write to its stdout.
- assert(server.stdout != null);
- const r = new TextProtoReader(new BufReader(server.stdout));
- const s = await r.readLine();
- assert(s !== null && s.includes("Racing server listening..."));
-}
-function killServer(): void {
- server.close();
- server.stdout.close();
-}
-
-const input = [
- "GET / HTTP/1.1\r\n\r\n",
- "GET / HTTP/1.1\r\n\r\n",
- "GET / HTTP/1.1\r\n\r\n",
- "POST / HTTP/1.1\r\ncontent-length: 4\r\n\r\ndeno",
- "POST / HTTP/1.1\r\ntransfer-encoding: chunked\r\n\r\n4\r\ndeno\r\n0\r\n\r\n",
- "POST / HTTP/1.1\r\ntransfer-encoding: chunked\r\ntrailer: deno\r\n\r\n4\r\ndeno\r\n0\r\n\r\ndeno: land\r\n\r\n",
- "GET / HTTP/1.1\r\n\r\n",
-].join("");
-const HUGE_BODY_SIZE = 1024 * 1024;
-const output = `HTTP/1.1 200 OK
-content-length: 6
-
-Step1
-HTTP/1.1 200 OK
-content-length: ${HUGE_BODY_SIZE}
-
-${"a".repeat(HUGE_BODY_SIZE)}HTTP/1.1 200 OK
-content-length: ${HUGE_BODY_SIZE}
-
-${"b".repeat(HUGE_BODY_SIZE)}HTTP/1.1 200 OK
-content-length: 6
-
-Step4
-HTTP/1.1 200 OK
-content-length: 6
-
-Step5
-HTTP/1.1 200 OK
-content-length: 6
-
-Step6
-HTTP/1.1 200 OK
-content-length: 6
-
-Step7
-`;
-
-Deno.test("serverPipelineRace", async function (): Promise<void> {
- await startServer();
-
- const conn = await Deno.connect({ port: 4501 });
- const r = new TextProtoReader(new BufReader(conn));
- const w = new BufWriter(conn);
- await w.write(new TextEncoder().encode(input));
- await w.flush();
- const outLines = output.split("\n");
- // length - 1 to disregard last empty line
- for (let i = 0; i < outLines.length - 1; i++) {
- const s = await r.readLine();
- assertEquals(s, outLines[i]);
- }
- killServer();
- conn.close();
-});
diff --git a/std/http/server.ts b/std/http/server.ts
deleted file mode 100644
index f17c759c4..000000000
--- a/std/http/server.ts
+++ /dev/null
@@ -1,399 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-import { encode } from "../encoding/utf8.ts";
-import { BufReader, BufWriter } from "../io/bufio.ts";
-import { assert } from "../_util/assert.ts";
-import { Deferred, deferred, MuxAsyncIterator } from "../async/mod.ts";
-import {
- bodyReader,
- chunkedBodyReader,
- emptyReader,
- readRequest,
- writeResponse,
-} from "./_io.ts";
-
-export class ServerRequest {
- url!: string;
- method!: string;
- proto!: string;
- protoMinor!: number;
- protoMajor!: number;
- headers!: Headers;
- conn!: Deno.Conn;
- r!: BufReader;
- w!: BufWriter;
-
- #done: Deferred<Error | undefined> = deferred();
- #contentLength?: number | null = undefined;
- #body?: Deno.Reader = undefined;
- #finalized = false;
-
- get done(): Promise<Error | undefined> {
- return this.#done.then((e) => e);
- }
-
- /**
- * Value of Content-Length header.
- * If null, then content length is invalid or not given (e.g. chunked encoding).
- */
- get contentLength(): number | null {
- // undefined means not cached.
- // null means invalid or not provided.
- if (this.#contentLength === undefined) {
- const cl = this.headers.get("content-length");
- if (cl) {
- this.#contentLength = parseInt(cl);
- // Convert NaN to null (as NaN harder to test)
- if (Number.isNaN(this.#contentLength)) {
- this.#contentLength = null;
- }
- } else {
- this.#contentLength = null;
- }
- }
- return this.#contentLength;
- }
-
- /**
- * Body of the request. The easiest way to consume the body is:
- *
- * const buf: Uint8Array = await Deno.readAll(req.body);
- */
- get body(): Deno.Reader {
- if (!this.#body) {
- if (this.contentLength != null) {
- this.#body = bodyReader(this.contentLength, this.r);
- } else {
- const transferEncoding = this.headers.get("transfer-encoding");
- if (transferEncoding != null) {
- const parts = transferEncoding
- .split(",")
- .map((e): string => e.trim().toLowerCase());
- assert(
- parts.includes("chunked"),
- 'transfer-encoding must include "chunked" if content-length is not set',
- );
- this.#body = chunkedBodyReader(this.headers, this.r);
- } else {
- // Neither content-length nor transfer-encoding: chunked
- this.#body = emptyReader();
- }
- }
- }
- return this.#body;
- }
-
- async respond(r: Response): Promise<void> {
- let err: Error | undefined;
- try {
- // Write our response!
- await writeResponse(this.w, r);
- } catch (e) {
- try {
- // Eagerly close on error.
- this.conn.close();
- } catch {
- // Pass
- }
- err = e;
- }
- // Signal that this request has been processed and the next pipelined
- // request on the same connection can be accepted.
- this.#done.resolve(err);
- if (err) {
- // Error during responding, rethrow.
- throw err;
- }
- }
-
- async finalize(): Promise<void> {
- if (this.#finalized) return;
- // Consume unread body
- const body = this.body;
- const buf = new Uint8Array(1024);
- while ((await body.read(buf)) !== null) {
- // Pass
- }
- this.#finalized = true;
- }
-}
-
-export class Server implements AsyncIterable<ServerRequest> {
- #closing = false;
- #connections: Deno.Conn[] = [];
-
- constructor(public listener: Deno.Listener) {}
-
- close(): void {
- this.#closing = true;
- this.listener.close();
- for (const conn of this.#connections) {
- try {
- conn.close();
- } catch (e) {
- // Connection might have been already closed
- if (!(e instanceof Deno.errors.BadResource)) {
- throw e;
- }
- }
- }
- }
-
- // Yields all HTTP requests on a single TCP connection.
- private async *iterateHttpRequests(
- conn: Deno.Conn,
- ): AsyncIterableIterator<ServerRequest> {
- const reader = new BufReader(conn);
- const writer = new BufWriter(conn);
-
- while (!this.#closing) {
- let request: ServerRequest | null;
- try {
- request = await readRequest(conn, reader);
- } catch (error) {
- if (
- error instanceof Deno.errors.InvalidData ||
- error instanceof Deno.errors.UnexpectedEof
- ) {
- // An error was thrown while parsing request headers.
- // Try to send the "400 Bad Request" before closing the connection.
- try {
- await writeResponse(writer, {
- status: 400,
- body: encode(`${error.message}\r\n\r\n`),
- });
- } catch (error) {
- // The connection is broken.
- }
- }
- break;
- }
- if (request === null) {
- break;
- }
-
- request.w = writer;
- yield request;
-
- // Wait for the request to be processed before we accept a new request on
- // this connection.
- const responseError = await request.done;
- if (responseError) {
- // Something bad happened during response.
- // (likely other side closed during pipelined req)
- // req.done implies this connection already closed, so we can just return.
- this.untrackConnection(request.conn);
- return;
- }
-
- try {
- // Consume unread body and trailers if receiver didn't consume those data
- await request.finalize();
- } catch (error) {
- // Invalid data was received or the connection was closed.
- break;
- }
- }
-
- this.untrackConnection(conn);
- try {
- conn.close();
- } catch (e) {
- // might have been already closed
- }
- }
-
- private trackConnection(conn: Deno.Conn): void {
- this.#connections.push(conn);
- }
-
- private untrackConnection(conn: Deno.Conn): void {
- const index = this.#connections.indexOf(conn);
- if (index !== -1) {
- this.#connections.splice(index, 1);
- }
- }
-
- // Accepts a new TCP connection and yields all HTTP requests that arrive on
- // it. When a connection is accepted, it also creates a new iterator of the
- // same kind and adds it to the request multiplexer so that another TCP
- // connection can be accepted.
- private async *acceptConnAndIterateHttpRequests(
- mux: MuxAsyncIterator<ServerRequest>,
- ): AsyncIterableIterator<ServerRequest> {
- if (this.#closing) return;
- // Wait for a new connection.
- let conn: Deno.Conn;
- try {
- conn = await this.listener.accept();
- } catch (error) {
- if (
- // The listener is closed:
- error instanceof Deno.errors.BadResource ||
- // TLS handshake errors:
- error instanceof Deno.errors.InvalidData ||
- error instanceof Deno.errors.UnexpectedEof ||
- error instanceof Deno.errors.ConnectionReset
- ) {
- return mux.add(this.acceptConnAndIterateHttpRequests(mux));
- }
- throw error;
- }
- this.trackConnection(conn);
- // Try to accept another connection and add it to the multiplexer.
- mux.add(this.acceptConnAndIterateHttpRequests(mux));
- // Yield the requests that arrive on the just-accepted connection.
- yield* this.iterateHttpRequests(conn);
- }
-
- [Symbol.asyncIterator](): AsyncIterableIterator<ServerRequest> {
- const mux: MuxAsyncIterator<ServerRequest> = new MuxAsyncIterator();
- mux.add(this.acceptConnAndIterateHttpRequests(mux));
- return mux.iterate();
- }
-}
-
-/** Options for creating an HTTP server. */
-export type HTTPOptions = Omit<Deno.ListenOptions, "transport">;
-
-/**
- * Parse addr from string
- *
- * const addr = "::1:8000";
- * parseAddrFromString(addr);
- *
- * @param addr Address string
- */
-export function _parseAddrFromStr(addr: string): HTTPOptions {
- let url: URL;
- try {
- const host = addr.startsWith(":") ? `0.0.0.0${addr}` : addr;
- url = new URL(`http://${host}`);
- } catch {
- throw new TypeError("Invalid address.");
- }
- if (
- url.username ||
- url.password ||
- url.pathname != "/" ||
- url.search ||
- url.hash
- ) {
- throw new TypeError("Invalid address.");
- }
-
- return {
- hostname: url.hostname,
- port: url.port === "" ? 80 : Number(url.port),
- };
-}
-
-/**
- * Create a HTTP server
- *
- * import { serve } from "https://deno.land/std/http/server.ts";
- * const body = "Hello World\n";
- * const server = serve({ port: 8000 });
- * for await (const req of server) {
- * req.respond({ body });
- * }
- */
-export function serve(addr: string | HTTPOptions): Server {
- if (typeof addr === "string") {
- addr = _parseAddrFromStr(addr);
- }
-
- const listener = Deno.listen(addr);
- return new Server(listener);
-}
-
-/**
- * Start an HTTP server with given options and request handler
- *
- * const body = "Hello World\n";
- * const options = { port: 8000 };
- * listenAndServe(options, (req) => {
- * req.respond({ body });
- * });
- *
- * @param options Server configuration
- * @param handler Request handler
- */
-export async function listenAndServe(
- addr: string | HTTPOptions,
- handler: (req: ServerRequest) => void,
-): Promise<void> {
- const server = serve(addr);
-
- for await (const request of server) {
- handler(request);
- }
-}
-
-/** Options for creating an HTTPS server. */
-export type HTTPSOptions = Omit<Deno.ListenTlsOptions, "transport">;
-
-/**
- * Create an HTTPS server with given options
- *
- * const body = "Hello HTTPS";
- * const options = {
- * hostname: "localhost",
- * port: 443,
- * certFile: "./path/to/localhost.crt",
- * keyFile: "./path/to/localhost.key",
- * };
- * for await (const req of serveTLS(options)) {
- * req.respond({ body });
- * }
- *
- * @param options Server configuration
- * @return Async iterable server instance for incoming requests
- */
-export function serveTLS(options: HTTPSOptions): Server {
- const tlsOptions: Deno.ListenTlsOptions = {
- ...options,
- transport: "tcp",
- };
- const listener = Deno.listenTls(tlsOptions);
- return new Server(listener);
-}
-
-/**
- * Start an HTTPS server with given options and request handler
- *
- * const body = "Hello HTTPS";
- * const options = {
- * hostname: "localhost",
- * port: 443,
- * certFile: "./path/to/localhost.crt",
- * keyFile: "./path/to/localhost.key",
- * };
- * listenAndServeTLS(options, (req) => {
- * req.respond({ body });
- * });
- *
- * @param options Server configuration
- * @param handler Request handler
- */
-export async function listenAndServeTLS(
- options: HTTPSOptions,
- handler: (req: ServerRequest) => void,
-): Promise<void> {
- const server = serveTLS(options);
-
- for await (const request of server) {
- handler(request);
- }
-}
-
-/**
- * Interface of HTTP server response.
- * If body is a Reader, response would be chunked.
- * If body is a string, it would be UTF-8 encoded by default.
- */
-export interface Response {
- status?: number;
- headers?: Headers;
- body?: Uint8Array | Deno.Reader | string;
- trailers?: () => Promise<Headers> | Headers;
-}
diff --git a/std/http/server_test.ts b/std/http/server_test.ts
deleted file mode 100644
index 8a3be71c2..000000000
--- a/std/http/server_test.ts
+++ /dev/null
@@ -1,784 +0,0 @@
-// Copyright 2010 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Ported from
-// https://github.com/golang/go/blob/master/src/net/http/responsewrite_test.go
-
-import { TextProtoReader } from "../textproto/mod.ts";
-import {
- assert,
- assertEquals,
- assertMatch,
- assertStringIncludes,
- assertThrowsAsync,
-} from "../testing/asserts.ts";
-import {
- _parseAddrFromStr,
- Response,
- serve,
- Server,
- ServerRequest,
- serveTLS,
-} from "./server.ts";
-import { BufReader, BufWriter } from "../io/bufio.ts";
-import { delay } from "../async/delay.ts";
-import { decode, encode } from "../encoding/utf8.ts";
-import { mockConn } from "./_mock_conn.ts";
-import { dirname, fromFileUrl, join, resolve } from "../path/mod.ts";
-
-const moduleDir = dirname(fromFileUrl(import.meta.url));
-const testdataDir = resolve(moduleDir, "testdata");
-
-interface ResponseTest {
- response: Response;
- raw: string;
-}
-
-const responseTests: ResponseTest[] = [
- // Default response
- {
- response: {},
- raw: "HTTP/1.1 200 OK\r\n" + "content-length: 0" + "\r\n\r\n",
- },
- // Empty body with status
- {
- response: {
- status: 404,
- },
- raw: "HTTP/1.1 404 Not Found\r\n" + "content-length: 0" + "\r\n\r\n",
- },
- // HTTP/1.1, chunked coding; empty trailer; close
- {
- response: {
- status: 200,
- body: new Deno.Buffer(new TextEncoder().encode("abcdef")),
- },
-
- raw: "HTTP/1.1 200 OK\r\n" +
- "transfer-encoding: chunked\r\n\r\n" +
- "6\r\nabcdef\r\n0\r\n\r\n",
- },
-];
-
-Deno.test("responseWrite", async function (): Promise<void> {
- for (const testCase of responseTests) {
- const buf = new Deno.Buffer();
- const bufw = new BufWriter(buf);
- const request = new ServerRequest();
- request.w = bufw;
-
- request.conn = mockConn();
-
- await request.respond(testCase.response);
- assertEquals(new TextDecoder().decode(buf.bytes()), testCase.raw);
- await request.done;
- }
-});
-
-Deno.test("requestContentLength", function (): void {
- // Has content length
- {
- const req = new ServerRequest();
- req.headers = new Headers();
- req.headers.set("content-length", "5");
- const buf = new Deno.Buffer(encode("Hello"));
- req.r = new BufReader(buf);
- assertEquals(req.contentLength, 5);
- }
- // No content length
- {
- const shortText = "Hello";
- const req = new ServerRequest();
- req.headers = new Headers();
- req.headers.set("transfer-encoding", "chunked");
- let chunksData = "";
- let chunkOffset = 0;
- const maxChunkSize = 70;
- while (chunkOffset < shortText.length) {
- const chunkSize = Math.min(maxChunkSize, shortText.length - chunkOffset);
- chunksData += `${chunkSize.toString(16)}\r\n${
- shortText.substr(chunkOffset, chunkSize)
- }\r\n`;
- chunkOffset += chunkSize;
- }
- chunksData += "0\r\n\r\n";
- const buf = new Deno.Buffer(encode(chunksData));
- req.r = new BufReader(buf);
- assertEquals(req.contentLength, null);
- }
-});
-
-interface TotalReader extends Deno.Reader {
- total: number;
-}
-function totalReader(r: Deno.Reader): TotalReader {
- let _total = 0;
- async function read(p: Uint8Array): Promise<number | null> {
- const result = await r.read(p);
- if (typeof result === "number") {
- _total += result;
- }
- return result;
- }
- return {
- read,
- get total(): number {
- return _total;
- },
- };
-}
-Deno.test("requestBodyWithContentLength", async function (): Promise<void> {
- {
- const req = new ServerRequest();
- req.headers = new Headers();
- req.headers.set("content-length", "5");
- const buf = new Deno.Buffer(encode("Hello"));
- req.r = new BufReader(buf);
- const body = decode(await Deno.readAll(req.body));
- assertEquals(body, "Hello");
- }
-
- // Larger than internal buf
- {
- const longText = "1234\n".repeat(1000);
- const req = new ServerRequest();
- req.headers = new Headers();
- req.headers.set("Content-Length", "5000");
- const buf = new Deno.Buffer(encode(longText));
- req.r = new BufReader(buf);
- const body = decode(await Deno.readAll(req.body));
- assertEquals(body, longText);
- }
- // Handler ignored to consume body
-});
-Deno.test(
- "ServerRequest.finalize() should consume unread body / content-length",
- async () => {
- const text = "deno.land";
- const req = new ServerRequest();
- req.headers = new Headers();
- req.headers.set("content-length", "" + text.length);
- const tr = totalReader(new Deno.Buffer(encode(text)));
- req.r = new BufReader(tr);
- req.w = new BufWriter(new Deno.Buffer());
- await req.respond({ status: 200, body: "ok" });
- assertEquals(tr.total, 0);
- await req.finalize();
- assertEquals(tr.total, text.length);
- },
-);
-Deno.test(
- "ServerRequest.finalize() should consume unread body / chunked, trailers",
- async () => {
- const text = [
- "5",
- "Hello",
- "4",
- "Deno",
- "0",
- "",
- "deno: land",
- "node: js",
- "",
- "",
- ].join("\r\n");
- const req = new ServerRequest();
- req.headers = new Headers();
- req.headers.set("transfer-encoding", "chunked");
- req.headers.set("trailer", "deno,node");
- const body = encode(text);
- const tr = totalReader(new Deno.Buffer(body));
- req.r = new BufReader(tr);
- req.w = new BufWriter(new Deno.Buffer());
- await req.respond({ status: 200, body: "ok" });
- assertEquals(tr.total, 0);
- assertEquals(req.headers.has("trailer"), true);
- assertEquals(req.headers.has("deno"), false);
- assertEquals(req.headers.has("node"), false);
- await req.finalize();
- assertEquals(tr.total, body.byteLength);
- assertEquals(req.headers.has("trailer"), false);
- assertEquals(req.headers.get("deno"), "land");
- assertEquals(req.headers.get("node"), "js");
- },
-);
-Deno.test("requestBodyWithTransferEncoding", async function (): Promise<void> {
- {
- const shortText = "Hello";
- const req = new ServerRequest();
- req.headers = new Headers();
- req.headers.set("transfer-encoding", "chunked");
- let chunksData = "";
- let chunkOffset = 0;
- const maxChunkSize = 70;
- while (chunkOffset < shortText.length) {
- const chunkSize = Math.min(maxChunkSize, shortText.length - chunkOffset);
- chunksData += `${chunkSize.toString(16)}\r\n${
- shortText.substr(chunkOffset, chunkSize)
- }\r\n`;
- chunkOffset += chunkSize;
- }
- chunksData += "0\r\n\r\n";
- const buf = new Deno.Buffer(encode(chunksData));
- req.r = new BufReader(buf);
- const body = decode(await Deno.readAll(req.body));
- assertEquals(body, shortText);
- }
-
- // Larger than internal buf
- {
- const longText = "1234\n".repeat(1000);
- const req = new ServerRequest();
- req.headers = new Headers();
- req.headers.set("transfer-encoding", "chunked");
- let chunksData = "";
- let chunkOffset = 0;
- const maxChunkSize = 70;
- while (chunkOffset < longText.length) {
- const chunkSize = Math.min(maxChunkSize, longText.length - chunkOffset);
- chunksData += `${chunkSize.toString(16)}\r\n${
- longText.substr(chunkOffset, chunkSize)
- }\r\n`;
- chunkOffset += chunkSize;
- }
- chunksData += "0\r\n\r\n";
- const buf = new Deno.Buffer(encode(chunksData));
- req.r = new BufReader(buf);
- const body = decode(await Deno.readAll(req.body));
- assertEquals(body, longText);
- }
-});
-
-Deno.test("requestBodyReaderWithContentLength", async function (): Promise<
- void
-> {
- {
- const shortText = "Hello";
- const req = new ServerRequest();
- req.headers = new Headers();
- req.headers.set("content-length", "" + shortText.length);
- const buf = new Deno.Buffer(encode(shortText));
- req.r = new BufReader(buf);
- const readBuf = new Uint8Array(6);
- let offset = 0;
- while (offset < shortText.length) {
- const nread = await req.body.read(readBuf);
- assert(nread !== null);
- const s = decode(readBuf.subarray(0, nread as number));
- assertEquals(shortText.substr(offset, nread as number), s);
- offset += nread as number;
- }
- const nread = await req.body.read(readBuf);
- assertEquals(nread, null);
- }
-
- // Larger than given buf
- {
- const longText = "1234\n".repeat(1000);
- const req = new ServerRequest();
- req.headers = new Headers();
- req.headers.set("Content-Length", "5000");
- const buf = new Deno.Buffer(encode(longText));
- req.r = new BufReader(buf);
- const readBuf = new Uint8Array(1000);
- let offset = 0;
- while (offset < longText.length) {
- const nread = await req.body.read(readBuf);
- assert(nread !== null);
- const s = decode(readBuf.subarray(0, nread as number));
- assertEquals(longText.substr(offset, nread as number), s);
- offset += nread as number;
- }
- const nread = await req.body.read(readBuf);
- assertEquals(nread, null);
- }
-});
-
-Deno.test("requestBodyReaderWithTransferEncoding", async function (): Promise<
- void
-> {
- {
- const shortText = "Hello";
- const req = new ServerRequest();
- req.headers = new Headers();
- req.headers.set("transfer-encoding", "chunked");
- let chunksData = "";
- let chunkOffset = 0;
- const maxChunkSize = 70;
- while (chunkOffset < shortText.length) {
- const chunkSize = Math.min(maxChunkSize, shortText.length - chunkOffset);
- chunksData += `${chunkSize.toString(16)}\r\n${
- shortText.substr(chunkOffset, chunkSize)
- }\r\n`;
- chunkOffset += chunkSize;
- }
- chunksData += "0\r\n\r\n";
- const buf = new Deno.Buffer(encode(chunksData));
- req.r = new BufReader(buf);
- const readBuf = new Uint8Array(6);
- let offset = 0;
- while (offset < shortText.length) {
- const nread = await req.body.read(readBuf);
- assert(nread !== null);
- const s = decode(readBuf.subarray(0, nread as number));
- assertEquals(shortText.substr(offset, nread as number), s);
- offset += nread as number;
- }
- const nread = await req.body.read(readBuf);
- assertEquals(nread, null);
- }
-
- // Larger than internal buf
- {
- const longText = "1234\n".repeat(1000);
- const req = new ServerRequest();
- req.headers = new Headers();
- req.headers.set("transfer-encoding", "chunked");
- let chunksData = "";
- let chunkOffset = 0;
- const maxChunkSize = 70;
- while (chunkOffset < longText.length) {
- const chunkSize = Math.min(maxChunkSize, longText.length - chunkOffset);
- chunksData += `${chunkSize.toString(16)}\r\n${
- longText.substr(chunkOffset, chunkSize)
- }\r\n`;
- chunkOffset += chunkSize;
- }
- chunksData += "0\r\n\r\n";
- const buf = new Deno.Buffer(encode(chunksData));
- req.r = new BufReader(buf);
- const readBuf = new Uint8Array(1000);
- let offset = 0;
- while (offset < longText.length) {
- const nread = await req.body.read(readBuf);
- assert(nread !== null);
- const s = decode(readBuf.subarray(0, nread as number));
- assertEquals(longText.substr(offset, nread as number), s);
- offset += nread as number;
- }
- const nread = await req.body.read(readBuf);
- assertEquals(nread, null);
- }
-});
-
-Deno.test({
- name: "destroyed connection",
- fn: async (): Promise<void> => {
- // Runs a simple server as another process
- const p = Deno.run({
- cmd: [
- Deno.execPath(),
- "run",
- "--quiet",
- "--allow-net",
- "testdata/simple_server.ts",
- ],
- cwd: moduleDir,
- stdout: "piped",
- });
-
- let serverIsRunning = true;
- const statusPromise = p
- .status()
- .then((): void => {
- serverIsRunning = false;
- })
- .catch((_): void => {}); // Ignores the error when closing the process.
-
- try {
- const r = new TextProtoReader(new BufReader(p.stdout));
- const s = await r.readLine();
- assert(s !== null && s.includes("server listening"));
- await delay(100);
- // Reqeusts to the server and immediately closes the connection
- const conn = await Deno.connect({ port: 4502 });
- await conn.write(new TextEncoder().encode("GET / HTTP/1.0\n\n"));
- conn.close();
- // Waits for the server to handle the above (broken) request
- await delay(100);
- assert(serverIsRunning);
- } finally {
- // Stops the sever and allows `p.status()` promise to resolve
- Deno.kill(p.pid, Deno.Signal.SIGKILL);
- await statusPromise;
- p.stdout.close();
- p.close();
- }
- },
-});
-
-Deno.test({
- name: "serveTLS",
- fn: async (): Promise<void> => {
- // Runs a simple server as another process
- const p = Deno.run({
- cmd: [
- Deno.execPath(),
- "run",
- "--quiet",
- "--allow-net",
- "--allow-read",
- "testdata/simple_https_server.ts",
- ],
- cwd: moduleDir,
- stdout: "piped",
- });
-
- let serverIsRunning = true;
- const statusPromise = p
- .status()
- .then((): void => {
- serverIsRunning = false;
- })
- .catch((_): void => {}); // Ignores the error when closing the process.
-
- try {
- const r = new TextProtoReader(new BufReader(p.stdout));
- const s = await r.readLine();
- assert(
- s !== null && s.includes("server listening"),
- "server must be started",
- );
- // Requests to the server and immediately closes the connection
- const conn = await Deno.connectTls({
- hostname: "localhost",
- port: 4503,
- certFile: join(testdataDir, "tls/RootCA.pem"),
- });
- await Deno.writeAll(
- conn,
- new TextEncoder().encode("GET / HTTP/1.0\r\n\r\n"),
- );
- const res = new Uint8Array(100);
- const nread = await conn.read(res);
- assert(nread !== null);
- conn.close();
- const resStr = new TextDecoder().decode(res.subarray(0, nread));
- assert(resStr.includes("Hello HTTPS"));
- assert(serverIsRunning);
- } finally {
- // Stops the sever and allows `p.status()` promise to resolve
- Deno.kill(p.pid, Deno.Signal.SIGKILL);
- await statusPromise;
- p.stdout.close();
- p.close();
- }
- },
-});
-
-Deno.test(
- "close server while iterating",
- async (): Promise<void> => {
- const server = serve(":8123");
- const nextWhileClosing = server[Symbol.asyncIterator]().next();
- server.close();
- assertEquals(await nextWhileClosing, { value: undefined, done: true });
-
- const nextAfterClosing = server[Symbol.asyncIterator]().next();
- assertEquals(await nextAfterClosing, { value: undefined, done: true });
- },
-);
-
-Deno.test({
- name: "[http] close server while connection is open",
- async fn(): Promise<void> {
- async function iteratorReq(server: Server): Promise<void> {
- for await (const req of server) {
- await req.respond({ body: new TextEncoder().encode(req.url) });
- }
- }
-
- const server = serve(":8123");
- const p = iteratorReq(server);
- const conn = await Deno.connect({ hostname: "127.0.0.1", port: 8123 });
- await Deno.writeAll(
- conn,
- new TextEncoder().encode("GET /hello HTTP/1.1\r\n\r\n"),
- );
- const res = new Uint8Array(100);
- const nread = await conn.read(res);
- assert(nread !== null);
- const resStr = new TextDecoder().decode(res.subarray(0, nread));
- assertStringIncludes(resStr, "/hello");
- server.close();
- await p;
- // Client connection should still be open, verify that
- // it's visible in resource table.
- const resources = Deno.resources();
- assertEquals(resources[conn.rid], "tcpStream");
- conn.close();
- },
-});
-
-Deno.test({
- name: "respond error closes connection",
- async fn(): Promise<void> {
- const serverRoutine = async (): Promise<void> => {
- const server = serve(":8124");
- for await (const req of server) {
- await assertThrowsAsync(async () => {
- await req.respond({
- status: 12345,
- body: new TextEncoder().encode("Hello World"),
- });
- }, Deno.errors.InvalidData);
- // The connection should be destroyed
- assert(!(req.conn.rid in Deno.resources()));
- server.close();
- }
- };
- const p = serverRoutine();
- const conn = await Deno.connect({
- hostname: "127.0.0.1",
- port: 8124,
- });
- await Deno.writeAll(
- conn,
- new TextEncoder().encode("GET / HTTP/1.1\r\n\r\n"),
- );
- conn.close();
- await p;
- },
-});
-
-Deno.test({
- name: "[http] request error gets 400 response",
- async fn(): Promise<void> {
- const server = serve(":8124");
- const entry = server[Symbol.asyncIterator]().next();
- const conn = await Deno.connect({
- hostname: "127.0.0.1",
- port: 8124,
- });
- await Deno.writeAll(
- conn,
- encode("GET / HTTP/1.1\r\nmalformedHeader\r\n\r\n\r\n\r\n"),
- );
- const responseString = decode(await Deno.readAll(conn));
- assertMatch(
- responseString,
- /^HTTP\/1\.1 400 Bad Request\r\ncontent-length: \d+\r\n\r\n.*\r\n\r\n$/ms,
- );
- conn.close();
- server.close();
- assert((await entry).done);
- },
-});
-
-Deno.test({
- name: "[http] finalizing invalid chunked data closes connection",
- async fn(): Promise<void> {
- const serverRoutine = async (): Promise<void> => {
- const server = serve(":8124");
- for await (const req of server) {
- await req.respond({ status: 200, body: "Hello, world!" });
- break;
- }
- server.close();
- };
- const p = serverRoutine();
- const conn = await Deno.connect({
- hostname: "127.0.0.1",
- port: 8124,
- });
- await Deno.writeAll(
- conn,
- encode(
- "PUT / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\nzzzzzzz\r\nhello",
- ),
- );
- await conn.closeWrite();
- const responseString = decode(await Deno.readAll(conn));
- assertEquals(
- responseString,
- "HTTP/1.1 200 OK\r\ncontent-length: 13\r\n\r\nHello, world!",
- );
- conn.close();
- await p;
- },
-});
-
-Deno.test({
- name: "[http] finalizing chunked unexpected EOF closes connection",
- async fn(): Promise<void> {
- const serverRoutine = async (): Promise<void> => {
- const server = serve(":8124");
- for await (const req of server) {
- await req.respond({ status: 200, body: "Hello, world!" });
- break;
- }
- server.close();
- };
- const p = serverRoutine();
- const conn = await Deno.connect({
- hostname: "127.0.0.1",
- port: 8124,
- });
- await Deno.writeAll(
- conn,
- encode("PUT / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nHello"),
- );
- conn.closeWrite();
- const responseString = decode(await Deno.readAll(conn));
- assertEquals(
- responseString,
- "HTTP/1.1 200 OK\r\ncontent-length: 13\r\n\r\nHello, world!",
- );
- conn.close();
- await p;
- },
-});
-
-Deno.test({
- name:
- "[http] receiving bad request from a closed connection should not throw",
- async fn(): Promise<void> {
- const server = serve(":8124");
- const serverRoutine = async (): Promise<void> => {
- for await (const req of server) {
- await req.respond({ status: 200, body: "Hello, world!" });
- }
- };
- const p = serverRoutine();
- const conn = await Deno.connect({
- hostname: "127.0.0.1",
- port: 8124,
- });
- await Deno.writeAll(
- conn,
- encode([
- // A normal request is required:
- "GET / HTTP/1.1",
- "Host: localhost",
- "",
- // The bad request:
- "GET / HTTP/1.1",
- "Host: localhost",
- "INVALID!HEADER!",
- "",
- "",
- ].join("\r\n")),
- );
- // After sending the two requests, don't receive the reponses.
-
- // Closing the connection now.
- conn.close();
-
- // The server will write responses to the closed connection,
- // the first few `write()` calls will not throws, until the server received
- // the TCP RST. So we need the normal request before the bad request to
- // make the server do a few writes before it writes that `400` response.
-
- // Wait for server to handle requests.
- await delay(10);
-
- server.close();
- await p;
- },
-});
-
-Deno.test({
- name: "serveTLS Invalid Cert",
- fn: async (): Promise<void> => {
- async function iteratorReq(server: Server): Promise<void> {
- for await (const req of server) {
- await req.respond({ body: new TextEncoder().encode("Hello HTTPS") });
- }
- }
- const port = 9122;
- const tlsOptions = {
- hostname: "localhost",
- port,
- certFile: join(testdataDir, "tls/localhost.crt"),
- keyFile: join(testdataDir, "tls/localhost.key"),
- };
- const server = serveTLS(tlsOptions);
- const p = iteratorReq(server);
-
- try {
- // Invalid certificate, connection should throw
- // but should not crash the server
- assertThrowsAsync(
- () =>
- Deno.connectTls({
- hostname: "localhost",
- port,
- // certFile
- }),
- Deno.errors.InvalidData,
- );
-
- // Valid request after invalid
- const conn = await Deno.connectTls({
- hostname: "localhost",
- port,
- certFile: join(testdataDir, "tls/RootCA.pem"),
- });
-
- await Deno.writeAll(
- conn,
- new TextEncoder().encode("GET / HTTP/1.0\r\n\r\n"),
- );
- const res = new Uint8Array(100);
- const nread = await conn.read(res);
- assert(nread !== null);
- conn.close();
- const resStr = new TextDecoder().decode(res.subarray(0, nread));
- assert(resStr.includes("Hello HTTPS"));
- } finally {
- // Stops the sever and allows `p.status()` promise to resolve
- server.close();
- await p;
- }
- },
-});
-
-Deno.test({
- name: "server.serve() should be able to parse IPV4 address",
- fn: (): void => {
- const server = serve("127.0.0.1:8124");
- const expected = {
- hostname: "127.0.0.1",
- port: 8124,
- transport: "tcp",
- };
- assertEquals(server.listener.addr, expected);
- server.close();
- },
-});
-
-Deno.test({
- name: "server._parseAddrFromStr() should be able to parse IPV6 address",
- fn: (): void => {
- const addr = _parseAddrFromStr("[::1]:8124");
- const expected = {
- hostname: "[::1]",
- port: 8124,
- };
- assertEquals(addr, expected);
- },
-});
-
-Deno.test({
- name: "server.serve() should be able to parse IPV6 address",
- fn: (): void => {
- const server = serve("[::1]:8124");
- const expected = {
- hostname: "::1",
- port: 8124,
- transport: "tcp",
- };
- assertEquals(server.listener.addr, expected);
- server.close();
- },
-});
-
-Deno.test({
- name: "server._parseAddrFromStr() port 80",
- fn: (): void => {
- const addr = _parseAddrFromStr(":80");
- assertEquals(addr.port, 80);
- assertEquals(addr.hostname, "0.0.0.0");
- },
-});
diff --git a/std/http/test.ts b/std/http/test.ts
deleted file mode 100644
index 590417055..000000000
--- a/std/http/test.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-import "./mod.ts";
diff --git a/std/http/testdata/% b/std/http/testdata/%
deleted file mode 100644
index e69de29bb..000000000
--- a/std/http/testdata/%
+++ /dev/null
diff --git a/std/http/testdata/file_server_as_library.ts b/std/http/testdata/file_server_as_library.ts
deleted file mode 100644
index cd4bf68db..000000000
--- a/std/http/testdata/file_server_as_library.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { serve } from "../server.ts";
-import { serveFile } from "../file_server.ts";
-
-const server = serve({ port: 8000 });
-
-console.log("Server running...");
-
-for await (const req of server) {
- serveFile(req, "./testdata/hello.html").then((response) => {
- req.respond(response);
- });
-}
diff --git a/std/http/testdata/hello.html b/std/http/testdata/hello.html
deleted file mode 100644
index e69de29bb..000000000
--- a/std/http/testdata/hello.html
+++ /dev/null
diff --git a/std/http/testdata/simple_https_server.ts b/std/http/testdata/simple_https_server.ts
deleted file mode 100644
index 84dfb39ab..000000000
--- a/std/http/testdata/simple_https_server.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-// This is an example of a https server
-import { serveTLS } from "../server.ts";
-
-const tlsOptions = {
- hostname: "localhost",
- port: 4503,
- certFile: "./testdata/tls/localhost.crt",
- keyFile: "./testdata/tls/localhost.key",
-};
-const s = serveTLS(tlsOptions);
-console.log(
- `Simple HTTPS server listening on ${tlsOptions.hostname}:${tlsOptions.port}`,
-);
-const body = new TextEncoder().encode("Hello HTTPS");
-for await (const req of s) {
- req.respond({ body });
-}
diff --git a/std/http/testdata/simple_server.ts b/std/http/testdata/simple_server.ts
deleted file mode 100644
index ff2a0b5ac..000000000
--- a/std/http/testdata/simple_server.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
-// This is an example of a server that responds with an empty body
-import { serve } from "../server.ts";
-
-const addr = "0.0.0.0:4502";
-console.log(`Simple server listening on ${addr}`);
-for await (const req of serve(addr)) {
- req.respond({});
-}
diff --git a/std/http/testdata/test file.txt b/std/http/testdata/test file.txt
deleted file mode 100644
index e69de29bb..000000000
--- a/std/http/testdata/test file.txt
+++ /dev/null
diff --git a/std/http/testdata/tls b/std/http/testdata/tls
deleted file mode 120000
index f6fd22ed8..000000000
--- a/std/http/testdata/tls
+++ /dev/null
@@ -1 +0,0 @@
-../../../cli/tests/tls \ No newline at end of file