1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-04 17:18:23 -05:00

fix(ext/flash): don't block requests (#15852)

This commit is contained in:
Divy Srivastava 2022-09-13 22:24:27 +05:30 committed by cjihrig
parent 503f8105c5
commit e65d8af1f7
No known key found for this signature in database
GPG key ID: 7434390BDBE9B9C5

View file

@ -32,6 +32,7 @@
TypedArrayPrototypeSubarray, TypedArrayPrototypeSubarray,
TypeError, TypeError,
Uint8Array, Uint8Array,
Promise,
Uint8ArrayPrototype, Uint8ArrayPrototype,
} = window.__bootstrap.primordials; } = window.__bootstrap.primordials;
@ -227,142 +228,23 @@
} }
} }
async function serve(arg1, arg2) { // TODO(@littledivy): Woah woah, cut down the number of arguments.
let options = undefined; async function handleResponse(
let handler = undefined; req,
if (arg1 instanceof Function) { resp,
handler = arg1;
options = arg2;
} else if (arg2 instanceof Function) {
handler = arg2;
options = arg1;
} else {
options = arg1;
}
if (handler === undefined) {
if (options === undefined) {
throw new TypeError(
"No handler was provided, so an options bag is mandatory.",
);
}
handler = options.handler;
}
if (!(handler instanceof Function)) {
throw new TypeError("A handler function must be provided.");
}
if (options === undefined) {
options = {};
}
const signal = options.signal;
const onError = options.onError ?? function (error) {
console.error(error);
return new Response("Internal Server Error", { status: 500 });
};
const onListen = options.onListen ?? function ({ port }) {
console.log(
`Listening on http://${
hostnameForDisplay(listenOpts.hostname)
}:${port}/`,
);
};
const listenOpts = {
hostname: options.hostname ?? "127.0.0.1",
port: options.port ?? 9000,
};
if (options.cert || options.key) {
if (!options.cert || !options.key) {
throw new TypeError(
"Both cert and key must be provided to enable HTTPS.",
);
}
listenOpts.cert = options.cert;
listenOpts.key = options.key;
}
const serverId = core.ops.op_flash_serve(listenOpts);
const serverPromise = core.opAsync("op_flash_drive_server", serverId);
core.opAsync("op_flash_wait_for_listening", serverId).then((port) => {
onListen({ hostname: listenOpts.hostname, port });
}).catch(() => {});
const finishedPromise = serverPromise.catch(() => {});
const server = {
id: serverId,
transport: listenOpts.cert && listenOpts.key ? "https" : "http",
hostname: listenOpts.hostname,
port: listenOpts.port,
closed: false,
finished: finishedPromise,
async close() {
if (server.closed) {
return;
}
server.closed = true;
await core.opAsync("op_flash_close_server", serverId);
await server.finished;
},
async serve() {
let offset = 0;
while (true) {
if (server.closed) {
break;
}
let tokens = nextRequestSync();
if (tokens === 0) {
tokens = await core.opAsync("op_flash_next_async", serverId);
if (server.closed) {
break;
}
}
for (let i = offset; i < offset + tokens; i++) {
let body = null;
// There might be a body, but we don't expose it for GET/HEAD requests.
// It will be closed automatically once the request has been handled and
// the response has been sent.
const method = getMethodSync(i);
let hasBody = method > 2; // Not GET/HEAD/CONNECT
if (hasBody) {
body = createRequestBodyStream(serverId, i);
if (body === null) {
hasBody = false;
}
}
const req = fromFlashRequest(
serverId,
/* streamRid */
i,
body, body,
/* methodCb */ hasBody,
() => methods[method], method,
/* urlCb */ serverId,
() => { i,
const path = core.ops.op_flash_path(serverId, i); respondFast,
return `${server.transport}://${server.hostname}:${server.port}${path}`; respondChunked,
}, ) {
/* headersCb */
() => core.ops.op_flash_headers(serverId, i),
);
let resp;
try {
resp = await handler(req);
} catch (e) {
resp = await onError(e);
}
// there might've been an HTTP upgrade. // there might've been an HTTP upgrade.
if (resp === undefined) { if (resp === undefined) {
return; return;
} }
const innerResp = toInnerResponse(resp); const innerResp = toInnerResponse(resp);
// If response body length is known, it will be sent synchronously in a // If response body length is known, it will be sent synchronously in a
// single op, in other case a "response body" resource will be created and // single op, in other case a "response body" resource will be created and
// we'll be streaming it. // we'll be streaming it.
@ -529,7 +411,167 @@
} }
ws[_serverHandleIdleTimeout](); ws[_serverHandleIdleTimeout]();
} }
})().catch(onError); })();
}
async function serve(arg1, arg2) {
let options = undefined;
let handler = undefined;
if (arg1 instanceof Function) {
handler = arg1;
options = arg2;
} else if (arg2 instanceof Function) {
handler = arg2;
options = arg1;
} else {
options = arg1;
}
if (handler === undefined) {
if (options === undefined) {
throw new TypeError(
"No handler was provided, so an options bag is mandatory.",
);
}
handler = options.handler;
}
if (!(handler instanceof Function)) {
throw new TypeError("A handler function must be provided.");
}
if (options === undefined) {
options = {};
}
const signal = options.signal;
const onError = options.onError ?? function (error) {
console.error(error);
return new Response("Internal Server Error", { status: 500 });
};
const onListen = options.onListen ?? function ({ port }) {
console.log(
`Listening on http://${
hostnameForDisplay(listenOpts.hostname)
}:${port}/`,
);
};
const listenOpts = {
hostname: options.hostname ?? "127.0.0.1",
port: options.port ?? 9000,
};
if (options.cert || options.key) {
if (!options.cert || !options.key) {
throw new TypeError(
"Both cert and key must be provided to enable HTTPS.",
);
}
listenOpts.cert = options.cert;
listenOpts.key = options.key;
}
const serverId = core.ops.op_flash_serve(listenOpts);
const serverPromise = core.opAsync("op_flash_drive_server", serverId);
core.opAsync("op_flash_wait_for_listening", serverId).then((port) => {
onListen({ hostname: listenOpts.hostname, port });
}).catch(() => {});
const finishedPromise = serverPromise.catch(() => {});
const server = {
id: serverId,
transport: listenOpts.cert && listenOpts.key ? "https" : "http",
hostname: listenOpts.hostname,
port: listenOpts.port,
closed: false,
finished: finishedPromise,
async close() {
if (server.closed) {
return;
}
server.closed = true;
await core.opAsync("op_flash_close_server", serverId);
await server.finished;
},
async serve() {
let offset = 0;
while (true) {
if (server.closed) {
break;
}
let tokens = nextRequestSync();
if (tokens === 0) {
tokens = await core.opAsync("op_flash_next_async", serverId);
if (server.closed) {
break;
}
}
for (let i = offset; i < offset + tokens; i++) {
let body = null;
// There might be a body, but we don't expose it for GET/HEAD requests.
// It will be closed automatically once the request has been handled and
// the response has been sent.
const method = getMethodSync(i);
let hasBody = method > 2; // Not GET/HEAD/CONNECT
if (hasBody) {
body = createRequestBodyStream(serverId, i);
if (body === null) {
hasBody = false;
}
}
const req = fromFlashRequest(
serverId,
/* streamRid */
i,
body,
/* methodCb */
() => methods[method],
/* urlCb */
() => {
const path = core.ops.op_flash_path(serverId, i);
return `${server.transport}://${server.hostname}:${server.port}${path}`;
},
/* headersCb */
() => core.ops.op_flash_headers(serverId, i),
);
let resp;
try {
resp = handler(req);
if (resp instanceof Promise || typeof resp.then === "function") {
resp.then((resp) =>
handleResponse(
req,
resp,
body,
hasBody,
method,
serverId,
i,
respondFast,
respondChunked,
)
).catch(onError);
continue;
}
} catch (e) {
resp = await onError(e);
}
handleResponse(
req,
resp,
body,
hasBody,
method,
serverId,
i,
respondFast,
respondChunked,
);
} }
offset += tokens; offset += tokens;