Remove UDS and thus the need for a proxy server
Summary: This change removes the UDS support and thus the need for having a proxy server. Reviewed By: antonk52 Differential Revision: D48265244 fbshipit-source-id: c76bb4afba63959ddd17901b3887aa278b000beb
This commit is contained in:
committed by
Facebook GitHub Bot
parent
7f3f1c0507
commit
b5ed57b7d0
@@ -47,7 +47,6 @@ import path from 'path';
|
|||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import {ElectronIpcClientRenderer} from './electronIpc';
|
import {ElectronIpcClientRenderer} from './electronIpc';
|
||||||
import {checkSocketInUse, makeSocketPath} from 'flipper-server-core';
|
|
||||||
import {KeytarModule} from 'flipper-server-core/src/utils/keytar';
|
import {KeytarModule} from 'flipper-server-core/src/utils/keytar';
|
||||||
import {initCompanionEnv} from 'flipper-server-companion';
|
import {initCompanionEnv} from 'flipper-server-companion';
|
||||||
import ReconnectingWebSocket from 'reconnecting-websocket';
|
import ReconnectingWebSocket from 'reconnecting-websocket';
|
||||||
@@ -118,8 +117,6 @@ async function getFlipperServer(
|
|||||||
|
|
||||||
const serverUsageEnabled = gatekeepers['flipper_desktop_use_server'];
|
const serverUsageEnabled = gatekeepers['flipper_desktop_use_server'];
|
||||||
const settings = await loadSettings();
|
const settings = await loadSettings();
|
||||||
|
|
||||||
const socketPath = await makeSocketPath();
|
|
||||||
const port = 52342;
|
const port = 52342;
|
||||||
/**
|
/**
|
||||||
* Only attempt to use the auth token if one is available. Otherwise,
|
* Only attempt to use the auth token if one is available. Otherwise,
|
||||||
@@ -134,7 +131,6 @@ async function getFlipperServer(
|
|||||||
// check first with the actual TCP socket
|
// check first with the actual TCP socket
|
||||||
const searchParams = new URLSearchParams(token ? {token} : {});
|
const searchParams = new URLSearchParams(token ? {token} : {});
|
||||||
const TCPconnectionURL = new URL(`ws://localhost:${port}?${searchParams}`);
|
const TCPconnectionURL = new URL(`ws://localhost:${port}?${searchParams}`);
|
||||||
const UDSconnectionURL = new URL(`ws+unix://${socketPath}`);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to shutdown a running instance of Flipper server.
|
* Attempt to shutdown a running instance of Flipper server.
|
||||||
@@ -159,12 +155,7 @@ async function getFlipperServer(
|
|||||||
*/
|
*/
|
||||||
if (await checkPortInUse(port)) {
|
if (await checkPortInUse(port)) {
|
||||||
console.warn(`[flipper-server] TCP port ${port} is already in use.`);
|
console.warn(`[flipper-server] TCP port ${port} is already in use.`);
|
||||||
|
|
||||||
await shutdown(TCPconnectionURL);
|
await shutdown(TCPconnectionURL);
|
||||||
} else if (await checkSocketInUse(socketPath)) {
|
|
||||||
console.warn(`[flipper-server] UDS socket is already in use.`);
|
|
||||||
|
|
||||||
await shutdown(UDSconnectionURL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const [homePath, tempPath, desktopPath] = await Promise.all([
|
const [homePath, tempPath, desktopPath] = await Promise.all([
|
||||||
@@ -223,9 +214,7 @@ async function getFlipperServer(
|
|||||||
await server.connect();
|
await server.connect();
|
||||||
await readyForIncomingConnections(server, companionEnv);
|
await readyForIncomingConnections(server, companionEnv);
|
||||||
|
|
||||||
return getExternalServer(
|
return getExternalServer(TCPconnectionURL);
|
||||||
os.platform() === 'win32' ? TCPconnectionURL : UDSconnectionURL,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return getEmbeddedServer();
|
return getEmbeddedServer();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
"flipper-server-companion": "0.0.0",
|
"flipper-server-companion": "0.0.0",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"fs-extra": "^11.1.0",
|
"fs-extra": "^11.1.0",
|
||||||
"http-proxy": "^1.18.1",
|
|
||||||
"invariant": "^2.2.4",
|
"invariant": "^2.2.4",
|
||||||
"js-base64": "^3.7.5",
|
"js-base64": "^3.7.5",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
@@ -52,7 +51,6 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/archiver": "^5.3.1",
|
"@types/archiver": "^5.3.1",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/http-proxy": "^1.17.8",
|
|
||||||
"@types/invariant": "^2.2.35",
|
"@types/invariant": "^2.2.35",
|
||||||
"@types/jsonwebtoken": "^9.0.1",
|
"@types/jsonwebtoken": "^9.0.1",
|
||||||
"@types/memorystream": "^0.3.0",
|
"@types/memorystream": "^0.3.0",
|
||||||
|
|||||||
@@ -7,18 +7,13 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import os from 'os';
|
|
||||||
|
|
||||||
import express, {Express} from 'express';
|
import express, {Express} from 'express';
|
||||||
import http, {ServerResponse} from 'http';
|
import http from 'http';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import {ServerOptions, VerifyClientCallbackSync, WebSocketServer} from 'ws';
|
import {ServerOptions, VerifyClientCallbackSync, WebSocketServer} from 'ws';
|
||||||
import {WEBSOCKET_MAX_MESSAGE_SIZE} from '../app-connectivity/ServerWebSocket';
|
import {WEBSOCKET_MAX_MESSAGE_SIZE} from '../app-connectivity/ServerWebSocket';
|
||||||
import {parse} from 'url';
|
import {parse} from 'url';
|
||||||
import {makeSocketPath, checkSocketInUse} from './utilities';
|
|
||||||
|
|
||||||
import proxy from 'http-proxy';
|
|
||||||
import exitHook from 'exit-hook';
|
import exitHook from 'exit-hook';
|
||||||
import {attachSocketServer} from './attachSocketServer';
|
import {attachSocketServer} from './attachSocketServer';
|
||||||
import {FlipperServerImpl} from '../FlipperServerImpl';
|
import {FlipperServerImpl} from '../FlipperServerImpl';
|
||||||
@@ -124,102 +119,23 @@ async function startHTTPServer(config: Config): Promise<{
|
|||||||
|
|
||||||
app.use(express.static(config.staticPath));
|
app.use(express.static(config.staticPath));
|
||||||
|
|
||||||
return startProxyServer(config, app);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates and starts the HTTP and proxy servers.
|
|
||||||
* @param config Server configuration.
|
|
||||||
* @param app Express app.
|
|
||||||
* @returns Returns both the app and server configured and
|
|
||||||
* listening.
|
|
||||||
*/
|
|
||||||
async function startProxyServer(
|
|
||||||
config: Config,
|
|
||||||
app: Express,
|
|
||||||
): Promise<{
|
|
||||||
app: Express;
|
|
||||||
server: http.Server;
|
|
||||||
socket: WebSocketServer;
|
|
||||||
readyForIncomingConnections: ReadyForConnections;
|
|
||||||
}> {
|
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
const socket = addWebsocket(server, config);
|
const socket = attachWS(server, config);
|
||||||
|
|
||||||
// For now, we only support domain socket access on POSIX-like systems.
|
|
||||||
// On Windows, a proxy is not created and the server starts
|
|
||||||
// listening at the specified port.
|
|
||||||
if (os.platform() === 'win32') {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
console.log(`Starting server on http://localhost:${config.port}`);
|
|
||||||
const readyForIncomingConnections = (
|
|
||||||
serverImpl: FlipperServerImpl,
|
|
||||||
companionEnv: FlipperServerCompanionEnv,
|
|
||||||
): Promise<void> => {
|
|
||||||
attachSocketServer(socket, serverImpl, companionEnv);
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
server.listen(config.port, undefined, () => resolve());
|
|
||||||
});
|
|
||||||
};
|
|
||||||
resolve({app, server, socket, readyForIncomingConnections});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const socketPath = await makeSocketPath();
|
|
||||||
if (await checkSocketInUse(socketPath)) {
|
|
||||||
console.warn(
|
|
||||||
`Cannot start flipper-server because socket ${socketPath} is in use.`,
|
|
||||||
);
|
|
||||||
tracker.track('server-socket-already-in-use', {});
|
|
||||||
} else {
|
|
||||||
console.info(`Cleaning up stale socket ${socketPath}`);
|
|
||||||
await fs.rm(socketPath, {force: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
const proxyServer: proxy | undefined = proxy.createProxyServer({
|
|
||||||
target: {host: 'localhost', port: 0, socketPath},
|
|
||||||
autoRewrite: true,
|
|
||||||
ws: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Starting socket server on ', socketPath);
|
|
||||||
if (proxyServer) {
|
|
||||||
console.log(`Starting proxy server on http://localhost:${config.port}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
exitHook(() => {
|
exitHook(() => {
|
||||||
console.log('Shutdown server');
|
console.log('Shutdown server');
|
||||||
proxyServer?.close();
|
|
||||||
server.close();
|
server.close();
|
||||||
|
|
||||||
console.log('Cleaning up socket on exit:', socketPath);
|
|
||||||
// This *must* run synchronously and we're not blocking any UI loop by definition.
|
|
||||||
// eslint-disable-next-line node/no-sync
|
|
||||||
fs.rmSync(socketPath, {force: true});
|
|
||||||
});
|
|
||||||
|
|
||||||
proxyServer?.on('error', (err, _req, _res) => {
|
|
||||||
console.warn('Error in proxy server:', err);
|
|
||||||
tracker.track('server-proxy-error', {error: err.message});
|
|
||||||
|
|
||||||
// As it stands, if the proxy fails, which is the one
|
|
||||||
// listening on the supplied TCP port, then we should exit.
|
|
||||||
// Otherwise we risk staying in an invalid state, unable
|
|
||||||
// to recover, and thus preventing us to serve incoming requests.
|
|
||||||
// Bear in mind that exiting the process will trigger the exit hook
|
|
||||||
// defined above which will clean and close the sockets.
|
|
||||||
process.exit(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
console.log(`Starting server on http://localhost:${config.port}`);
|
||||||
const readyForIncomingConnections = (
|
const readyForIncomingConnections = (
|
||||||
serverImpl: FlipperServerImpl,
|
serverImpl: FlipperServerImpl,
|
||||||
companionEnv: FlipperServerCompanionEnv,
|
companionEnv: FlipperServerCompanionEnv,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
attachSocketServer(socket, serverImpl, companionEnv);
|
attachSocketServer(socket, serverImpl, companionEnv);
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
proxyServer?.listen(config.port);
|
server.listen(config.port, undefined, () => {
|
||||||
server.listen(socketPath, undefined, () => {
|
|
||||||
tracker.track('server-started', {
|
tracker.track('server-started', {
|
||||||
port: config.port,
|
port: config.port,
|
||||||
});
|
});
|
||||||
@@ -238,7 +154,7 @@ async function startProxyServer(
|
|||||||
* incoming connections origin.
|
* incoming connections origin.
|
||||||
* @returns Returns the created WS.
|
* @returns Returns the created WS.
|
||||||
*/
|
*/
|
||||||
function addWebsocket(server: http.Server, config: Config) {
|
function attachWS(server: http.Server, config: Config) {
|
||||||
const localhost = 'localhost';
|
const localhost = 'localhost';
|
||||||
const localhostIPV4 = `localhost:${config.port}`;
|
const localhostIPV4 = `localhost:${config.port}`;
|
||||||
const localhostIPV6 = `[::1]:${config.port}`;
|
const localhostIPV6 = `[::1]:${config.port}`;
|
||||||
|
|||||||
@@ -39,63 +39,3 @@ export async function checkPortInUse(port: number): Promise<boolean> {
|
|||||||
.listen(port);
|
.listen(port);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a socket is in used for given path.
|
|
||||||
* If the path doesn't exist then is not in use. If it does,
|
|
||||||
* create a socket client and attempt to connect to it.
|
|
||||||
* If the connection is established, then there's a process
|
|
||||||
* already listening. Otherwise, it kind of indicates the
|
|
||||||
* contrary.
|
|
||||||
* @param path Path used instead of port number.
|
|
||||||
* @returns True or false dependning on whether the socket is in
|
|
||||||
* use or not.
|
|
||||||
*/
|
|
||||||
export async function checkSocketInUse(path: string): Promise<boolean> {
|
|
||||||
if (!(await fs.pathExists(path))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return new Promise((resolve, _reject) => {
|
|
||||||
const client = net
|
|
||||||
.createConnection(path, () => {
|
|
||||||
resolve(true);
|
|
||||||
client.destroy();
|
|
||||||
})
|
|
||||||
.on('error', (e) => {
|
|
||||||
if (e.message.includes('ECONNREFUSED')) {
|
|
||||||
resolve(false);
|
|
||||||
} else {
|
|
||||||
console.warn(
|
|
||||||
`[conn] Socket ${path} is in use, but we don't know why.`,
|
|
||||||
e,
|
|
||||||
);
|
|
||||||
resolve(false);
|
|
||||||
}
|
|
||||||
client.destroy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a socket path. Used to open the socket at location.
|
|
||||||
* Format: flipper-server-${userInfo}.sock
|
|
||||||
* @returns The created socket path.
|
|
||||||
*/
|
|
||||||
export async function makeSocketPath(): Promise<string> {
|
|
||||||
const runtimeDir = xdgBasedir.runtime || '/tmp';
|
|
||||||
await fs.mkdirp(runtimeDir);
|
|
||||||
const filename = `flipper-server-${os.userInfo().uid}.sock`;
|
|
||||||
const path = `${runtimeDir}/${filename}`;
|
|
||||||
|
|
||||||
// Depending on the OS this is between 104 and 108:
|
|
||||||
// https://unix.stackexchange.com/a/367012/10198
|
|
||||||
if (path.length >= 104) {
|
|
||||||
console.warn(
|
|
||||||
'Ignoring XDG_RUNTIME_DIR as it would exceed the total limit for domain socket paths. See man 7 unix.',
|
|
||||||
);
|
|
||||||
// Even with the INT32_MAX userid, we should have plenty of room.
|
|
||||||
return `/tmp/${filename}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user