summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin (Kun) "Kassimo" Qian <kevinkassimo@gmail.com>2018-12-06 16:08:21 -0500
committerRyan Dahl <ry@tinyclouds.org>2018-12-06 16:08:21 -0500
commit17c255581dbeff6bad6fe385b6cdee174e3f93ae (patch)
treec3f6f89811323b1cd9874a76821d641845238cc4
parenta34bc9040dc86efe5dfa9e89a6d4daadb425891d (diff)
Unblock server on multiple HTTP requests (denoland/deno_std#3)
Original: https://github.com/denoland/deno_std/commit/1bf555ab2f9c6074b07697d72cb93f276ecf5bb1
-rw-r--r--http.ts108
-rw-r--r--http_test.ts3
2 files changed, 80 insertions, 31 deletions
diff --git a/http.ts b/http.ts
index c7d2851c0..2dab9e1e5 100644
--- a/http.ts
+++ b/http.ts
@@ -4,37 +4,82 @@ import { TextProtoReader } from "./textproto.ts";
import { STATUS_TEXT } from "./http_status";
import { assert } from "./util";
-export async function* serve(addr: string) {
- const listener = listen("tcp", addr);
- while (true) {
- const c = await listener.accept();
- yield* serveConn(c);
+interface Deferred {
+ promise: Promise<{}>;
+ resolve: () => void;
+ reject: () => void;
+}
+
+function deferred(): Deferred {
+ let resolve, reject;
+ const promise = new Promise((res, rej) => {
+ resolve = res;
+ reject = rej;
+ });
+ return {
+ promise,
+ resolve,
+ reject
+ };
+}
+
+interface ServeEnv {
+ reqQueue: ServerRequest[];
+ serveDeferred: Deferred;
+}
+
+// Continuously read more requests from conn until EOF
+// Mutually calling with maybeHandleReq
+// TODO: make them async function after this change is done
+// https://github.com/tc39/ecma262/pull/1250
+// See https://v8.dev/blog/fast-async
+export function serveConn(env: ServeEnv, conn: Conn) {
+ readRequest(conn).then(maybeHandleReq.bind(null, env, conn));
+}
+function maybeHandleReq(env: ServeEnv, conn: Conn, maybeReq: any) {
+ const [req, _err] = maybeReq;
+ if (_err) {
+ conn.close(); // assume EOF for now...
+ return;
}
- listener.close();
+ env.reqQueue.push(req); // push req to queue
+ env.serveDeferred.resolve(); // signal while loop to process it
+ // TODO: protection against client req flooding
+ serveConn(env, conn); // try read more (reusing connection)
}
-export async function* serveConn(c: Conn) {
- let bufr = new BufReader(c);
- let bufw = new BufWriter(c);
- try {
- while (true) {
- const [req, err] = await readRequest(bufr);
- if (err == "EOF") {
- break;
- }
- if (err == "ShortWrite") {
- console.log("ShortWrite error");
- break;
- }
- if (err) {
- throw err;
- }
- req.w = bufw;
- yield req;
+export async function* serve(addr: string) {
+ const listener = listen("tcp", addr);
+ const env: ServeEnv = {
+ reqQueue: [], // in case multiple promises are ready
+ serveDeferred: deferred()
+ };
+
+ // Routine that keeps calling accept
+ const acceptRoutine = () => {
+ const handleConn = (conn: Conn) => {
+ serveConn(env, conn); // don't block
+ scheduleAccept(); // schedule next accept
+ };
+ const scheduleAccept = () => {
+ listener.accept().then(handleConn);
+ };
+ scheduleAccept();
+ };
+
+ acceptRoutine();
+
+ // Loop hack to allow yield (yield won't work in callbacks)
+ while (true) {
+ await env.serveDeferred.promise;
+ env.serveDeferred = deferred(); // use a new deferred
+ let queueToProcess = env.reqQueue;
+ env.reqQueue = [];
+ for (const result of queueToProcess) {
+ yield result;
}
- } finally {
- c.close();
}
+ listener.close();
}
interface Response {
@@ -75,7 +120,7 @@ class ServerRequest {
setContentLength(r);
if (r.headers) {
- for (let [key, value] of r.headers) {
+ for (const [key, value] of r.headers) {
out += `${key}: ${value}\r\n`;
}
}
@@ -93,9 +138,12 @@ class ServerRequest {
}
}
-async function readRequest(b: BufReader): Promise<[ServerRequest, BufState]> {
- const tp = new TextProtoReader(b);
+async function readRequest(c: Conn): Promise<[ServerRequest, BufState]> {
+ const bufr = new BufReader(c);
+ const bufw = new BufWriter(c);
const req = new ServerRequest();
+ req.w = bufw;
+ const tp = new TextProtoReader(bufr);
let s: string;
let err: BufState;
@@ -109,5 +157,7 @@ async function readRequest(b: BufReader): Promise<[ServerRequest, BufState]> {
[req.headers, err] = await tp.readMIMEHeader();
+ // TODO: handle body
+
return [req, err];
}
diff --git a/http_test.ts b/http_test.ts
index e6cb87f01..b0007a892 100644
--- a/http_test.ts
+++ b/http_test.ts
@@ -5,8 +5,7 @@ const addr = "0.0.0.0:8000";
const s = serve(addr);
console.log(`listening on http://${addr}/`);
-const body = (new TextEncoder()).encode("Hello World\n");
-
+const body = new TextEncoder().encode("Hello World\n");
async function main() {
for await (const req of s) {