UDS/TCP options

Summary:
Provide an option to enable/disable TCP connections on flipper-server.

The only change at this stage is that Flipper Desktop will use UDS to connect to flipper-server.

Reviewed By: passy

Differential Revision: D37519656

fbshipit-source-id: 3d02084666fde532ec76134edf8cf6a231060a48
This commit is contained in:
Lorenzo Blasa
2022-06-29 15:01:05 -07:00
committed by Facebook GitHub Bot
parent f46cf2b0ce
commit 646b9d5a5d
9 changed files with 110 additions and 35 deletions

View File

@@ -22,7 +22,9 @@
"fs-extra": "^10.1.0", "fs-extra": "^10.1.0",
"invariant": "^2.2.2", "invariant": "^2.2.2",
"metro-runtime": "^0.70.2", "metro-runtime": "^0.70.2",
"pretty-format": "^27.5.0" "pretty-format": "^27.5.0",
"reconnecting-websocket": "^4.4.0",
"ws": "8.8.0"
}, },
"optionalDependencies": {}, "optionalDependencies": {},
"devDependencies": { "devDependencies": {

View File

@@ -14,7 +14,10 @@ import {
_setGlobalInteractionReporter, _setGlobalInteractionReporter,
_LoggerContext, _LoggerContext,
} from 'flipper-plugin'; } from 'flipper-plugin';
import {createFlipperServer, FlipperServerState} from 'flipper-frontend-core'; import {
createFlipperServerWithSocket,
FlipperServerState,
} from 'flipper-frontend-core';
import { import {
FlipperServerImpl, FlipperServerImpl,
getEnvironmentInfo, getEnvironmentInfo,
@@ -45,6 +48,8 @@ import {ElectronIpcClientRenderer} from './electronIpc';
import {checkSocketInUse, makeSocketPath} from 'flipper-server-core'; 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 WS from 'ws';
enableMapSet(); enableMapSet();
@@ -70,10 +75,13 @@ async function getKeytarModule(staticPath: string): Promise<KeytarModule> {
return keytar; return keytar;
} }
async function getExternalServer() { async function getExternalServer(path: string) {
const server = await createFlipperServer( const options = {
'localhost', WebSocket: WS,
52342, };
const socket = new ReconnectingWebSocket(`ws+unix://${path}`, [], options);
const server = await createFlipperServerWithSocket(
socket,
(_state: FlipperServerState) => {}, (_state: FlipperServerState) => {},
); );
return server; return server;
@@ -105,7 +113,7 @@ async function getFlipperServer(
const getEmbeddedServer = async () => { const getEmbeddedServer = async () => {
if (serverRunning) { if (serverRunning) {
const server = await getExternalServer(); const server = await getExternalServer(socketPath);
await server.exec('shutdown').catch(() => { await server.exec('shutdown').catch(() => {
/** shutdown will ultimately make this request fail, ignore error. */ /** shutdown will ultimately make this request fail, ignore error. */
}); });
@@ -114,7 +122,6 @@ async function getFlipperServer(
{ {
environmentInfo, environmentInfo,
env: parseEnvironmentVariables(env), env: parseEnvironmentVariables(env),
// TODO: make username parameterizable
gatekeepers: gatekeepers, gatekeepers: gatekeepers,
paths: { paths: {
appPath, appPath,
@@ -142,9 +149,10 @@ async function getFlipperServer(
console.info('flipper-server: not running/listening, start'); console.info('flipper-server: not running/listening, start');
const {readyForIncomingConnections} = await startServer({ const {readyForIncomingConnections} = await startServer({
port: 52342,
staticDir: staticPath, staticDir: staticPath,
entry: 'index.web.dev.html', entry: 'index.web.dev.html',
tcp: false,
port: 52342,
}); });
const server = await startFlipperServer( const server = await startFlipperServer(
@@ -167,7 +175,7 @@ async function getFlipperServer(
tailServerLogs(path.join(staticPath, loggerOutputFile)); tailServerLogs(path.join(staticPath, loggerOutputFile));
} }
return getExternalServer(); return getExternalServer(socketPath);
} }
return getEmbeddedServer(); return getEmbeddedServer();
} }

View File

@@ -28,6 +28,14 @@ export function createFlipperServer(
host: string, host: string,
port: number, port: number,
onStateChange: (state: FlipperServerState) => void, onStateChange: (state: FlipperServerState) => void,
): Promise<FlipperServer> {
const socket = new ReconnectingWebSocket(`ws://${host}:${port}`);
return createFlipperServerWithSocket(socket, onStateChange);
}
export function createFlipperServerWithSocket(
socket: ReconnectingWebSocket,
onStateChange: (state: FlipperServerState) => void,
): Promise<FlipperServer> { ): Promise<FlipperServer> {
onStateChange(FlipperServerState.CONNECTING); onStateChange(FlipperServerState.CONNECTING);
@@ -40,7 +48,6 @@ export function createFlipperServer(
const eventEmitter = new EventEmitter(); const eventEmitter = new EventEmitter();
const socket = new ReconnectingWebSocket(`ws://${host}:${port}`);
const pendingRequests: Map< const pendingRequests: Map<
number, number,
{ {

View File

@@ -13,7 +13,7 @@ import express, {Express} from 'express';
import http, {ServerResponse} from 'http'; import http, {ServerResponse} from 'http';
import path from 'path'; import path from 'path';
import fs from 'fs-extra'; import fs from 'fs-extra';
import {VerifyClientCallbackSync, WebSocketServer} from 'ws'; import {ServerOptions, VerifyClientCallbackSync, WebSocketServer} from 'ws';
import {WEBSOCKET_MAX_MESSAGE_SIZE} from '../comms/ServerWebSocket'; import {WEBSOCKET_MAX_MESSAGE_SIZE} from '../comms/ServerWebSocket';
import {parse} from 'url'; import {parse} from 'url';
import {makeSocketPath, checkSocketInUse} from './utilities'; import {makeSocketPath, checkSocketInUse} from './utilities';
@@ -28,6 +28,7 @@ type Config = {
port: number; port: number;
staticDir: string; staticDir: string;
entry: string; entry: string;
tcp: boolean;
}; };
type ReadyForConnections = ( type ReadyForConnections = (
@@ -108,6 +109,12 @@ async function startProxyServer(
// On Windows, a proxy is not created and the server starts // On Windows, a proxy is not created and the server starts
// listening at the specified port. // listening at the specified port.
if (os.platform() === 'win32') { if (os.platform() === 'win32') {
if (!config.tcp) {
console.error(
'No port was supplied and domain socket access is not available for non-POSIX systems, unable to start server',
);
process.exit(1);
}
return new Promise((resolve) => { return new Promise((resolve) => {
console.log(`Starting server on http://localhost:${config.port}`); console.log(`Starting server on http://localhost:${config.port}`);
const readyForIncomingConnections = (): Promise<void> => { const readyForIncomingConnections = (): Promise<void> => {
@@ -129,17 +136,22 @@ async function startProxyServer(
await fs.rm(socketPath, {force: true}); await fs.rm(socketPath, {force: true});
} }
const proxyServer = proxy.createProxyServer({ const proxyServer: proxy | undefined = config.tcp
target: {host: 'localhost', port: 0, socketPath}, ? proxy.createProxyServer({
autoRewrite: true, target: {host: 'localhost', port: 0, socketPath},
ws: true, autoRewrite: true,
}); ws: true,
})
: undefined;
console.log('Starting socket server on ', socketPath); console.log('Starting socket server on ', socketPath);
console.log(`Starting proxy server on http://localhost:${config.port}`); if (proxyServer) {
console.log(`Starting proxy server on http://localhost:${config.port}`);
}
exitHook(() => { exitHook(() => {
console.log('Shutdown server'); console.log('Shutdown server');
proxyServer.close(); proxyServer?.close();
server.close(); server.close();
console.log('Cleaning up socket on exit:', socketPath); console.log('Cleaning up socket on exit:', socketPath);
@@ -148,7 +160,7 @@ async function startProxyServer(
fs.rmSync(socketPath, {force: true}); fs.rmSync(socketPath, {force: true});
}); });
proxyServer.on('error', (err, _req, res) => { proxyServer?.on('error', (err, _req, res) => {
console.warn('Error in proxy server:', err); console.warn('Error in proxy server:', err);
if (res instanceof ServerResponse) { if (res instanceof ServerResponse) {
res.writeHead(502, 'Failed to proxy request'); res.writeHead(502, 'Failed to proxy request');
@@ -163,7 +175,7 @@ async function startProxyServer(
): Promise<void> => { ): Promise<void> => {
attachSocketServer(socket, serverImpl, companionEnv); attachSocketServer(socket, serverImpl, companionEnv);
return new Promise((resolve) => { return new Promise((resolve) => {
proxyServer.listen(config.port); proxyServer?.listen(config.port);
server.listen(socketPath, undefined, () => resolve()); server.listen(socketPath, undefined, () => resolve());
}); });
}; };
@@ -222,12 +234,15 @@ function addWebsocket(server: http.Server, config: Config) {
} }
}; };
const wss = new WebSocketServer({ const options: ServerOptions = {
noServer: true, noServer: true,
maxPayload: WEBSOCKET_MAX_MESSAGE_SIZE, maxPayload: WEBSOCKET_MAX_MESSAGE_SIZE,
verifyClient, };
}); if (config.tcp) {
options.verifyClient = verifyClient;
}
const wss = new WebSocketServer(options);
server.on('upgrade', function upgrade(request, socket, head) { server.on('upgrade', function upgrade(request, socket, head) {
const {pathname} = parse(request.url!); const {pathname} = parse(request.url!);

View File

@@ -23,7 +23,7 @@ const argv = yargs
.usage('yarn flipper-server [args]') .usage('yarn flipper-server [args]')
.options({ .options({
port: { port: {
describe: 'Port to serve on', describe: 'TCP port to serve on',
type: 'number', type: 'number',
default: 52342, default: 52342,
}, },
@@ -55,6 +55,12 @@ const argv = yargs
type: 'boolean', type: 'boolean',
default: true, default: true,
}, },
tcp: {
describe:
'Open a TCP port (--no-tcp can be specified as to use unix-domain-socket exclusively)',
type: 'boolean',
default: true,
},
}) })
.version('DEV') .version('DEV')
.help() .help()
@@ -94,9 +100,10 @@ async function start() {
} }
const {app, server, socket, readyForIncomingConnections} = await startServer({ const {app, server, socket, readyForIncomingConnections} = await startServer({
port: argv.port,
staticDir, staticDir,
entry: `index.web${argv.bundler ? '.dev' : ''}.html`, entry: `index.web${argv.bundler ? '.dev' : ''}.html`,
port: argv.port,
tcp: argv.tcp,
}); });
const flipperServer = await startFlipperServer( const flipperServer = await startFlipperServer(
@@ -150,6 +157,10 @@ process.on('unhandledRejection', (reason, promise) => {
start() start()
.then(() => { .then(() => {
if (!argv.tcp) {
console.log('Flipper server started and listening');
return;
}
console.log( console.log(
'Flipper server started and listening at port ' + chalk.green(argv.port), 'Flipper server started and listening at port ' + chalk.green(argv.port),
); );

View File

@@ -71,6 +71,11 @@ const argv = yargs
type: 'boolean', type: 'boolean',
default: false, default: false,
}, },
tcp: {
describe: 'Enable TCP connections on flipper-server.',
type: 'boolean',
default: true,
},
'rebuild-plugins': { 'rebuild-plugins': {
describe: describe:
'Enables rebuilding of default plugins on Flipper build. Only make sense in conjunction with "--no-bundled-plugins". Enabled by default, but if disabled using "--no-plugin-rebuild", then plugins are just released as is without rebuilding. This can save some time if you know plugin bundles are already up-to-date.', 'Enables rebuilding of default plugins on Flipper build. Only make sense in conjunction with "--no-bundled-plugins". Enabled by default, but if disabled using "--no-plugin-rebuild", then plugins are just released as is without rebuilding. This can save some time if you know plugin bundles are already up-to-date.',
@@ -296,16 +301,28 @@ async function runPostBuildAction(archive: string, dir: string) {
// didn't change // didn't change
console.log(`⚙️ Installing flipper-server.tgz using npx`); console.log(`⚙️ Installing flipper-server.tgz using npx`);
await fs.remove(path.join(homedir(), '.npm', '_npx')); await fs.remove(path.join(homedir(), '.npm', '_npx'));
await spawn('npx', [archive, argv.open ? '--open' : '--no-open'], { await spawn(
stdio: 'inherit', 'npx',
}); [
archive,
argv.open ? '--open' : '--no-open',
argv.tcp ? '--tcp' : '--no-tcp',
],
{
stdio: 'inherit',
},
);
} else if (argv.start) { } else if (argv.start) {
console.log(`⚙️ Starting flipper-server from build dir`); console.log(`⚙️ Starting flipper-server from build dir`);
await yarnInstall(dir); await yarnInstall(dir);
await spawn('./server.js', [argv.open ? '--open' : '--no-open'], { await spawn(
cwd: dir, './server.js',
stdio: 'inherit', [argv.open ? '--open' : '--no-open', argv.tcp ? '--tcp' : '--no-tcp'],
}); {
cwd: dir,
stdio: 'inherit',
},
);
} }
} }

View File

@@ -603,7 +603,11 @@ export function sleep(ms: number) {
let proc: child.ChildProcess | undefined; let proc: child.ChildProcess | undefined;
export async function launchServer(startBundler: boolean, open: boolean) { export async function launchServer(
startBundler: boolean,
open: boolean,
tcp: boolean,
) {
if (proc) { if (proc) {
console.log('⚙️ Killing old flipper-server...'); console.log('⚙️ Killing old flipper-server...');
proc.kill(9); proc.kill(9);
@@ -617,6 +621,7 @@ export async function launchServer(startBundler: boolean, open: boolean) {
`../flipper-server/server.js`, `../flipper-server/server.js`,
startBundler ? `--bundler` : `--no-bundler`, startBundler ? `--bundler` : `--no-bundler`,
open ? `--open` : `--no-open`, open ? `--open` : `--no-open`,
tcp ? `--tcp` : `--no-tcp`,
], ],
{ {
cwd: serverDir, cwd: serverDir,

View File

@@ -64,6 +64,11 @@ const argv = yargs
type: 'boolean', type: 'boolean',
default: true, default: true,
}, },
tcp: {
describe: 'Enable TCP connections on flipper-server.',
type: 'boolean',
default: true,
},
channel: { channel: {
description: 'Release channel for the build', description: 'Release channel for the build',
choices: ['stable', 'insiders'], choices: ['stable', 'insiders'],
@@ -131,7 +136,7 @@ let startCount = 0;
async function restartServer() { async function restartServer() {
try { try {
await compileServerMain(true); await compileServerMain(true);
await launchServer(true, argv.open && ++startCount === 1); // only open on the first time await launchServer(true, argv.open && ++startCount === 1, argv.tcp); // only open on the first time
} catch (e) { } catch (e) {
console.error( console.error(
chalk.red( chalk.red(

View File

@@ -14605,6 +14605,11 @@ write-json-file@^4.1.1:
sort-keys "^4.0.0" sort-keys "^4.0.0"
write-file-atomic "^3.0.0" write-file-atomic "^3.0.0"
ws@8.8.0:
version "8.8.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.0.tgz#8e71c75e2f6348dbf8d78005107297056cb77769"
integrity sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==
ws@^7.0.0, ws@^7.4.5, ws@^7.5.1: ws@^7.0.0, ws@^7.4.5, ws@^7.5.1:
version "7.5.7" version "7.5.7"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67"