Skip to content

Commit

Permalink
First pass at streaming http response (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
bartlomieju authored and ry committed Dec 17, 2018
1 parent 20714fe commit 2696658
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 17 deletions.
2 changes: 1 addition & 1 deletion buffer_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions bufio_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();
Expand Down
20 changes: 13 additions & 7 deletions file_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand Down
53 changes: 46 additions & 7 deletions http.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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");
}
}
}
}

Expand All @@ -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;
Expand All @@ -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();
Expand Down
58 changes: 58 additions & 0 deletions http_test.ts
Original file line number Diff line number Diff line change
@@ -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);
}
});
1 change: 1 addition & 0 deletions test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down

0 comments on commit 2696658

Please sign in to comment.