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:
parent
503f8105c5
commit
e65d8af1f7
1 changed files with 215 additions and 173 deletions
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue