mirror of
https://github.com/denoland/deno.git
synced 2024-12-03 17:08:35 -05:00
fix(ext/flash): don't block requests (#15852)
This commit is contained in:
parent
503f8105c5
commit
e65d8af1f7
1 changed files with 215 additions and 173 deletions
|
@ -32,6 +32,7 @@
|
|||
TypedArrayPrototypeSubarray,
|
||||
TypeError,
|
||||
Uint8Array,
|
||||
Promise,
|
||||
Uint8ArrayPrototype,
|
||||
} = window.__bootstrap.primordials;
|
||||
|
||||
|
@ -227,142 +228,23 @@
|
|||
}
|
||||
}
|
||||
|
||||
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,
|
||||
// TODO(@littledivy): Woah woah, cut down the number of arguments.
|
||||
async function handleResponse(
|
||||
req,
|
||||
resp,
|
||||
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 = await handler(req);
|
||||
} catch (e) {
|
||||
resp = await onError(e);
|
||||
}
|
||||
hasBody,
|
||||
method,
|
||||
serverId,
|
||||
i,
|
||||
respondFast,
|
||||
respondChunked,
|
||||
) {
|
||||
// there might've been an HTTP upgrade.
|
||||
if (resp === undefined) {
|
||||
return;
|
||||
}
|
||||
const innerResp = toInnerResponse(resp);
|
||||
|
||||
// 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
|
||||
// we'll be streaming it.
|
||||
|
@ -529,7 +411,167 @@
|
|||
}
|
||||
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;
|
||||
|
|
Loading…
Reference in a new issue