summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2018-12-17 17:49:10 +0100
committerRyan Dahl <ry@tinyclouds.org>2018-12-17 11:49:10 -0500
commitf6dae45cd2bb0615c136188b4dba8a3272ac5d70 (patch)
tree9330a72a0c9eda2f668d00f07a7c6b11d2edd346
parent579b92de599b056c308a3b2d26f0b09188c4441b (diff)
First pass at streaming http response (denoland/deno_std#16)
Original: https://github.com/denoland/deno_std/commit/269665873a9219423085418d605b8af8ac2565dc
-rw-r--r--buffer_test.ts2
-rw-r--r--bufio_test.ts4
-rwxr-xr-xfile_server.ts20
-rw-r--r--http.ts53
-rw-r--r--http_test.ts58
-rw-r--r--test.ts1
6 files changed, 121 insertions, 17 deletions
diff --git a/buffer_test.ts b/buffer_test.ts
index 1958f8e97..9a71e80a3 100644
--- a/buffer_test.ts
+++ b/buffer_test.ts
@@ -17,7 +17,7 @@ function init() {
if (testBytes == null) {
testBytes = new Uint8Array(N);
for (let i = 0; i < N; i++) {
- testBytes[i] = "a".charCodeAt(0) + (i % 26);
+ testBytes[i] = "a".charCodeAt(0) + i % 26;
}
const decoder = new TextDecoder();
testString = decoder.decode(testBytes);
diff --git a/bufio_test.ts b/bufio_test.ts
index fb5dc23e8..5f32500a7 100644
--- a/bufio_test.ts
+++ b/bufio_test.ts
@@ -109,7 +109,7 @@ test(async function bufioBufReader() {
for (let i = 0; i < texts.length - 1; i++) {
texts[i] = str + "\n";
all += texts[i];
- str += String.fromCharCode((i % 26) + 97);
+ str += String.fromCharCode(i % 26 + 97);
}
texts[texts.length - 1] = all;
@@ -294,7 +294,7 @@ test(async function bufioWriter() {
const data = new Uint8Array(8192);
for (let i = 0; i < data.byteLength; i++) {
- data[i] = charCode(" ") + (i % (charCode("~") - charCode(" ")));
+ data[i] = charCode(" ") + i % (charCode("~") - charCode(" "));
}
const w = new Buffer();
diff --git a/file_server.ts b/file_server.ts
index 5cd87e2ec..bd1c52b88 100755
--- a/file_server.ts
+++ b/file_server.ts
@@ -5,8 +5,13 @@
// TODO Add tests like these:
// https://github.com/indexzero/http-server/blob/master/test/http-server-test.js
-import { listenAndServe, ServerRequest, setContentLength, Response } from "./http";
-import { cwd, readFile, DenoError, ErrorKind, args, stat, readDir } from "deno";
+import {
+ listenAndServe,
+ ServerRequest,
+ setContentLength,
+ Response
+} from "./http";
+import { cwd, DenoError, ErrorKind, args, stat, readDir, open } from "deno";
const dirViewerTemplate = `
<!DOCTYPE html>
@@ -146,9 +151,10 @@ async function serveDir(req: ServerRequest, dirPath: string, dirName: string) {
}
async function serveFile(req: ServerRequest, filename: string) {
- let file = await readFile(filename);
+ const file = await open(filename);
+ const fileInfo = await stat(filename);
const headers = new Headers();
- headers.set("content-type", "octet-stream");
+ headers.set("content-length", fileInfo.len.toString());
const res = {
status: 200,
@@ -163,9 +169,9 @@ async function serveFallback(req: ServerRequest, e: Error) {
e instanceof DenoError &&
(e as DenoError<any>).kind === ErrorKind.NotFound
) {
- return {
- status: 404,
- body: encoder.encode("Not found")
+ return {
+ status: 404,
+ body: encoder.encode("Not found")
};
} else {
return {
diff --git a/http.ts b/http.ts
index 6954a48ba..bd45aea0d 100644
--- a/http.ts
+++ b/http.ts
@@ -1,4 +1,4 @@
-import { listen, Conn } from "deno";
+import { listen, Conn, toAsyncIterator, Reader, copy } from "deno";
import { BufReader, BufState, BufWriter } from "./bufio.ts";
import { TextProtoReader } from "./textproto.ts";
import { STATUS_TEXT } from "./http_status";
@@ -96,16 +96,23 @@ export async function listenAndServe(
export interface Response {
status?: number;
headers?: Headers;
- body?: Uint8Array;
+ body?: Uint8Array | Reader;
}
export function setContentLength(r: Response): void {
if (!r.headers) {
r.headers = new Headers();
}
- if (!r.headers.has("content-length")) {
- const bodyLength = r.body ? r.body.byteLength : 0;
- r.headers.append("Content-Length", bodyLength.toString());
+
+ if (r.body) {
+ if (!r.headers.has("content-length")) {
+ if (r.body instanceof Uint8Array) {
+ const bodyLength = r.body.byteLength;
+ r.headers.append("Content-Length", bodyLength.toString());
+ } else {
+ r.headers.append("Transfer-Encoding", "chunked");
+ }
+ }
}
}
@@ -116,6 +123,26 @@ export class ServerRequest {
headers: Headers;
w: BufWriter;
+ private async _streamBody(body: Reader, bodyLength: number) {
+ const n = await copy(this.w, body);
+ assert(n == bodyLength);
+ }
+
+ private async _streamChunkedBody(body: Reader) {
+ const encoder = new TextEncoder();
+
+ for await (const chunk of toAsyncIterator(body)) {
+ const start = encoder.encode(`${chunk.byteLength.toString(16)}\r\n`);
+ const end = encoder.encode("\r\n");
+ await this.w.write(start);
+ await this.w.write(chunk);
+ await this.w.write(end);
+ }
+
+ const endChunk = encoder.encode("0\r\n\r\n");
+ await this.w.write(endChunk);
+ }
+
async respond(r: Response): Promise<void> {
const protoMajor = 1;
const protoMinor = 1;
@@ -139,9 +166,21 @@ export class ServerRequest {
const header = new TextEncoder().encode(out);
let n = await this.w.write(header);
assert(header.byteLength == n);
+
if (r.body) {
- n = await this.w.write(r.body);
- assert(r.body.byteLength == n);
+ if (r.body instanceof Uint8Array) {
+ n = await this.w.write(r.body);
+ assert(r.body.byteLength == n);
+ } else {
+ if (r.headers.has("content-length")) {
+ await this._streamBody(
+ r.body,
+ parseInt(r.headers.get("content-length"))
+ );
+ } else {
+ await this._streamChunkedBody(r.body);
+ }
+ }
}
await this.w.flush();
diff --git a/http_test.ts b/http_test.ts
new file mode 100644
index 000000000..879afbf53
--- /dev/null
+++ b/http_test.ts
@@ -0,0 +1,58 @@
+// 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 {
+ test,
+ assert,
+ assertEqual
+} from "https://deno.land/x/testing/testing.ts";
+
+import {
+ listenAndServe,
+ ServerRequest,
+ setContentLength,
+ Response
+} from "./http";
+import { Buffer } from "./buffer";
+import { BufWriter } from "./bufio";
+
+interface ResponseTest {
+ response: Response;
+ raw: string;
+}
+
+const responseTests: ResponseTest[] = [
+ // Default response
+ {
+ response: {},
+ raw: "HTTP/1.1 200 OK\r\n" + "\r\n"
+ },
+ // HTTP/1.1, chunked coding; empty trailer; close
+ {
+ response: {
+ status: 200,
+ body: new 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"
+ }
+];
+
+test(async function responseWrite() {
+ for (const testCase of responseTests) {
+ const buf = new Buffer();
+ const bufw = new BufWriter(buf);
+ const request = new ServerRequest();
+ request.w = bufw;
+
+ await request.respond(testCase.response);
+ assertEqual(buf.toString(), testCase.raw);
+ }
+});
diff --git a/test.ts b/test.ts
index 44a692015..8cb0a2ca2 100644
--- a/test.ts
+++ b/test.ts
@@ -2,6 +2,7 @@ import { run } from "deno";
import "./buffer_test.ts";
import "./bufio_test.ts";
+import "./http_test.ts";
import "./textproto_test.ts";
import { runTests, completePromise } from "./file_server_test.ts";