1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Add process of cleaning up lens proxy startup if down quickly

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2022-11-03 16:13:24 -04:00
parent b86bc6d84c
commit 4c1d07693f
5 changed files with 133 additions and 60 deletions

View File

@ -22,6 +22,10 @@ import type { GetClusterForRequest } from "../cluster/get-cluster-for-request.in
export type ServerIncomingMessage = SetRequired<http.IncomingMessage, "url" | "method">;
export type ProxyRequestHandler = (args: ProxyApiRequestArgs) => void | Promise<void>;
export interface ListenOptions {
signal: AbortSignal;
}
interface Dependencies {
getClusterForRequest: GetClusterForRequest;
shellApiRequest: ProxyRequestHandler;
@ -108,10 +112,14 @@ export class LensProxy {
*
* Resolves with the port number that was picked
*/
private attemptToListen(): Promise<number> {
private attemptToListen(options: ListenOptions): Promise<number> {
return new Promise<number>((resolve, reject) => {
this.proxyServer.listen(0, "127.0.0.1");
options.signal.addEventListener("abort", () => {
this.close();
});
this.proxyServer
.once("listening", () => {
this.proxyServer.removeAllListeners("error"); // don't reject the promise
@ -140,12 +148,12 @@ export class LensProxy {
* @resolves After the server is listening on a good port
* @rejects if there is an error before that happens
*/
async listen(): Promise<void> {
async listen(options: ListenOptions): Promise<void> {
const seenPorts = new Set<number>();
while(true) {
this.proxyServer?.close();
const port = await this.attemptToListen();
const port = await this.attemptToListen(options);
if (!disallowedPorts.has(port)) {
// We didn't get a port that would result in an ERR_UNSAFE_PORT error, use it

View File

@ -4,16 +4,31 @@
*/
import { getInjectable } from "@ogre-tools/injectable";
import lensFetchInjectable from "../../../../common/fetch/lens-fetch.injectable";
import type { AbortSignal as NonStandardAbortSignal } from "abort-controller";
export interface RequestOptions {
signal: AbortSignal;
}
export type RequestAppVersionViaProxy = (options: RequestOptions) => Promise<string>;
const requestAppVersionViaProxyInjectable = getInjectable({
id: "request-app-version-via-proxy",
instantiate: (di) => {
instantiate: (di): RequestAppVersionViaProxy => {
const lensFetch = di.inject(lensFetchInjectable);
return async () => {
const response = await lensFetch("/version");
return async (options) => {
const response = await lensFetch("/version", {
signal: options.signal as NonStandardAbortSignal,
});
return (await response.json() as { version: string }).version;
if (response.status !== 200) {
throw new Error(`Retrieving version failed: ${response.statusText}`);
}
const body = await response.json() as { version: string };
return body.version;
};
},
});

View File

@ -3,68 +3,19 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import exitAppInjectable from "../../../electron-app/features/exit-app.injectable";
import lensProxyInjectable from "../../../lens-proxy/lens-proxy.injectable";
import loggerInjectable from "../../../../common/logger.injectable";
import isWindowsInjectable from "../../../../common/vars/is-windows.injectable";
import showErrorPopupInjectable from "../../../electron-app/features/show-error-popup.injectable";
import { beforeApplicationIsLoadingInjectionToken } from "../../runnable-tokens/before-application-is-loading-injection-token";
import buildVersionInjectable from "../../../vars/build-version/build-version.injectable";
import requestAppVersionViaProxyInjectable from "./request-app-version.injectable";
import setupLensProxyStartableStoppableInjectable from "./startable-stoppable.injectable";
const setupLensProxyInjectable = getInjectable({
id: "setup-lens-proxy",
instantiate: (di) => {
const lensProxy = di.inject(lensProxyInjectable);
const exitApp = di.inject(exitAppInjectable);
const logger = di.inject(loggerInjectable);
const requestAppVersionViaProxy = di.inject(requestAppVersionViaProxyInjectable);
const isWindows = di.inject(isWindowsInjectable);
const showErrorPopup = di.inject(showErrorPopupInjectable);
const buildVersion = di.inject(buildVersionInjectable);
const setupLensProxyStartableStoppable = di.inject(setupLensProxyStartableStoppableInjectable);
return {
id: "setup-lens-proxy",
run: async () => {
try {
logger.info("🔌 Starting LensProxy");
await lensProxy.listen(); // lensProxy.port available
} catch (error: any) {
showErrorPopup("Lens Error", `Could not start proxy: ${error?.message || "unknown error"}`);
return exitApp();
}
// test proxy connection
try {
logger.info("🔎 Testing LensProxy connection ...");
const versionFromProxy = await requestAppVersionViaProxy();
if (buildVersion.get() !== versionFromProxy) {
logger.error("Proxy server responded with invalid response");
return exitApp();
}
logger.info("⚡ LensProxy connection OK");
} catch (error) {
logger.error(`🛑 LensProxy: failed connection test: ${error}`);
const hostsPath = isWindows
? "C:\\windows\\system32\\drivers\\etc\\hosts"
: "/etc/hosts";
const message = [
`Failed connection test: ${error}`,
"Check to make sure that no other versions of Lens are running",
`Check ${hostsPath} to make sure that it is clean and that the localhost loopback is at the top and set to 127.0.0.1`,
"If you have HTTP_PROXY or http_proxy set in your environment, make sure that the localhost and the ipv4 loopback address 127.0.0.1 are added to the NO_PROXY environment variable.",
];
showErrorPopup("Lens Proxy Error", message.join("\n\n"));
return exitApp();
}
run: () => {
setupLensProxyStartableStoppable.start();
},
};
},

View File

@ -0,0 +1,75 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import loggerInjectable from "../../../../common/logger.injectable";
import { getStartableStoppable } from "../../../../common/utils/get-startable-stoppable";
import isWindowsInjectable from "../../../../common/vars/is-windows.injectable";
import exitAppInjectable from "../../../electron-app/features/exit-app.injectable";
import showErrorPopupInjectable from "../../../electron-app/features/show-error-popup.injectable";
import lensProxyInjectable from "../../../lens-proxy/lens-proxy.injectable";
import buildVersionInjectable from "../../../vars/build-version/build-version.injectable";
import requestAppVersionViaProxyInjectable from "./request-app-version.injectable";
const setupLensProxyStartableStoppableInjectable = getInjectable({
id: "setup-lens-proxy-startable-stoppable",
instantiate: (di) => {
const lensProxy = di.inject(lensProxyInjectable);
const exitApp = di.inject(exitAppInjectable);
const logger = di.inject(loggerInjectable);
const requestAppVersionViaProxy = di.inject(requestAppVersionViaProxyInjectable);
const isWindows = di.inject(isWindowsInjectable);
const showErrorPopup = di.inject(showErrorPopupInjectable);
const buildVersion = di.inject(buildVersionInjectable);
return getStartableStoppable("setup-lens-proxy", () => {
const controller = new AbortController();
(async () => {
try {
logger.info("🔌 Starting LensProxy");
await lensProxy.listen({ signal: controller.signal }); // lensProxy.port available
} catch (error: any) {
showErrorPopup("Lens Error", `Could not start proxy: ${error?.message || "unknown error"}`);
return exitApp();
}
// test proxy connection
try {
logger.info("🔎 Testing LensProxy connection ...");
const versionFromProxy = await requestAppVersionViaProxy({ signal: controller.signal });
if (buildVersion.get() !== versionFromProxy) {
logger.error("Proxy server responded with invalid response");
return exitApp();
}
logger.info("⚡ LensProxy connection OK");
} catch (error) {
logger.error(`🛑 LensProxy: failed connection test: ${error}`);
const hostsPath = isWindows
? "C:\\windows\\system32\\drivers\\etc\\hosts"
: "/etc/hosts";
const message = [
`Failed connection test: ${error}`,
"Check to make sure that no other versions of Lens are running",
`Check ${hostsPath} to make sure that it is clean and that the localhost loopback is at the top and set to 127.0.0.1`,
"If you have HTTP_PROXY or http_proxy set in your environment, make sure that the localhost and the ipv4 loopback address 127.0.0.1 are added to the NO_PROXY environment variable.",
];
showErrorPopup("Lens Proxy Error", message.join("\n\n"));
return exitApp();
}
})();
return () => controller.abort();
});
},
});
export default setupLensProxyStartableStoppableInjectable;

View File

@ -0,0 +1,24 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { beforeQuitOfBackEndInjectionToken } from "../../runnable-tokens/before-quit-of-back-end-injection-token";
import setupLensProxyStartableStoppableInjectable from "./startable-stoppable.injectable";
const stopSettingUpLensProxyInjectable = getInjectable({
id: "stop-setting-up-lens-proxy",
instantiate: (di) => {
const setupLensProxyStartableStoppable = di.inject(setupLensProxyStartableStoppableInjectable);
return {
id: "stop-setting-up-lens-proxy",
run: () => {
setupLensProxyStartableStoppable.stop();
},
};
},
injectionToken: beforeQuitOfBackEndInjectionToken,
});
export default stopSettingUpLensProxyInjectable;