summaryrefslogtreecommitdiff
path: root/std/http
diff options
context:
space:
mode:
Diffstat (limited to 'std/http')
-rw-r--r--std/http/server.ts62
-rw-r--r--std/http/server_test.ts92
2 files changed, 140 insertions, 14 deletions
diff --git a/std/http/server.ts b/std/http/server.ts
index 0bd5d06b5..b8a41379f 100644
--- a/std/http/server.ts
+++ b/std/http/server.ts
@@ -12,14 +12,6 @@ import { deferred, Deferred, MuxAsyncIterator } from "../util/async.ts";
const encoder = new TextEncoder();
-function bufWriter(w: Writer): BufWriter {
- if (w instanceof BufWriter) {
- return w;
- } else {
- return new BufWriter(w);
- }
-}
-
export function setContentLength(r: Response): void {
if (!r.headers) {
r.headers = new Headers();
@@ -30,16 +22,16 @@ export function setContentLength(r: Response): void {
// typeof r.body === "string" handled in writeResponse.
if (r.body instanceof Uint8Array) {
const bodyLength = r.body.byteLength;
- r.headers.append("Content-Length", bodyLength.toString());
+ r.headers.set("content-length", bodyLength.toString());
} else {
- r.headers.append("Transfer-Encoding", "chunked");
+ r.headers.set("transfer-encoding", "chunked");
}
}
}
}
async function writeChunkedBody(w: Writer, r: Reader): Promise<void> {
- const writer = bufWriter(w);
+ const writer = BufWriter.create(w);
for await (const chunk of toAsyncIterator(r)) {
if (chunk.byteLength <= 0) continue;
@@ -53,13 +45,54 @@ async function writeChunkedBody(w: Writer, r: Reader): Promise<void> {
const endChunk = encoder.encode("0\r\n\r\n");
await writer.write(endChunk);
}
+const kProhibitedTrailerHeaders = [
+ "transfer-encoding",
+ "content-length",
+ "trailer"
+];
+
+/** write trailer headers to writer. it mostly should be called after writeResponse */
+export async function writeTrailers(
+ w: Writer,
+ headers: Headers,
+ trailers: Headers
+): Promise<void> {
+ const trailer = headers.get("trailer");
+ if (trailer === null) {
+ throw new Error('response headers must have "trailer" header field');
+ }
+ const transferEncoding = headers.get("transfer-encoding");
+ if (transferEncoding === null || !transferEncoding.match(/^chunked/)) {
+ throw new Error(
+ `trailer headers is only allowed for "transfer-encoding: chunked": got "${transferEncoding}"`
+ );
+ }
+ const writer = BufWriter.create(w);
+ const trailerHeaderFields = trailer
+ .split(",")
+ .map(s => s.trim().toLowerCase());
+ for (const f of trailerHeaderFields) {
+ assert(
+ !kProhibitedTrailerHeaders.includes(f),
+ `"${f}" is prohibited for trailer header`
+ );
+ }
+ for (const [key, value] of trailers) {
+ assert(
+ trailerHeaderFields.includes(key),
+ `Not trailer header field: ${key}`
+ );
+ await writer.write(encoder.encode(`${key}: ${value}\r\n`));
+ }
+ await writer.flush();
+}
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(w);
+ const writer = BufWriter.create(w);
if (!statusText) {
throw Error("bad status code");
}
@@ -97,6 +130,10 @@ export async function writeResponse(w: Writer, r: Response): Promise<void> {
} else {
await writeChunkedBody(writer, r.body);
}
+ if (r.trailers) {
+ const t = await r.trailers();
+ await writeTrailers(writer, headers, t);
+ }
await writer.flush();
}
@@ -572,4 +609,5 @@ export interface Response {
status?: number;
headers?: Headers;
body?: Uint8Array | Reader | string;
+ trailers?: () => Promise<Headers> | Headers;
}
diff --git a/std/http/server_test.ts b/std/http/server_test.ts
index c14d63145..10b0fec20 100644
--- a/std/http/server_test.ts
+++ b/std/http/server_test.ts
@@ -8,14 +8,21 @@
const { Buffer } = Deno;
import { TextProtoReader } from "../textproto/mod.ts";
import { test, runIfMain } from "../testing/mod.ts";
-import { assert, assertEquals, assertNotEquals } from "../testing/asserts.ts";
+import {
+ assert,
+ assertEquals,
+ assertNotEquals,
+ assertThrowsAsync,
+ AssertionError
+} from "../testing/asserts.ts";
import {
Response,
ServerRequest,
writeResponse,
serve,
readRequest,
- parseHTTPVersion
+ parseHTTPVersion,
+ writeTrailers
} from "./server.ts";
import {
BufReader,
@@ -426,6 +433,35 @@ test(async function writeStringReaderResponse(): Promise<void> {
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
@@ -733,4 +769,56 @@ if (Deno.build.os !== "win") {
});
}
+test("writeTrailer", async () => {
+ const w = new Buffer();
+ await writeTrailers(
+ w,
+ new Headers({ "transfer-encoding": "chunked", trailer: "deno,node" }),
+ new Headers({ deno: "land", node: "js" })
+ );
+ assertEquals(w.toString(), "deno: land\r\nnode: js\r\n");
+});
+
+test("writeTrailer should throw", async () => {
+ const w = new Buffer();
+ await assertThrowsAsync(
+ () => {
+ return writeTrailers(w, new Headers(), new Headers());
+ },
+ Error,
+ 'must have "trailer"'
+ );
+ await assertThrowsAsync(
+ () => {
+ return writeTrailers(w, new Headers({ trailer: "deno" }), new Headers());
+ },
+ Error,
+ "only allowed"
+ );
+ 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" })
+ );
+ },
+ AssertionError,
+ "prohibited"
+ );
+ }
+ await assertThrowsAsync(
+ () => {
+ return writeTrailers(
+ w,
+ new Headers({ "transfer-encoding": "chunked", trailer: "deno" }),
+ new Headers({ node: "js" })
+ );
+ },
+ AssertionError,
+ "Not trailer"
+ );
+});
+
runIfMain(import.meta);