summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--std/http/cookie_test.ts13
-rwxr-xr-xstd/http/file_server.ts8
-rw-r--r--std/http/file_server_test.ts1
-rw-r--r--std/http/io.ts175
-rw-r--r--std/http/io_test.ts292
-rw-r--r--std/http/mock.ts26
-rw-r--r--std/http/racing_server_test.ts5
-rw-r--r--std/http/server.ts194
-rw-r--r--std/http/server_test.ts644
-rw-r--r--std/ws/mod.ts2
10 files changed, 668 insertions, 692 deletions
diff --git a/std/http/cookie_test.ts b/std/http/cookie_test.ts
index 5e29fede7..8ab862bb3 100644
--- a/std/http/cookie_test.ts
+++ b/std/http/cookie_test.ts
@@ -2,9 +2,10 @@
import { ServerRequest, Response } from "./server.ts";
import { getCookies, delCookie, setCookie } from "./cookie.ts";
import { assert, assertEquals } from "../testing/asserts.ts";
+const { test } = Deno;
-Deno.test({
- name: "[HTTP] Cookie parser",
+test({
+ name: "Cookie parser",
fn(): void {
const req = new ServerRequest();
req.headers = new Headers();
@@ -31,8 +32,8 @@ Deno.test({
}
});
-Deno.test({
- name: "[HTTP] Cookie Delete",
+test({
+ name: "Cookie Delete",
fn(): void {
const res: Response = {};
delCookie(res, "deno");
@@ -43,8 +44,8 @@ Deno.test({
}
});
-Deno.test({
- name: "[HTTP] Cookie Set",
+test({
+ name: "Cookie Set",
fn(): void {
const res: Response = {};
diff --git a/std/http/file_server.ts b/std/http/file_server.ts
index da2b0b5a2..e1fe14fcf 100755
--- a/std/http/file_server.ts
+++ b/std/http/file_server.ts
@@ -8,14 +8,10 @@
const { args, stat, readDir, open, exit } = Deno;
import { posix } from "../path/mod.ts";
-import {
- listenAndServe,
- ServerRequest,
- setContentLength,
- Response
-} from "./server.ts";
+import { listenAndServe, ServerRequest, Response } from "./server.ts";
import { parse } from "../flags/mod.ts";
import { assert } from "../testing/asserts.ts";
+import { setContentLength } from "./io.ts";
interface EntryInfo {
mode: string;
diff --git a/std/http/file_server_test.ts b/std/http/file_server_test.ts
index 7c60d5f56..a023aca3d 100644
--- a/std/http/file_server_test.ts
+++ b/std/http/file_server_test.ts
@@ -3,7 +3,6 @@ import { assert, assertEquals, assertStrContains } from "../testing/asserts.ts";
import { BufReader } from "../io/bufio.ts";
import { TextProtoReader } from "../textproto/mod.ts";
const { test } = Deno;
-
let fileServer: Deno.Process;
async function startFileServer(): Promise<void> {
diff --git a/std/http/io.ts b/std/http/io.ts
index a51fada54..477bab831 100644
--- a/std/http/io.ts
+++ b/std/http/io.ts
@@ -2,6 +2,8 @@ import { BufReader, UnexpectedEOFError, BufWriter } from "../io/bufio.ts";
import { TextProtoReader } from "../textproto/mod.ts";
import { assert } from "../testing/asserts.ts";
import { encoder } from "../strings/mod.ts";
+import { ServerRequest, Response } from "./server.ts";
+import { STATUS_TEXT } from "./http_status.ts";
export function emptyReader(): Deno.Reader {
return {
@@ -211,3 +213,176 @@ export async function writeTrailers(
await writer.write(encoder.encode("\r\n"));
await writer.flush();
}
+
+export function setContentLength(r: Response): void {
+ if (!r.headers) {
+ r.headers = new Headers();
+ }
+
+ if (r.body) {
+ if (!r.headers.has("content-length")) {
+ // typeof r.body === "string" handled in writeResponse.
+ if (r.body instanceof Uint8Array) {
+ const bodyLength = r.body.byteLength;
+ r.headers.set("content-length", bodyLength.toString());
+ } else {
+ r.headers.set("transfer-encoding", "chunked");
+ }
+ }
+ }
+}
+
+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 Error("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`;
+
+ setContentLength(r);
+ assert(r.headers != null);
+ const headers = r.headers;
+
+ 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(writer, r.body);
+ 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, true).
+ * 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
+ const digitReg = /^\d+$/; // test if string is only digit
+
+ if (!vers.startsWith("HTTP/")) {
+ break;
+ }
+
+ const dot = vers.indexOf(".");
+ if (dot < 0) {
+ break;
+ }
+
+ const majorStr = vers.substring(vers.indexOf("/") + 1, dot);
+ const major = parseInt(majorStr);
+ if (
+ !digitReg.test(majorStr) ||
+ isNaN(major) ||
+ major < 0 ||
+ major > Big
+ ) {
+ break;
+ }
+
+ const minorStr = vers.substring(dot + 1);
+ const minor = parseInt(minorStr);
+ if (
+ !digitReg.test(minorStr) ||
+ isNaN(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 | Deno.EOF> {
+ const tp = new TextProtoReader(bufr);
+ const firstLine = await tp.readLine(); // e.g. GET /index.html HTTP/1.0
+ if (firstLine === Deno.EOF) return Deno.EOF;
+ const headers = await tp.readMIMEHeader();
+ if (headers === Deno.EOF) throw new UnexpectedEOFError();
+
+ const req = new ServerRequest();
+ req.conn = conn;
+ req.r = bufr;
+ [req.method, req.url, req.proto] = firstLine.split(" ", 3);
+ [req.protoMinor, req.protoMajor] = 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
index 7e7701596..acaa882f7 100644
--- a/std/http/io_test.ts
+++ b/std/http/io_test.ts
@@ -1,13 +1,26 @@
import {
AssertionError,
assertThrowsAsync,
- assertEquals
+ assertEquals,
+ assert,
+ assertNotEOF,
+ assertNotEquals
} from "../testing/asserts.ts";
-import { bodyReader, writeTrailers, readTrailers } from "./io.ts";
+import {
+ bodyReader,
+ writeTrailers,
+ readTrailers,
+ parseHTTPVersion,
+ readRequest,
+ writeResponse
+} from "./io.ts";
import { encode, decode } from "../strings/mod.ts";
-import { BufReader } from "../io/bufio.ts";
+import { BufReader, UnexpectedEOFError, ReadLineResult } from "../io/bufio.ts";
import { chunkedBodyReader } from "./io.ts";
-const { test, Buffer } = Deno;
+import { ServerRequest, Response } from "./server.ts";
+import { StringReader } from "../io/readers.ts";
+import { mockConn } from "./mock.ts";
+const { Buffer, test } = Deno;
test("bodyReader", async () => {
const text = "Hello, Deno";
@@ -165,3 +178,274 @@ test("writeTrailer should throw", async () => {
"Not trailer"
);
});
+
+// Ported from https://github.com/golang/go/blob/f5c43b9/src/net/http/request_test.go#L535-L565
+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 }
+ ];
+ 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);
+ }
+ }
+});
+
+test(async function writeUint8ArrayResponse(): 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;
+ r = assertNotEOF(await reader.readLine());
+ assertEquals(decoder.decode(r.line), "HTTP/1.1 200 OK");
+ assertEquals(r.more, false);
+
+ r = assertNotEOF(await reader.readLine());
+ assertEquals(decoder.decode(r.line), `content-length: ${shortText.length}`);
+ assertEquals(r.more, false);
+
+ r = assertNotEOF(await reader.readLine());
+ assertEquals(r.line.byteLength, 0);
+ assertEquals(r.more, false);
+
+ r = assertNotEOF(await reader.readLine());
+ assertEquals(decoder.decode(r.line), shortText);
+ assertEquals(r.more, false);
+
+ const eof = await reader.readLine();
+ assertEquals(eof, Deno.EOF);
+});
+
+test(async function writeStringResponse(): 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;
+ r = assertNotEOF(await reader.readLine());
+ assertEquals(decoder.decode(r.line), "HTTP/1.1 200 OK");
+ assertEquals(r.more, false);
+
+ r = assertNotEOF(await reader.readLine());
+ assertEquals(decoder.decode(r.line), `content-length: ${body.length}`);
+ assertEquals(r.more, false);
+
+ r = assertNotEOF(await reader.readLine());
+ assertEquals(r.line.byteLength, 0);
+ assertEquals(r.more, false);
+
+ r = assertNotEOF(await reader.readLine());
+ assertEquals(decoder.decode(r.line), body);
+ assertEquals(r.more, false);
+
+ const eof = await reader.readLine();
+ assertEquals(eof, Deno.EOF);
+});
+
+test(async function writeStringReaderResponse(): 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;
+ r = assertNotEOF(await reader.readLine());
+ assertEquals(decoder.decode(r.line), "HTTP/1.1 200 OK");
+ assertEquals(r.more, false);
+
+ r = assertNotEOF(await reader.readLine());
+ assertEquals(decoder.decode(r.line), "transfer-encoding: chunked");
+ assertEquals(r.more, false);
+
+ r = assertNotEOF(await reader.readLine());
+ assertEquals(r.line.byteLength, 0);
+ assertEquals(r.more, false);
+
+ r = assertNotEOF(await reader.readLine());
+ assertEquals(decoder.decode(r.line), shortText.length.toString());
+ assertEquals(r.more, false);
+
+ r = assertNotEOF(await reader.readLine());
+ assertEquals(decoder.decode(r.line), shortText);
+ assertEquals(r.more, false);
+
+ r = assertNotEOF(await reader.readLine());
+ assertEquals(decoder.decode(r.line), "0");
+ assertEquals(r.more, false);
+});
+
+test("writeResponse with trailer", async () => {
+ const w = new 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 = w.toString();
+ 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);
+});
+
+test(async function readRequestError(): 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
+test(async function testReadRequestError(): 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: UnexpectedEOFError
+ },
+ { in: "", err: Deno.EOF },
+ {
+ 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 | Deno.EOF | undefined;
+ try {
+ req = await readRequest(mockConn(), reader);
+ } catch (e) {
+ err = e;
+ }
+ if (test.err === Deno.EOF) {
+ assertEquals(req, Deno.EOF);
+ } else if (typeof test.err === "string") {
+ assertEquals(err.message, test.err);
+ } else if (test.err) {
+ assert(err instanceof (test.err as typeof UnexpectedEOFError));
+ } else {
+ assert(req instanceof ServerRequest);
+ assert(test.headers);
+ assertEquals(err, undefined);
+ assertNotEquals(req, Deno.EOF);
+ for (const h of test.headers) {
+ assertEquals(req.headers.get(h.key), h.value);
+ }
+ }
+ }
+});
diff --git a/std/http/mock.ts b/std/http/mock.ts
new file mode 100644
index 000000000..3a4eeed82
--- /dev/null
+++ b/std/http/mock.ts
@@ -0,0 +1,26 @@
+/** 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,
+ closeRead: (): void => {},
+ closeWrite: (): void => {},
+ read: async (): Promise<number | Deno.EOF> => {
+ return 0;
+ },
+ write: async (): Promise<number> => {
+ return -1;
+ },
+ close: (): void => {},
+ ...base
+ };
+}
diff --git a/std/http/racing_server_test.ts b/std/http/racing_server_test.ts
index 07df92bae..0f228419d 100644
--- a/std/http/racing_server_test.ts
+++ b/std/http/racing_server_test.ts
@@ -1,8 +1,7 @@
-const { connect, run } = Deno;
-
import { assert, assertEquals } from "../testing/asserts.ts";
import { BufReader, BufWriter } from "../io/bufio.ts";
import { TextProtoReader } from "../textproto/mod.ts";
+const { connect, run, test } = Deno;
let server: Deno.Process;
async function startServer(): Promise<void> {
@@ -59,7 +58,7 @@ content-length: 6
Step7
`;
-Deno.test(async function serverPipelineRace(): Promise<void> {
+test(async function serverPipelineRace(): Promise<void> {
await startServer();
const conn = await connect({ port: 4501 });
diff --git a/std/http/server.ts b/std/http/server.ts
index e7d6bd598..6e26e8456 100644
--- a/std/http/server.ts
+++ b/std/http/server.ts
@@ -1,91 +1,19 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
-const { listen, listenTLS, copy } = Deno;
-type Listener = Deno.Listener;
-type Conn = Deno.Conn;
-type Reader = Deno.Reader;
-type Writer = Deno.Writer;
-import { BufReader, BufWriter, UnexpectedEOFError } from "../io/bufio.ts";
-import { TextProtoReader } from "../textproto/mod.ts";
-import { STATUS_TEXT } from "./http_status.ts";
+import { BufReader, BufWriter } from "../io/bufio.ts";
import { assert } from "../testing/asserts.ts";
import { deferred, Deferred, MuxAsyncIterator } from "../util/async.ts";
import {
bodyReader,
chunkedBodyReader,
- writeChunkedBody,
- writeTrailers,
- emptyReader
+ emptyReader,
+ writeResponse,
+ readRequest
} from "./io.ts";
-
-const encoder = new TextEncoder();
-
-export function setContentLength(r: Response): void {
- if (!r.headers) {
- r.headers = new Headers();
- }
-
- if (r.body) {
- if (!r.headers.has("content-length")) {
- // typeof r.body === "string" handled in writeResponse.
- if (r.body instanceof Uint8Array) {
- const bodyLength = r.body.byteLength;
- r.headers.set("content-length", bodyLength.toString());
- } else {
- r.headers.set("transfer-encoding", "chunked");
- }
- }
- }
-}
-
-export async function writeResponse(w: 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 Error("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`;
-
- setContentLength(r);
- assert(r.headers != null);
- const headers = r.headers;
-
- 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 copy(writer, r.body);
- 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();
-}
+import { encode } from "../strings/mod.ts";
+import Listener = Deno.Listener;
+import Conn = Deno.Conn;
+import Reader = Deno.Reader;
+const { listen, listenTLS } = Deno;
export class ServerRequest {
url!: string;
@@ -194,108 +122,6 @@ export class ServerRequest {
}
}
-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"
- );
- }
- }
-}
-
-/**
- * ParseHTTPVersion parses a HTTP version string.
- * "HTTP/1.0" returns (1, 0, true).
- * 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
- const digitReg = /^\d+$/; // test if string is only digit
-
- if (!vers.startsWith("HTTP/")) {
- break;
- }
-
- const dot = vers.indexOf(".");
- if (dot < 0) {
- break;
- }
-
- const majorStr = vers.substring(vers.indexOf("/") + 1, dot);
- const major = parseInt(majorStr);
- if (
- !digitReg.test(majorStr) ||
- isNaN(major) ||
- major < 0 ||
- major > Big
- ) {
- break;
- }
-
- const minorStr = vers.substring(dot + 1);
- const minor = parseInt(minorStr);
- if (
- !digitReg.test(minorStr) ||
- isNaN(minor) ||
- minor < 0 ||
- minor > Big
- ) {
- break;
- }
-
- return [major, minor];
- }
- }
-
- throw new Error(`malformed HTTP version ${vers}`);
-}
-
-export async function readRequest(
- conn: Conn,
- bufr: BufReader
-): Promise<ServerRequest | Deno.EOF> {
- const tp = new TextProtoReader(bufr);
- const firstLine = await tp.readLine(); // e.g. GET /index.html HTTP/1.0
- if (firstLine === Deno.EOF) return Deno.EOF;
- const headers = await tp.readMIMEHeader();
- if (headers === Deno.EOF) throw new UnexpectedEOFError();
-
- const req = new ServerRequest();
- req.conn = conn;
- req.r = bufr;
- [req.method, req.url, req.proto] = firstLine.split(" ", 3);
- [req.protoMinor, req.protoMajor] = parseHTTPVersion(req.proto);
- req.headers = headers;
- fixLength(req);
- return req;
-}
-
export class Server implements AsyncIterable<ServerRequest> {
private closing = false;
@@ -349,7 +175,7 @@ export class Server implements AsyncIterable<ServerRequest> {
try {
await writeResponse(req.w, {
status: 400,
- body: encoder.encode(`${err.message}\r\n\r\n`)
+ body: encode(`${err.message}\r\n\r\n`)
});
} catch (_) {
// The connection is destroyed.
diff --git a/std/http/server_test.ts b/std/http/server_test.ts
index 70ce5f2f1..fec487925 100644
--- a/std/http/server_test.ts
+++ b/std/http/server_test.ts
@@ -5,65 +5,21 @@
// Ported from
// https://github.com/golang/go/blob/master/src/net/http/responsewrite_test.go
-const { Buffer, test } = Deno;
import { TextProtoReader } from "../textproto/mod.ts";
-import {
- assert,
- assertEquals,
- assertNotEquals,
- assertNotEOF
-} from "../testing/asserts.ts";
-import {
- Response,
- ServerRequest,
- writeResponse,
- serve,
- readRequest,
- parseHTTPVersion
-} from "./server.ts";
-import {
- BufReader,
- BufWriter,
- ReadLineResult,
- UnexpectedEOFError
-} from "../io/bufio.ts";
+import { assert, assertEquals, assertNotEOF } from "../testing/asserts.ts";
+import { Response, ServerRequest, serve } from "./server.ts";
+import { BufReader, BufWriter } from "../io/bufio.ts";
import { delay, deferred } from "../util/async.ts";
-import { StringReader } from "../io/readers.ts";
-import { encode } from "../strings/mod.ts";
+import { encode, decode } from "../strings/mod.ts";
+import { mockConn } from "./mock.ts";
+
+const { Buffer, test } = Deno;
interface ResponseTest {
response: Response;
raw: string;
}
-const enc = new TextEncoder();
-const dec = new TextDecoder();
-
-type Handler = () => void;
-
-const mockConn = (): Deno.Conn => ({
- localAddr: {
- transport: "tcp",
- hostname: "",
- port: 0
- },
- remoteAddr: {
- transport: "tcp",
- hostname: "",
- port: 0
- },
- rid: -1,
- closeRead: (): void => {},
- closeWrite: (): void => {},
- read: async (): Promise<number | Deno.EOF> => {
- return 0;
- },
- write: async (): Promise<number> => {
- return -1;
- },
- close: (): void => {}
-});
-
const responseTests: ResponseTest[] = [
// Default response
{
@@ -112,7 +68,7 @@ test(async function requestContentLength(): Promise<void> {
const req = new ServerRequest();
req.headers = new Headers();
req.headers.set("content-length", "5");
- const buf = new Buffer(enc.encode("Hello"));
+ const buf = new Buffer(encode("Hello"));
req.r = new BufReader(buf);
assertEquals(req.contentLength, 5);
}
@@ -134,7 +90,7 @@ test(async function requestContentLength(): Promise<void> {
chunkOffset += chunkSize;
}
chunksData += "0\r\n\r\n";
- const buf = new Buffer(enc.encode(chunksData));
+ const buf = new Buffer(encode(chunksData));
req.r = new BufReader(buf);
assertEquals(req.contentLength, null);
}
@@ -164,9 +120,9 @@ test(async function requestBodyWithContentLength(): Promise<void> {
const req = new ServerRequest();
req.headers = new Headers();
req.headers.set("content-length", "5");
- const buf = new Buffer(enc.encode("Hello"));
+ const buf = new Buffer(encode("Hello"));
req.r = new BufReader(buf);
- const body = dec.decode(await Deno.readAll(req.body));
+ const body = decode(await Deno.readAll(req.body));
assertEquals(body, "Hello");
}
@@ -176,9 +132,9 @@ test(async function requestBodyWithContentLength(): Promise<void> {
const req = new ServerRequest();
req.headers = new Headers();
req.headers.set("Content-Length", "5000");
- const buf = new Buffer(enc.encode(longText));
+ const buf = new Buffer(encode(longText));
req.r = new BufReader(buf);
- const body = dec.decode(await Deno.readAll(req.body));
+ const body = decode(await Deno.readAll(req.body));
assertEquals(body, longText);
}
// Handler ignored to consume body
@@ -246,9 +202,9 @@ test(async function requestBodyWithTransferEncoding(): Promise<void> {
chunkOffset += chunkSize;
}
chunksData += "0\r\n\r\n";
- const buf = new Buffer(enc.encode(chunksData));
+ const buf = new Buffer(encode(chunksData));
req.r = new BufReader(buf);
- const body = dec.decode(await Deno.readAll(req.body));
+ const body = decode(await Deno.readAll(req.body));
assertEquals(body, shortText);
}
@@ -270,9 +226,9 @@ test(async function requestBodyWithTransferEncoding(): Promise<void> {
chunkOffset += chunkSize;
}
chunksData += "0\r\n\r\n";
- const buf = new Buffer(enc.encode(chunksData));
+ const buf = new Buffer(encode(chunksData));
req.r = new BufReader(buf);
- const body = dec.decode(await Deno.readAll(req.body));
+ const body = decode(await Deno.readAll(req.body));
assertEquals(body, longText);
}
});
@@ -283,14 +239,14 @@ test(async function requestBodyReaderWithContentLength(): Promise<void> {
const req = new ServerRequest();
req.headers = new Headers();
req.headers.set("content-length", "" + shortText.length);
- const buf = new Buffer(enc.encode(shortText));
+ const buf = new 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);
assertNotEOF(nread);
- const s = dec.decode(readBuf.subarray(0, nread as number));
+ const s = decode(readBuf.subarray(0, nread as number));
assertEquals(shortText.substr(offset, nread as number), s);
offset += nread as number;
}
@@ -304,14 +260,14 @@ test(async function requestBodyReaderWithContentLength(): Promise<void> {
const req = new ServerRequest();
req.headers = new Headers();
req.headers.set("Content-Length", "5000");
- const buf = new Buffer(enc.encode(longText));
+ const buf = new 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);
assertNotEOF(nread);
- const s = dec.decode(readBuf.subarray(0, nread as number));
+ const s = decode(readBuf.subarray(0, nread as number));
assertEquals(longText.substr(offset, nread as number), s);
offset += nread as number;
}
@@ -338,14 +294,14 @@ test(async function requestBodyReaderWithTransferEncoding(): Promise<void> {
chunkOffset += chunkSize;
}
chunksData += "0\r\n\r\n";
- const buf = new Buffer(enc.encode(chunksData));
+ const buf = new 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);
assertNotEOF(nread);
- const s = dec.decode(readBuf.subarray(0, nread as number));
+ const s = decode(readBuf.subarray(0, nread as number));
assertEquals(shortText.substr(offset, nread as number), s);
offset += nread as number;
}
@@ -371,14 +327,14 @@ test(async function requestBodyReaderWithTransferEncoding(): Promise<void> {
chunkOffset += chunkSize;
}
chunksData += "0\r\n\r\n";
- const buf = new Buffer(enc.encode(chunksData));
+ const buf = new 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);
assertNotEOF(nread);
- const s = dec.decode(readBuf.subarray(0, nread as number));
+ const s = decode(readBuf.subarray(0, nread as number));
assertEquals(longText.substr(offset, nread as number), s);
offset += nread as number;
}
@@ -387,382 +343,99 @@ test(async function requestBodyReaderWithTransferEncoding(): Promise<void> {
}
});
-test(async function writeUint8ArrayResponse(): 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;
- r = assertNotEOF(await reader.readLine());
- assertEquals(decoder.decode(r.line), "HTTP/1.1 200 OK");
- assertEquals(r.more, false);
-
- r = assertNotEOF(await reader.readLine());
- assertEquals(decoder.decode(r.line), `content-length: ${shortText.length}`);
- assertEquals(r.more, false);
-
- r = assertNotEOF(await reader.readLine());
- assertEquals(r.line.byteLength, 0);
- assertEquals(r.more, false);
-
- r = assertNotEOF(await reader.readLine());
- assertEquals(decoder.decode(r.line), shortText);
- assertEquals(r.more, false);
-
- const eof = await reader.readLine();
- assertEquals(eof, Deno.EOF);
-});
-
-test(async function writeStringResponse(): 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;
- r = assertNotEOF(await reader.readLine());
- assertEquals(decoder.decode(r.line), "HTTP/1.1 200 OK");
- assertEquals(r.more, false);
-
- r = assertNotEOF(await reader.readLine());
- assertEquals(decoder.decode(r.line), `content-length: ${body.length}`);
- assertEquals(r.more, false);
-
- r = assertNotEOF(await reader.readLine());
- assertEquals(r.line.byteLength, 0);
- assertEquals(r.more, false);
-
- r = assertNotEOF(await reader.readLine());
- assertEquals(decoder.decode(r.line), body);
- assertEquals(r.more, false);
-
- const eof = await reader.readLine();
- assertEquals(eof, Deno.EOF);
-});
-
-test(async function writeStringReaderResponse(): 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;
- r = assertNotEOF(await reader.readLine());
- assertEquals(decoder.decode(r.line), "HTTP/1.1 200 OK");
- assertEquals(r.more, false);
-
- r = assertNotEOF(await reader.readLine());
- assertEquals(decoder.decode(r.line), "transfer-encoding: chunked");
- assertEquals(r.more, false);
-
- r = assertNotEOF(await reader.readLine());
- assertEquals(r.line.byteLength, 0);
- assertEquals(r.more, false);
-
- r = assertNotEOF(await reader.readLine());
- assertEquals(decoder.decode(r.line), shortText.length.toString());
- assertEquals(r.more, false);
-
- r = assertNotEOF(await reader.readLine());
- assertEquals(decoder.decode(r.line), shortText);
- assertEquals(r.more, false);
-
- r = assertNotEOF(await reader.readLine());
- assertEquals(decoder.decode(r.line), "0");
- assertEquals(r.more, false);
-});
-
-test("writeResponse with trailer", async () => {
- const w = new 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" })
+test("destroyed connection", async (): Promise<void> => {
+ // Runs a simple server as another process
+ const p = Deno.run({
+ args: [Deno.execPath(), "--allow-net", "http/testdata/simple_server.ts"],
+ stdout: "piped"
});
- const ret = w.toString();
- 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);
-});
-test(async function readRequestError(): 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
-test(async function testReadRequestError(): 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: UnexpectedEOFError
- },
- { in: "", err: Deno.EOF },
- {
- 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 | Deno.EOF | undefined;
- try {
- req = await readRequest(mockConn(), reader);
- } catch (e) {
- err = e;
- }
- if (test.err === Deno.EOF) {
- assertEquals(req, Deno.EOF);
- } else if (typeof test.err === "string") {
- assertEquals(err.message, test.err);
- } else if (test.err) {
- assert(err instanceof (test.err as typeof UnexpectedEOFError));
- } else {
- assert(req instanceof ServerRequest);
- assert(test.headers);
- assertEquals(err, undefined);
- assertNotEquals(req, Deno.EOF);
- for (const h of test.headers) {
- assertEquals(req.headers.get(h.key), h.value);
- }
- }
+ const r = new TextProtoReader(new BufReader(p.stdout!));
+ const s = await r.readLine();
+ assert(s !== Deno.EOF && s.includes("server listening"));
+
+ let serverIsRunning = true;
+ p.status()
+ .then((): void => {
+ serverIsRunning = false;
+ })
+ .catch((_): void => {}); // Ignores the error when closing the process.
+
+ 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.
+ p.close();
}
});
-// Ported from https://github.com/golang/go/blob/f5c43b9/src/net/http/request_test.go#L535-L565
-test({
- name: "[http] parseHttpVersion",
- fn(): 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 }
- ];
- 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);
- }
- }
- }
-});
-
-test({
- name: "[http] destroyed connection",
- async fn(): Promise<void> {
- // Runs a simple server as another process
- const p = Deno.run({
- args: [Deno.execPath(), "--allow-net", "http/testdata/simple_server.ts"],
- stdout: "piped"
- });
-
- try {
- const r = new TextProtoReader(new BufReader(p.stdout!));
- const s = await r.readLine();
- assert(s !== Deno.EOF && s.includes("server listening"));
-
- let serverIsRunning = true;
- p.status()
- .then((): void => {
- serverIsRunning = false;
- })
- .catch((_): void => {}); // Ignores the error when closing the process.
-
- 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.
- p.close();
- }
- }
-});
+test("serveTLS", async (): Promise<void> => {
+ // Runs a simple server as another process
+ const p = Deno.run({
+ args: [
+ Deno.execPath(),
+ "--allow-net",
+ "--allow-read",
+ "http/testdata/simple_https_server.ts"
+ ],
+ stdout: "piped"
+ });
-test({
- name: "[http] serveTLS",
- async fn(): Promise<void> {
- // Runs a simple server as another process
- const p = Deno.run({
- args: [
- Deno.execPath(),
- "--allow-net",
- "--allow-read",
- "http/testdata/simple_https_server.ts"
- ],
- stdout: "piped"
+ try {
+ const r = new TextProtoReader(new BufReader(p.stdout!));
+ const s = await r.readLine();
+ assert(
+ s !== Deno.EOF && s.includes("server listening"),
+ "server must be started"
+ );
+
+ let serverIsRunning = true;
+ p.status()
+ .then((): void => {
+ serverIsRunning = false;
+ })
+ .catch((_): void => {}); // Ignores the error when closing the process.
+
+ // Requests to the server and immediately closes the connection
+ const conn = await Deno.connectTLS({
+ hostname: "localhost",
+ port: 4503,
+ certFile: "http/testdata/tls/RootCA.pem"
});
-
- try {
- const r = new TextProtoReader(new BufReader(p.stdout!));
- const s = await r.readLine();
- assert(
- s !== Deno.EOF && s.includes("server listening"),
- "server must be started"
- );
-
- let serverIsRunning = true;
- p.status()
- .then((): void => {
- serverIsRunning = false;
- })
- .catch((_): void => {}); // Ignores the error when closing the process.
-
- // Requests to the server and immediately closes the connection
- const conn = await Deno.connectTLS({
- hostname: "localhost",
- port: 4503,
- certFile: "http/testdata/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 = assertNotEOF(await conn.read(res));
- conn.close();
- const resStr = new TextDecoder().decode(res.subarray(0, nread));
- assert(resStr.includes("Hello HTTPS"));
- assert(serverIsRunning);
- } finally {
- // Stops the sever.
- p.close();
- }
+ await Deno.writeAll(
+ conn,
+ new TextEncoder().encode("GET / HTTP/1.0\r\n\r\n")
+ );
+ const res = new Uint8Array(100);
+ const nread = assertNotEOF(await conn.read(res));
+ conn.close();
+ const resStr = new TextDecoder().decode(res.subarray(0, nread));
+ assert(resStr.includes("Hello HTTPS"));
+ assert(serverIsRunning);
+ } finally {
+ // Stops the sever.
+ p.close();
}
});
-test({
- name: "[http] close server while iterating",
- async fn(): Promise<void> {
- const server = serve(":8123");
- const nextWhileClosing = server[Symbol.asyncIterator]().next();
- server.close();
- assertEquals(await nextWhileClosing, { value: undefined, done: true });
+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 });
- }
+ const nextAfterClosing = server[Symbol.asyncIterator]().next();
+ assertEquals(await nextAfterClosing, { value: undefined, done: true });
});
// TODO(kevinkassimo): create a test that works on Windows.
@@ -773,60 +446,57 @@ test({
// We need to find a way to similarly trigger an error on Windows so that
// we can test if connection is closed.
if (Deno.build.os !== "win") {
- test({
- name: "[http] respond error handling",
- async fn(): Promise<void> {
- const connClosedPromise = deferred();
- const serverRoutine = async (): Promise<void> => {
- let reqCount = 0;
- const server = serve(":8124");
- // @ts-ignore
- const serverRid = server.listener["rid"];
- let connRid = -1;
- for await (const req of server) {
- connRid = req.conn.rid;
- reqCount++;
- await Deno.readAll(req.body);
- await connClosedPromise;
- try {
- await req.respond({
- body: new TextEncoder().encode("Hello World")
- });
- await delay(100);
- req.done = deferred();
- // This duplicate respond is to ensure we get a write failure from the
- // other side. Our client would enter CLOSE_WAIT stage after close(),
- // meaning first server .send (.respond) after close would still work.
- // However, a second send would fail under RST, which is similar
- // to the scenario where a failure happens during .respond
- await req.respond({
- body: new TextEncoder().encode("Hello World")
- });
- } catch {
- break;
- }
+ test("respond error handling", async (): Promise<void> => {
+ const connClosedPromise = deferred();
+ const serverRoutine = async (): Promise<void> => {
+ let reqCount = 0;
+ const server = serve(":8124");
+ // @ts-ignore
+ const serverRid = server.listener["rid"];
+ let connRid = -1;
+ for await (const req of server) {
+ connRid = req.conn.rid;
+ reqCount++;
+ await Deno.readAll(req.body);
+ await connClosedPromise;
+ try {
+ await req.respond({
+ body: new TextEncoder().encode("Hello World")
+ });
+ await delay(100);
+ req.done = deferred();
+ // This duplicate respond is to ensure we get a write failure from the
+ // other side. Our client would enter CLOSE_WAIT stage after close(),
+ // meaning first server .send (.respond) after close would still work.
+ // However, a second send would fail under RST, which is similar
+ // to the scenario where a failure happens during .respond
+ await req.respond({
+ body: new TextEncoder().encode("Hello World")
+ });
+ } catch {
+ break;
}
- server.close();
- const resources = Deno.resources();
- assert(reqCount === 1);
- // Server should be gone
- assert(!(serverRid in resources));
- // The connection should be destroyed
- assert(!(connRid in resources));
- };
- 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(); // abruptly closing connection before response.
- // conn on server side enters CLOSE_WAIT state.
- connClosedPromise.resolve();
- await p;
- }
+ }
+ server.close();
+ const resources = Deno.resources();
+ assert(reqCount === 1);
+ // Server should be gone
+ assert(!(serverRid in resources));
+ // The connection should be destroyed
+ assert(!(connRid in resources));
+ };
+ 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(); // abruptly closing connection before response.
+ // conn on server side enters CLOSE_WAIT state.
+ connClosedPromise.resolve();
+ await p;
});
}
diff --git a/std/ws/mod.ts b/std/ws/mod.ts
index 3128a88b5..7a6e14a13 100644
--- a/std/ws/mod.ts
+++ b/std/ws/mod.ts
@@ -5,7 +5,7 @@ import { hasOwnProperty } from "../util/has_own_property.ts";
import { BufReader, BufWriter, UnexpectedEOFError } from "../io/bufio.ts";
import { readLong, readShort, sliceLongToBytes } from "../io/ioutil.ts";
import { Sha1 } from "./sha1.ts";
-import { writeResponse } from "../http/server.ts";
+import { writeResponse } from "../http/io.ts";
import { TextProtoReader } from "../textproto/mod.ts";
import { Deferred, deferred } from "../util/async.ts";
import { assertNotEOF } from "../testing/asserts.ts";