Skip to content

Commit c6d4c47

Browse files
authored
refactor: App.prototype.listen() (#2907)
1 parent c0e2b80 commit c6d4c47

File tree

1 file changed

+77
-70
lines changed

1 file changed

+77
-70
lines changed

src/app.ts

Lines changed: 77 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,80 @@ export type ListenOptions =
4444
& {
4545
remoteAddress?: string;
4646
};
47+
function createOnListen(
48+
basePath: string,
49+
options: ListenOptions,
50+
): (localAddr: Deno.NetAddr) => void {
51+
return (params) => {
52+
// Don't spam logs with this on live deployments
53+
if (DENO_DEPLOYMENT_ID) return;
54+
55+
const pathname = basePath + "/";
56+
const protocol = "key" in options && options.key && options.cert
57+
? "https:"
58+
: "http:";
59+
60+
let hostname = params.hostname;
61+
// Windows being windows...
62+
if (
63+
Deno.build.os === "windows" &&
64+
(hostname === "0.0.0.0" || hostname === "::")
65+
) {
66+
hostname = "localhost";
67+
}
68+
// Work around https://github.com/denoland/deno/issues/23650
69+
hostname = hostname.startsWith("::") ? `[${hostname}]` : hostname;
70+
71+
// deno-lint-ignore no-console
72+
console.log();
73+
// deno-lint-ignore no-console
74+
console.log(
75+
colors.bgRgb8(colors.rgb8(" 🍋 Fresh ready ", 0), 121),
76+
);
77+
const sep = options.remoteAddress ? "" : "\n";
78+
const space = options.remoteAddress ? " " : "";
79+
80+
const localLabel = colors.bold("Local:");
81+
const address = colors.cyan(
82+
`${protocol}//${hostname}:${params.port}${pathname}`,
83+
);
84+
// deno-lint-ignore no-console
85+
console.log(` ${localLabel} ${space}${address}${sep}`);
86+
if (options.remoteAddress) {
87+
const remoteLabel = colors.bold("Remote:");
88+
const remoteAddress = colors.cyan(options.remoteAddress);
89+
// deno-lint-ignore no-console
90+
console.log(` ${remoteLabel} ${remoteAddress}\n`);
91+
}
92+
};
93+
}
4794

48-
Deno.serve;
95+
async function listenOnFreePort(
96+
options: ListenOptions,
97+
handler: (
98+
request: Request,
99+
info?: Deno.ServeHandlerInfo,
100+
) => Promise<Response>,
101+
) {
102+
// No port specified, check for a free port. Instead of picking just
103+
// any port we'll check if the next one is free for UX reasons.
104+
// That way the user only needs to increment a number when running
105+
// multiple apps vs having to remember completely different ports.
106+
let firstError = null;
107+
for (let port = 8000; port < 8020; port++) {
108+
try {
109+
return await Deno.serve({ ...options, port }, handler);
110+
} catch (err) {
111+
if (err instanceof Deno.errors.AddrInUse) {
112+
// Throw first EADDRINUSE error if no port is free
113+
if (!firstError) firstError = err;
114+
continue;
115+
}
116+
throw err;
117+
}
118+
}
119+
throw firstError;
120+
}
49121

50122
export let getRouter: <State>(app: App<State>) => Router<MiddlewareFn<State>>;
51123
// deno-lint-ignore no-explicit-any
@@ -248,81 +320,16 @@ export class App<State> {
248320

249321
async listen(options: ListenOptions = {}): Promise<void> {
250322
if (!options.onListen) {
251-
options.onListen = (params) => {
252-
const pathname = (this.config.basePath) + "/";
253-
const protocol = "key" in options && options.key && options.cert
254-
? "https:"
255-
: "http:";
256-
257-
let hostname = params.hostname;
258-
// Windows being windows...
259-
if (
260-
Deno.build.os === "windows" &&
261-
(hostname === "0.0.0.0" || hostname === "::")
262-
) {
263-
hostname = "localhost";
264-
}
265-
// Work around https://github.com/denoland/deno/issues/23650
266-
hostname = hostname.startsWith("::") ? `[${hostname}]` : hostname;
267-
const address = colors.cyan(
268-
`${protocol}//${hostname}:${params.port}${pathname}`,
269-
);
270-
const localLabel = colors.bold("Local:");
271-
272-
// Don't spam logs with this on live deployments
273-
if (!DENO_DEPLOYMENT_ID) {
274-
// deno-lint-ignore no-console
275-
console.log();
276-
// deno-lint-ignore no-console
277-
console.log(
278-
colors.bgRgb8(colors.rgb8(" 🍋 Fresh ready ", 0), 121),
279-
);
280-
const sep = options.remoteAddress ? "" : "\n";
281-
const space = options.remoteAddress ? " " : "";
282-
// deno-lint-ignore no-console
283-
console.log(` ${localLabel} ${space}${address}${sep}`);
284-
if (options.remoteAddress) {
285-
const remoteLabel = colors.bold("Remote:");
286-
const remoteAddress = colors.cyan(options.remoteAddress);
287-
// deno-lint-ignore no-console
288-
console.log(` ${remoteLabel} ${remoteAddress}\n`);
289-
}
290-
}
291-
};
323+
options.onListen = createOnListen(this.config.basePath, options);
292324
}
293325

294326
const handler = await this.handler();
295327
if (options.port) {
296328
await Deno.serve(options, handler);
297-
} else {
298-
// No port specified, check for a free port. Instead of picking just
299-
// any port we'll check if the next one is free for UX reasons.
300-
// That way the user only needs to increment a number when running
301-
// multiple apps vs having to remember completely different ports.
302-
let firstError;
303-
for (let port = 8000; port < 8020; port++) {
304-
try {
305-
await Deno.serve({ ...options, port }, handler);
306-
firstError = undefined;
307-
break;
308-
} catch (err) {
309-
if (err instanceof Deno.errors.AddrInUse) {
310-
// Throw first EADDRINUSE error
311-
// if no port is free
312-
if (!firstError) {
313-
firstError = err;
314-
}
315-
continue;
316-
}
317-
318-
throw err;
319-
}
320-
}
321-
322-
if (firstError) {
323-
throw firstError;
324-
}
329+
return;
325330
}
331+
332+
await listenOnFreePort(options, handler);
326333
}
327334
}
328335

0 commit comments

Comments
 (0)