summaryrefslogtreecommitdiff
path: root/std/http
diff options
context:
space:
mode:
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