mirror of
https://github.com/denoland/deno.git
synced 2024-12-23 15:49:44 -05:00
feat(ext/http): Add addr
to HttpServer (#23442)
Adds an `addr` field to `HttpServer` to simplify the pattern `Deno.serve({ onListen({ port } => listenPort = port })`. This becomes: `const server = Deno.serve({}); port = server.addr.port`. Changes: - Refactors `serve` overloads to split TLS out (in preparation for landing a place for the TLS SNI information) - Adds an `addr` field to `HttpServer` that matches the `addr` field of the corresponding `Deno.Listener`s.
This commit is contained in:
parent
93a40c1c64
commit
365e1f48f7
3 changed files with 137 additions and 32 deletions
122
cli/tsc/dts/lib.deno.ns.d.ts
vendored
122
cli/tsc/dts/lib.deno.ns.d.ts
vendored
|
@ -6217,7 +6217,7 @@ declare namespace Deno {
|
||||||
*/
|
*/
|
||||||
export function gid(): number | null;
|
export function gid(): number | null;
|
||||||
|
|
||||||
/** Information for a HTTP request.
|
/** Additional information for an HTTP request and its connection.
|
||||||
*
|
*
|
||||||
* @category HTTP Server
|
* @category HTTP Server
|
||||||
*/
|
*/
|
||||||
|
@ -6269,7 +6269,7 @@ declare namespace Deno {
|
||||||
onError?: (error: unknown) => Response | Promise<Response>;
|
onError?: (error: unknown) => Response | Promise<Response>;
|
||||||
|
|
||||||
/** The callback which is called when the server starts listening. */
|
/** 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.
|
/** Additional options which are used when opening a TLS (HTTPS) server.
|
||||||
|
@ -6304,6 +6304,14 @@ declare namespace Deno {
|
||||||
handler: ServeHandler;
|
handler: ServeHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @category HTTP Server
|
||||||
|
*/
|
||||||
|
export interface ServeTlsInit {
|
||||||
|
/** The handler to invoke to process each incoming request. */
|
||||||
|
handler: ServeHandler;
|
||||||
|
}
|
||||||
|
|
||||||
/** @category HTTP Server */
|
/** @category HTTP Server */
|
||||||
export interface ServeUnixOptions {
|
export interface ServeUnixOptions {
|
||||||
/** The unix domain socket path to listen on. */
|
/** The unix domain socket path to listen on. */
|
||||||
|
@ -6316,7 +6324,7 @@ declare namespace Deno {
|
||||||
onError?: (error: unknown) => Response | Promise<Response>;
|
onError?: (error: unknown) => Response | Promise<Response>;
|
||||||
|
|
||||||
/** The callback which is called when the server starts listening. */
|
/** 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.
|
/** Information for a unix domain socket HTTP request.
|
||||||
|
@ -6353,12 +6361,16 @@ declare namespace Deno {
|
||||||
*
|
*
|
||||||
* @category HTTP Server
|
* @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
|
/** A promise that resolves once server finishes - eg. when aborted using
|
||||||
* the signal passed to {@linkcode ServeOptions.signal}.
|
* the signal passed to {@linkcode ServeOptions.signal}.
|
||||||
*/
|
*/
|
||||||
finished: Promise<void>;
|
finished: Promise<void>;
|
||||||
|
|
||||||
|
/** The local address this server is listening on. */
|
||||||
|
addr: A;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make the server block the event loop from finishing.
|
* Make the server block the event loop from finishing.
|
||||||
*
|
*
|
||||||
|
@ -6395,7 +6407,7 @@ declare namespace Deno {
|
||||||
*
|
*
|
||||||
* @category HTTP Server
|
* @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.
|
/** Serves HTTP requests with the given option bag and handler.
|
||||||
*
|
*
|
||||||
* You can specify the socket path with `path` option.
|
* You can specify the socket path with `path` option.
|
||||||
|
@ -6444,7 +6456,67 @@ declare namespace Deno {
|
||||||
export function serve(
|
export function serve(
|
||||||
options: ServeUnixOptions,
|
options: ServeUnixOptions,
|
||||||
handler: ServeUnixHandler,
|
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.
|
/** Serves HTTP requests with the given option bag and handler.
|
||||||
*
|
*
|
||||||
* You can specify an object with a port and hostname option, which is the
|
* You can specify an object with a port and hostname option, which is the
|
||||||
|
@ -6503,11 +6575,10 @@ declare namespace Deno {
|
||||||
*/
|
*/
|
||||||
export function serve(
|
export function serve(
|
||||||
options:
|
options:
|
||||||
| ServeOptions
|
|
||||||
| ServeTlsOptions
|
| ServeTlsOptions
|
||||||
| (ServeTlsOptions & TlsCertifiedKeyOptions),
|
| (ServeTlsOptions & TlsCertifiedKeyOptions),
|
||||||
handler: ServeHandler,
|
handler: ServeHandler,
|
||||||
): HttpServer;
|
): HttpServer<Deno.NetAddr>;
|
||||||
/** Serves HTTP requests with the given option bag.
|
/** Serves HTTP requests with the given option bag.
|
||||||
*
|
*
|
||||||
* You can specify an object with the path option, which is the
|
* You can specify an object with the path option, which is the
|
||||||
|
@ -6534,7 +6605,7 @@ declare namespace Deno {
|
||||||
*/
|
*/
|
||||||
export function serve(
|
export function serve(
|
||||||
options: ServeUnixInit & ServeUnixOptions,
|
options: ServeUnixInit & ServeUnixOptions,
|
||||||
): HttpServer;
|
): HttpServer<Deno.UnixAddr>;
|
||||||
/** Serves HTTP requests with the given option bag.
|
/** Serves HTTP requests with the given option bag.
|
||||||
*
|
*
|
||||||
* You can specify an object with a port and hostname option, which is the
|
* You can specify an object with a port and hostname option, which is the
|
||||||
|
@ -6563,10 +6634,39 @@ declare namespace Deno {
|
||||||
export function serve(
|
export function serve(
|
||||||
options:
|
options:
|
||||||
& ServeInit
|
& 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
|
||||||
| (ServeTlsOptions & TlsCertifiedKeyOptions)
|
| (ServeTlsOptions & TlsCertifiedKeyOptions)
|
||||||
),
|
),
|
||||||
): HttpServer;
|
): HttpServer<Deno.NetAddr>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -552,7 +552,7 @@ function serve(arg1, arg2) {
|
||||||
const path = listener.addr.path;
|
const path = listener.addr.path;
|
||||||
return serveHttpOnListener(listener, signal, handler, onError, () => {
|
return serveHttpOnListener(listener, signal, handler, onError, () => {
|
||||||
if (options.onListen) {
|
if (options.onListen) {
|
||||||
options.onListen({ path });
|
options.onListen(listener.addr);
|
||||||
} else {
|
} else {
|
||||||
console.log(`Listening on ${path}`);
|
console.log(`Listening on ${path}`);
|
||||||
}
|
}
|
||||||
|
@ -593,19 +593,18 @@ function serve(arg1, arg2) {
|
||||||
listenOpts.port = listener.addr.port;
|
listenOpts.port = listener.addr.port;
|
||||||
}
|
}
|
||||||
|
|
||||||
const onListen = (scheme) => {
|
const addr = listener.addr;
|
||||||
// If the hostname is "0.0.0.0", we display "localhost" in console
|
// If the hostname is "0.0.0.0", we display "localhost" in console
|
||||||
// because browsers in Windows don't resolve "0.0.0.0".
|
// because browsers in Windows don't resolve "0.0.0.0".
|
||||||
// See the discussion in https://github.com/denoland/deno_std/issues/1165
|
// See the discussion in https://github.com/denoland/deno_std/issues/1165
|
||||||
const hostname = listenOpts.hostname == "0.0.0.0"
|
const hostname = addr.hostname == "0.0.0.0" ? "localhost" : addr.hostname;
|
||||||
? "localhost"
|
addr.hostname = hostname;
|
||||||
: listenOpts.hostname;
|
|
||||||
const port = listenOpts.port;
|
|
||||||
|
|
||||||
|
const onListen = (scheme) => {
|
||||||
if (options.onListen) {
|
if (options.onListen) {
|
||||||
options.onListen({ hostname, port });
|
options.onListen(addr);
|
||||||
} else {
|
} 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);
|
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);
|
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 ref = true;
|
||||||
let currentPromise = null;
|
let currentPromise = null;
|
||||||
|
|
||||||
|
@ -700,6 +699,7 @@ function serveHttpOn(context, callback) {
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
addr,
|
||||||
finished,
|
finished,
|
||||||
async shutdown() {
|
async shutdown() {
|
||||||
if (!context.closing && !context.closed) {
|
if (!context.closing && !context.closed) {
|
||||||
|
|
|
@ -43,7 +43,10 @@ function onListen(
|
||||||
}
|
}
|
||||||
|
|
||||||
async function makeServer(
|
async function makeServer(
|
||||||
handler: (req: Request) => Response | Promise<Response>,
|
handler: (
|
||||||
|
req: Request,
|
||||||
|
info: Deno.ServeHandlerInfo,
|
||||||
|
) => Response | Promise<Response>,
|
||||||
): Promise<
|
): Promise<
|
||||||
{
|
{
|
||||||
finished: Promise<void>;
|
finished: Promise<void>;
|
||||||
|
@ -432,7 +435,7 @@ Deno.test(async function httpServerRejectsOnAddrInUse() {
|
||||||
Deno.test({ permissions: { net: true } }, async function httpServerBasic() {
|
Deno.test({ permissions: { net: true } }, async function httpServerBasic() {
|
||||||
const ac = new AbortController();
|
const ac = new AbortController();
|
||||||
const deferred = Promise.withResolvers<void>();
|
const deferred = Promise.withResolvers<void>();
|
||||||
const listeningDeferred = Promise.withResolvers<void>();
|
const listeningDeferred = Promise.withResolvers<Deno.NetAddr>();
|
||||||
|
|
||||||
const server = Deno.serve({
|
const server = Deno.serve({
|
||||||
handler: async (request, { remoteAddr }) => {
|
handler: async (request, { remoteAddr }) => {
|
||||||
|
@ -447,11 +450,13 @@ Deno.test({ permissions: { net: true } }, async function httpServerBasic() {
|
||||||
},
|
},
|
||||||
port: servePort,
|
port: servePort,
|
||||||
signal: ac.signal,
|
signal: ac.signal,
|
||||||
onListen: onListen(listeningDeferred.resolve),
|
onListen: (addr) => listeningDeferred.resolve(addr),
|
||||||
onError: createOnErrorCb(ac),
|
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}/`, {
|
const resp = await fetch(`http://127.0.0.1:${servePort}/`, {
|
||||||
headers: { "connection": "close" },
|
headers: { "connection": "close" },
|
||||||
});
|
});
|
||||||
|
@ -472,7 +477,7 @@ Deno.test(
|
||||||
async function httpServerOnListener() {
|
async function httpServerOnListener() {
|
||||||
const ac = new AbortController();
|
const ac = new AbortController();
|
||||||
const deferred = Promise.withResolvers<void>();
|
const deferred = Promise.withResolvers<void>();
|
||||||
const listeningDeferred = Promise.withResolvers<void>();
|
const listeningDeferred = Promise.withResolvers();
|
||||||
const listener = Deno.listen({ port: servePort });
|
const listener = Deno.listen({ port: servePort });
|
||||||
const server = serveHttpOnListener(
|
const server = serveHttpOnListener(
|
||||||
listener,
|
listener,
|
||||||
|
@ -3812,7 +3817,7 @@ Deno.test(
|
||||||
permissions: { run: true, read: true, write: true },
|
permissions: { run: true, read: true, write: true },
|
||||||
},
|
},
|
||||||
async function httpServerUnixDomainSocket() {
|
async function httpServerUnixDomainSocket() {
|
||||||
const { promise, resolve } = Promise.withResolvers<{ path: string }>();
|
const { promise, resolve } = Promise.withResolvers<Deno.UnixAddr>();
|
||||||
const ac = new AbortController();
|
const ac = new AbortController();
|
||||||
const filePath = tmpUnixSocketPath();
|
const filePath = tmpUnixSocketPath();
|
||||||
const server = Deno.serve(
|
const server = Deno.serve(
|
||||||
|
@ -3830,7 +3835,7 @@ Deno.test(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
assertEquals(await promise, { path: filePath });
|
assertEquals((await promise).path, filePath);
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"hello world!",
|
"hello world!",
|
||||||
await curlRequest(["--unix-socket", filePath, "http://localhost"]),
|
await curlRequest(["--unix-socket", filePath, "http://localhost"]),
|
||||||
|
|
Loading…
Reference in a new issue