mirror of
https://github.com/denoland/deno.git
synced 2025-01-12 17:09:00 -05:00
Unblock server on multiple HTTP requests (denoland/deno_std#3)
Original: 1bf555ab2f
This commit is contained in:
parent
a34bc9040d
commit
17c255581d
2 changed files with 82 additions and 33 deletions
108
http.ts
108
http.ts
|
@ -4,37 +4,82 @@ import { TextProtoReader } from "./textproto.ts";
|
||||||
import { STATUS_TEXT } from "./http_status";
|
import { STATUS_TEXT } from "./http_status";
|
||||||
import { assert } from "./util";
|
import { assert } from "./util";
|
||||||
|
|
||||||
export async function* serve(addr: string) {
|
interface Deferred {
|
||||||
const listener = listen("tcp", addr);
|
promise: Promise<{}>;
|
||||||
while (true) {
|
resolve: () => void;
|
||||||
const c = await listener.accept();
|
reject: () => void;
|
||||||
yield* serveConn(c);
|
|
||||||
}
|
|
||||||
listener.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function* serveConn(c: Conn) {
|
function deferred(): Deferred {
|
||||||
let bufr = new BufReader(c);
|
let resolve, reject;
|
||||||
let bufw = new BufWriter(c);
|
const promise = new Promise((res, rej) => {
|
||||||
try {
|
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;
|
||||||
|
}
|
||||||
|
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* 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) {
|
while (true) {
|
||||||
const [req, err] = await readRequest(bufr);
|
await env.serveDeferred.promise;
|
||||||
if (err == "EOF") {
|
env.serveDeferred = deferred(); // use a new deferred
|
||||||
break;
|
let queueToProcess = env.reqQueue;
|
||||||
|
env.reqQueue = [];
|
||||||
|
for (const result of queueToProcess) {
|
||||||
|
yield result;
|
||||||
}
|
}
|
||||||
if (err == "ShortWrite") {
|
|
||||||
console.log("ShortWrite error");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
req.w = bufw;
|
|
||||||
yield req;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
c.close();
|
|
||||||
}
|
}
|
||||||
|
listener.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Response {
|
interface Response {
|
||||||
|
@ -75,7 +120,7 @@ class ServerRequest {
|
||||||
setContentLength(r);
|
setContentLength(r);
|
||||||
|
|
||||||
if (r.headers) {
|
if (r.headers) {
|
||||||
for (let [key, value] of r.headers) {
|
for (const [key, value] of r.headers) {
|
||||||
out += `${key}: ${value}\r\n`;
|
out += `${key}: ${value}\r\n`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,9 +138,12 @@ class ServerRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readRequest(b: BufReader): Promise<[ServerRequest, BufState]> {
|
async function readRequest(c: Conn): Promise<[ServerRequest, BufState]> {
|
||||||
const tp = new TextProtoReader(b);
|
const bufr = new BufReader(c);
|
||||||
|
const bufw = new BufWriter(c);
|
||||||
const req = new ServerRequest();
|
const req = new ServerRequest();
|
||||||
|
req.w = bufw;
|
||||||
|
const tp = new TextProtoReader(bufr);
|
||||||
|
|
||||||
let s: string;
|
let s: string;
|
||||||
let err: BufState;
|
let err: BufState;
|
||||||
|
@ -109,5 +157,7 @@ async function readRequest(b: BufReader): Promise<[ServerRequest, BufState]> {
|
||||||
|
|
||||||
[req.headers, err] = await tp.readMIMEHeader();
|
[req.headers, err] = await tp.readMIMEHeader();
|
||||||
|
|
||||||
|
// TODO: handle body
|
||||||
|
|
||||||
return [req, err];
|
return [req, err];
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,7 @@ const addr = "0.0.0.0:8000";
|
||||||
const s = serve(addr);
|
const s = serve(addr);
|
||||||
console.log(`listening on http://${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() {
|
async function main() {
|
||||||
for await (const req of s) {
|
for await (const req of s) {
|
||||||
|
|
Loading…
Reference in a new issue