summaryrefslogtreecommitdiff
path: root/ext/http/01_http.js
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2023-04-03 17:44:18 +0200
committerGitHub <noreply@github.com>2023-04-03 17:44:18 +0200
commit2846bbe0a3de0cc366006f97023ce146112c40c9 (patch)
tree2022df8b885fa8b833f1842f7469b66c0766bbc3 /ext/http/01_http.js
parent6d68392f8ae1b29626af8edc1666a889b69470a9 (diff)
refactor: "Deno.serve()" API uses "Deno.serveHttp()" internally (#18568)
This commit changes implementation of "Deno.serve()" API to use "Deno.serveHttp()" under the hood. This change will allow us to remove the "flash" server implementation, bringing stability to the "Deno.serve()" API. "cli/tests/unit/flash_test.ts" was renamed to "serve_test.ts". Closes https://github.com/denoland/deno/issues/15574 Closes https://github.com/denoland/deno/issues/15504 Closes https://github.com/denoland/deno/issues/15646 Closes https://github.com/denoland/deno/issues/15909 Closes https://github.com/denoland/deno/issues/15911 Closes https://github.com/denoland/deno/issues/16828 Closes https://github.com/denoland/deno/issues/18046 Closes https://github.com/denoland/deno/issues/15869
Diffstat (limited to 'ext/http/01_http.js')
-rw-r--r--ext/http/01_http.js236
1 files changed, 233 insertions, 3 deletions
diff --git a/ext/http/01_http.js b/ext/http/01_http.js
index 7224df3c5..ab0d6626a 100644
--- a/ext/http/01_http.js
+++ b/ext/http/01_http.js
@@ -31,8 +31,8 @@ import {
_serverHandleIdleTimeout,
WebSocket,
} from "ext:deno_websocket/01_websocket.js";
-import { TcpConn, UnixConn } from "ext:deno_net/01_net.js";
-import { TlsConn } from "ext:deno_net/02_tls.js";
+import { listen, TcpConn, UnixConn } from "ext:deno_net/01_net.js";
+import { listenTls, TlsConn } from "ext:deno_net/02_tls.js";
import {
Deferred,
getReadableStreamResourceBacking,
@@ -50,10 +50,13 @@ const {
Set,
SetPrototypeAdd,
SetPrototypeDelete,
+ SetPrototypeClear,
StringPrototypeCharCodeAt,
StringPrototypeIncludes,
StringPrototypeToLowerCase,
StringPrototypeSplit,
+ SafeSet,
+ PromisePrototypeCatch,
Symbol,
SymbolAsyncIterator,
TypeError,
@@ -554,4 +557,231 @@ function buildCaseInsensitiveCommaValueFinder(checkText) {
internals.buildCaseInsensitiveCommaValueFinder =
buildCaseInsensitiveCommaValueFinder;
-export { _ws, HttpConn, upgradeHttp, upgradeHttpRaw, upgradeWebSocket };
+function hostnameForDisplay(hostname) {
+ // If the hostname is "0.0.0.0", we display "localhost" in console
+ // because browsers in Windows don't resolve "0.0.0.0".
+ // See the discussion in https://github.com/denoland/deno_std/issues/1165
+ return hostname === "0.0.0.0" ? "localhost" : hostname;
+}
+
+async function respond(handler, requestEvent, connInfo, onError) {
+ let response;
+
+ try {
+ response = await handler(requestEvent.request, connInfo);
+
+ if (response.bodyUsed && response.body !== null) {
+ throw new TypeError("Response body already consumed.");
+ }
+ } catch (e) {
+ // Invoke `onError` handler if the request handler throws.
+ response = await onError(e);
+ }
+
+ try {
+ // Send the response.
+ await requestEvent.respondWith(response);
+ } catch {
+ // `respondWith()` can throw for various reasons, including downstream and
+ // upstream connection errors, as well as errors thrown during streaming
+ // of the response content. In order to avoid false negatives, we ignore
+ // the error here and let `serveHttp` close the connection on the
+ // following iteration if it is in fact a downstream connection error.
+ }
+}
+
+async function serveConnection(
+ server,
+ activeHttpConnections,
+ handler,
+ httpConn,
+ connInfo,
+ onError,
+) {
+ while (!server.closed) {
+ let requestEvent = null;
+
+ try {
+ // Yield the new HTTP request on the connection.
+ requestEvent = await httpConn.nextRequest();
+ } catch {
+ // Connection has been closed.
+ break;
+ }
+
+ if (requestEvent === null) {
+ break;
+ }
+
+ respond(handler, requestEvent, connInfo, onError);
+ }
+
+ SetPrototypeDelete(activeHttpConnections, httpConn);
+ try {
+ httpConn.close();
+ } catch {
+ // Connection has already been closed.
+ }
+}
+
+async function serve(arg1, arg2) {
+ let options = undefined;
+ let handler = undefined;
+ if (typeof arg1 === "function") {
+ handler = arg1;
+ options = arg2;
+ } else if (typeof arg2 === "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 (typeof handler !== "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,
+ reuseport: options.reusePort ?? false,
+ };
+
+ 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;
+ }
+
+ let listener;
+ if (listenOpts.cert && listenOpts.key) {
+ listener = listenTls({
+ hostname: listenOpts.hostname,
+ port: listenOpts.port,
+ cert: listenOpts.cert,
+ key: listenOpts.key,
+ });
+ } else {
+ listener = listen({
+ hostname: listenOpts.hostname,
+ port: listenOpts.port,
+ });
+ }
+
+ const serverDeferred = new Deferred();
+ const activeHttpConnections = new SafeSet();
+
+ const server = {
+ transport: listenOpts.cert && listenOpts.key ? "https" : "http",
+ hostname: listenOpts.hostname,
+ port: listenOpts.port,
+ closed: false,
+
+ close() {
+ if (server.closed) {
+ return;
+ }
+ server.closed = true;
+ try {
+ listener.close();
+ } catch {
+ // Might have been already closed.
+ }
+
+ for (const httpConn of new SafeSetIterator(activeHttpConnections)) {
+ try {
+ httpConn.close();
+ } catch {
+ // Might have been already closed.
+ }
+ }
+
+ SetPrototypeClear(activeHttpConnections);
+ serverDeferred.resolve();
+ },
+
+ async serve() {
+ while (!server.closed) {
+ let conn;
+
+ try {
+ conn = await listener.accept();
+ } catch {
+ // Listener has been closed.
+ if (!server.closed) {
+ console.log("Listener has closed unexpectedly");
+ }
+ break;
+ }
+
+ let httpConn;
+ try {
+ const rid = ops.op_http_start(conn.rid);
+ httpConn = new HttpConn(rid, conn.remoteAddr, conn.localAddr);
+ } catch {
+ // Connection has been closed;
+ continue;
+ }
+
+ SetPrototypeAdd(activeHttpConnections, httpConn);
+
+ const connInfo = {
+ localAddr: conn.localAddr,
+ remoteAddr: conn.remoteAddr,
+ };
+ // Serve the HTTP connection
+ serveConnection(
+ server,
+ activeHttpConnections,
+ handler,
+ httpConn,
+ connInfo,
+ onError,
+ );
+ }
+ await serverDeferred.promise;
+ },
+ };
+
+ signal?.addEventListener(
+ "abort",
+ () => {
+ try {
+ server.close();
+ } catch {
+ // Pass
+ }
+ },
+ { once: true },
+ );
+
+ onListen(listener.addr);
+
+ await PromisePrototypeCatch(server.serve(), console.error);
+}
+
+export { _ws, HttpConn, serve, upgradeHttp, upgradeHttpRaw, upgradeWebSocket };