diff options
-rw-r--r-- | cli/tsc/dts/lib.deno.ns.d.ts | 122 | ||||
-rw-r--r-- | ext/http/00_serve.js | 28 | ||||
-rw-r--r-- | tests/unit/serve_test.ts | 19 |
3 files changed, 137 insertions, 32 deletions
diff --git a/cli/tsc/dts/lib.deno.ns.d.ts b/cli/tsc/dts/lib.deno.ns.d.ts index c63b4a261..fc64876f4 100644 --- a/cli/tsc/dts/lib.deno.ns.d.ts +++ b/cli/tsc/dts/lib.deno.ns.d.ts @@ -6217,7 +6217,7 @@ declare namespace Deno { */ export function gid(): number | null; - /** Information for a HTTP request. + /** Additional information for an HTTP request and its connection. * * @category HTTP Server */ @@ -6269,7 +6269,7 @@ declare namespace Deno { onError?: (error: unknown) => Response | Promise<Response>; /** The callback which is called when the server starts listening. */ - onListen?: (params: { hostname: string; port: number }) => void; + onListen?: (localAddr: Deno.NetAddr) => void; } /** Additional options which are used when opening a TLS (HTTPS) server. @@ -6304,6 +6304,14 @@ declare namespace Deno { handler: ServeHandler; } + /** + * @category HTTP Server + */ + export interface ServeTlsInit { + /** The handler to invoke to process each incoming request. */ + handler: ServeHandler; + } + /** @category HTTP Server */ export interface ServeUnixOptions { /** The unix domain socket path to listen on. */ @@ -6316,7 +6324,7 @@ declare namespace Deno { onError?: (error: unknown) => Response | Promise<Response>; /** The callback which is called when the server starts listening. */ - onListen?: (params: { path: string }) => void; + onListen?: (localAddr: Deno.UnixAddr) => void; } /** Information for a unix domain socket HTTP request. @@ -6353,12 +6361,16 @@ declare namespace Deno { * * @category HTTP Server */ - export interface HttpServer extends AsyncDisposable { + export interface HttpServer<A extends Deno.Addr = Deno.Addr> + extends AsyncDisposable { /** A promise that resolves once server finishes - eg. when aborted using * the signal passed to {@linkcode ServeOptions.signal}. */ finished: Promise<void>; + /** The local address this server is listening on. */ + addr: A; + /** * Make the server block the event loop from finishing. * @@ -6395,7 +6407,7 @@ declare namespace Deno { * * @category HTTP Server */ - export function serve(handler: ServeHandler): HttpServer; + export function serve(handler: ServeHandler): HttpServer<Deno.NetAddr>; /** Serves HTTP requests with the given option bag and handler. * * You can specify the socket path with `path` option. @@ -6444,7 +6456,67 @@ declare namespace Deno { export function serve( options: ServeUnixOptions, handler: ServeUnixHandler, - ): HttpServer; + ): HttpServer<Deno.UnixAddr>; + /** Serves HTTP requests with the given option bag and handler. + * + * You can specify an object with a port and hostname option, which is the + * address to listen on. The default is port `8000` on hostname `"127.0.0.1"`. + * + * You can change the address to listen on using the `hostname` and `port` + * options. The below example serves on port `3000` and hostname `"0.0.0.0"`. + * + * ```ts + * Deno.serve( + * { port: 3000, hostname: "0.0.0.0" }, + * (_req) => new Response("Hello, world") + * ); + * ``` + * + * You can stop the server with an {@linkcode AbortSignal}. The abort signal + * needs to be passed as the `signal` option in the options bag. The server + * aborts when the abort signal is aborted. To wait for the server to close, + * await the promise returned from the `Deno.serve` API. + * + * ```ts + * const ac = new AbortController(); + * + * const server = Deno.serve( + * { signal: ac.signal }, + * (_req) => new Response("Hello, world") + * ); + * server.finished.then(() => console.log("Server closed")); + * + * console.log("Closing server..."); + * ac.abort(); + * ``` + * + * By default `Deno.serve` prints the message + * `Listening on http://<hostname>:<port>/` on listening. If you like to + * change this behavior, you can specify a custom `onListen` callback. + * + * ```ts + * Deno.serve({ + * onListen({ port, hostname }) { + * console.log(`Server started at http://${hostname}:${port}`); + * // ... more info specific to your server .. + * }, + * }, (_req) => new Response("Hello, world")); + * ``` + * + * To enable TLS you must specify the `key` and `cert` options. + * + * ```ts + * const cert = "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n"; + * const key = "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"; + * Deno.serve({ cert, key }, (_req) => new Response("Hello, world")); + * ``` + * + * @category HTTP Server + */ + export function serve( + options: ServeOptions, + handler: ServeHandler, + ): HttpServer<Deno.NetAddr>; /** Serves HTTP requests with the given option bag and handler. * * You can specify an object with a port and hostname option, which is the @@ -6503,11 +6575,10 @@ declare namespace Deno { */ export function serve( options: - | ServeOptions | ServeTlsOptions | (ServeTlsOptions & TlsCertifiedKeyOptions), handler: ServeHandler, - ): HttpServer; + ): HttpServer<Deno.NetAddr>; /** Serves HTTP requests with the given option bag. * * You can specify an object with the path option, which is the @@ -6534,7 +6605,7 @@ declare namespace Deno { */ export function serve( options: ServeUnixInit & ServeUnixOptions, - ): HttpServer; + ): HttpServer<Deno.UnixAddr>; /** Serves HTTP requests with the given option bag. * * You can specify an object with a port and hostname option, which is the @@ -6563,10 +6634,39 @@ declare namespace Deno { export function serve( options: & ServeInit + & ServeOptions, + ): HttpServer<Deno.NetAddr>; + /** Serves HTTP requests with the given option bag. + * + * You can specify an object with a port and hostname option, which is the + * address to listen on. The default is port `8000` on hostname `"127.0.0.1"`. + * + * ```ts + * const ac = new AbortController(); + * + * const server = Deno.serve({ + * port: 3000, + * hostname: "0.0.0.0", + * handler: (_req) => new Response("Hello, world"), + * signal: ac.signal, + * onListen({ port, hostname }) { + * console.log(`Server started at http://${hostname}:${port}`); + * }, + * }); + * server.finished.then(() => console.log("Server closed")); + * + * console.log("Closing server..."); + * ac.abort(); + * ``` + * + * @category HTTP Server + */ + export function serve( + options: + & ServeTlsInit & ( - | ServeOptions | ServeTlsOptions | (ServeTlsOptions & TlsCertifiedKeyOptions) ), - ): HttpServer; + ): HttpServer<Deno.NetAddr>; } diff --git a/ext/http/00_serve.js b/ext/http/00_serve.js index 660287edb..52b833f10 100644 --- a/ext/http/00_serve.js +++ b/ext/http/00_serve.js @@ -552,7 +552,7 @@ function serve(arg1, arg2) { const path = listener.addr.path; return serveHttpOnListener(listener, signal, handler, onError, () => { if (options.onListen) { - options.onListen({ path }); + options.onListen(listener.addr); } else { console.log(`Listening on ${path}`); } @@ -593,19 +593,18 @@ function serve(arg1, arg2) { listenOpts.port = listener.addr.port; } - const onListen = (scheme) => { - // 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 - const hostname = listenOpts.hostname == "0.0.0.0" - ? "localhost" - : listenOpts.hostname; - const port = listenOpts.port; + const addr = listener.addr; + // 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 + const hostname = addr.hostname == "0.0.0.0" ? "localhost" : addr.hostname; + addr.hostname = hostname; + const onListen = (scheme) => { if (options.onListen) { - options.onListen({ hostname, port }); + options.onListen(addr); } else { - console.log(`Listening on ${scheme}${hostname}:${port}/`); + console.log(`Listening on ${scheme}${addr.hostname}:${addr.port}/`); } }; @@ -625,7 +624,7 @@ function serveHttpOnListener(listener, signal, handler, onError, onListen) { onListen(context.scheme); - return serveHttpOn(context, callback); + return serveHttpOn(context, listener.addr, callback); } /** @@ -641,10 +640,10 @@ function serveHttpOnConnection(connection, signal, handler, onError, onListen) { onListen(context.scheme); - return serveHttpOn(context, callback); + return serveHttpOn(context, connection.localAddr, callback); } -function serveHttpOn(context, callback) { +function serveHttpOn(context, addr, callback) { let ref = true; let currentPromise = null; @@ -700,6 +699,7 @@ function serveHttpOn(context, callback) { })(); return { + addr, finished, async shutdown() { if (!context.closing && !context.closed) { diff --git a/tests/unit/serve_test.ts b/tests/unit/serve_test.ts index 9b2870ebd..048529ae9 100644 --- a/tests/unit/serve_test.ts +++ b/tests/unit/serve_test.ts @@ -43,7 +43,10 @@ function onListen( } async function makeServer( - handler: (req: Request) => Response | Promise<Response>, + handler: ( + req: Request, + info: Deno.ServeHandlerInfo, + ) => Response | Promise<Response>, ): Promise< { finished: Promise<void>; @@ -432,7 +435,7 @@ Deno.test(async function httpServerRejectsOnAddrInUse() { Deno.test({ permissions: { net: true } }, async function httpServerBasic() { const ac = new AbortController(); const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); + const listeningDeferred = Promise.withResolvers<Deno.NetAddr>(); const server = Deno.serve({ handler: async (request, { remoteAddr }) => { @@ -447,11 +450,13 @@ Deno.test({ permissions: { net: true } }, async function httpServerBasic() { }, port: servePort, signal: ac.signal, - onListen: onListen(listeningDeferred.resolve), + onListen: (addr) => listeningDeferred.resolve(addr), onError: createOnErrorCb(ac), }); - await listeningDeferred.promise; + const addr = await listeningDeferred.promise; + assertEquals(addr.hostname, server.addr.hostname); + assertEquals(addr.port, server.addr.port); const resp = await fetch(`http://127.0.0.1:${servePort}/`, { headers: { "connection": "close" }, }); @@ -472,7 +477,7 @@ Deno.test( async function httpServerOnListener() { const ac = new AbortController(); const deferred = Promise.withResolvers<void>(); - const listeningDeferred = Promise.withResolvers<void>(); + const listeningDeferred = Promise.withResolvers(); const listener = Deno.listen({ port: servePort }); const server = serveHttpOnListener( listener, @@ -3812,7 +3817,7 @@ Deno.test( permissions: { run: true, read: true, write: true }, }, async function httpServerUnixDomainSocket() { - const { promise, resolve } = Promise.withResolvers<{ path: string }>(); + const { promise, resolve } = Promise.withResolvers<Deno.UnixAddr>(); const ac = new AbortController(); const filePath = tmpUnixSocketPath(); const server = Deno.serve( @@ -3830,7 +3835,7 @@ Deno.test( }, ); - assertEquals(await promise, { path: filePath }); + assertEquals((await promise).path, filePath); assertEquals( "hello world!", await curlRequest(["--unix-socket", filePath, "http://localhost"]), |