From 646b9d5a5dd0dd21ee652ff463a4325f5e8bc72f Mon Sep 17 00:00:00 2001 From: Lorenzo Blasa Date: Wed, 29 Jun 2022 15:01:05 -0700 Subject: [PATCH] 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 --- desktop/app/package.json | 4 +- desktop/app/src/init.tsx | 26 ++++++++---- .../src/client/FlipperServerClient.tsx | 9 +++- .../src/server/startServer.tsx | 41 +++++++++++++------ desktop/flipper-server/src/index.tsx | 15 ++++++- .../scripts/build-flipper-server-release.tsx | 31 ++++++++++---- desktop/scripts/build-utils.tsx | 7 +++- desktop/scripts/start-flipper-server-dev.tsx | 7 +++- desktop/yarn.lock | 5 +++ 9 files changed, 110 insertions(+), 35 deletions(-) diff --git a/desktop/app/package.json b/desktop/app/package.json index 9e80d91a4..af6acf2e5 100644 --- a/desktop/app/package.json +++ b/desktop/app/package.json @@ -22,7 +22,9 @@ "fs-extra": "^10.1.0", "invariant": "^2.2.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": {}, "devDependencies": { diff --git a/desktop/app/src/init.tsx b/desktop/app/src/init.tsx index 099de356d..df78b3584 100644 --- a/desktop/app/src/init.tsx +++ b/desktop/app/src/init.tsx @@ -14,7 +14,10 @@ import { _setGlobalInteractionReporter, _LoggerContext, } from 'flipper-plugin'; -import {createFlipperServer, FlipperServerState} from 'flipper-frontend-core'; +import { + createFlipperServerWithSocket, + FlipperServerState, +} from 'flipper-frontend-core'; import { FlipperServerImpl, getEnvironmentInfo, @@ -45,6 +48,8 @@ import {ElectronIpcClientRenderer} from './electronIpc'; import {checkSocketInUse, makeSocketPath} from 'flipper-server-core'; import {KeytarModule} from 'flipper-server-core/src/utils/keytar'; import {initCompanionEnv} from 'flipper-server-companion'; +import ReconnectingWebSocket from 'reconnecting-websocket'; +import WS from 'ws'; enableMapSet(); @@ -70,10 +75,13 @@ async function getKeytarModule(staticPath: string): Promise { return keytar; } -async function getExternalServer() { - const server = await createFlipperServer( - 'localhost', - 52342, +async function getExternalServer(path: string) { + const options = { + WebSocket: WS, + }; + const socket = new ReconnectingWebSocket(`ws+unix://${path}`, [], options); + const server = await createFlipperServerWithSocket( + socket, (_state: FlipperServerState) => {}, ); return server; @@ -105,7 +113,7 @@ async function getFlipperServer( const getEmbeddedServer = async () => { if (serverRunning) { - const server = await getExternalServer(); + const server = await getExternalServer(socketPath); await server.exec('shutdown').catch(() => { /** shutdown will ultimately make this request fail, ignore error. */ }); @@ -114,7 +122,6 @@ async function getFlipperServer( { environmentInfo, env: parseEnvironmentVariables(env), - // TODO: make username parameterizable gatekeepers: gatekeepers, paths: { appPath, @@ -142,9 +149,10 @@ async function getFlipperServer( console.info('flipper-server: not running/listening, start'); const {readyForIncomingConnections} = await startServer({ - port: 52342, staticDir: staticPath, entry: 'index.web.dev.html', + tcp: false, + port: 52342, }); const server = await startFlipperServer( @@ -167,7 +175,7 @@ async function getFlipperServer( tailServerLogs(path.join(staticPath, loggerOutputFile)); } - return getExternalServer(); + return getExternalServer(socketPath); } return getEmbeddedServer(); } diff --git a/desktop/flipper-frontend-core/src/client/FlipperServerClient.tsx b/desktop/flipper-frontend-core/src/client/FlipperServerClient.tsx index a64cfd519..77acae301 100644 --- a/desktop/flipper-frontend-core/src/client/FlipperServerClient.tsx +++ b/desktop/flipper-frontend-core/src/client/FlipperServerClient.tsx @@ -28,6 +28,14 @@ export function createFlipperServer( host: string, port: number, onStateChange: (state: FlipperServerState) => void, +): Promise { + const socket = new ReconnectingWebSocket(`ws://${host}:${port}`); + return createFlipperServerWithSocket(socket, onStateChange); +} + +export function createFlipperServerWithSocket( + socket: ReconnectingWebSocket, + onStateChange: (state: FlipperServerState) => void, ): Promise { onStateChange(FlipperServerState.CONNECTING); @@ -40,7 +48,6 @@ export function createFlipperServer( const eventEmitter = new EventEmitter(); - const socket = new ReconnectingWebSocket(`ws://${host}:${port}`); const pendingRequests: Map< number, { diff --git a/desktop/flipper-server-core/src/server/startServer.tsx b/desktop/flipper-server-core/src/server/startServer.tsx index d14eb6eef..832b5038a 100644 --- a/desktop/flipper-server-core/src/server/startServer.tsx +++ b/desktop/flipper-server-core/src/server/startServer.tsx @@ -13,7 +13,7 @@ import express, {Express} from 'express'; import http, {ServerResponse} from 'http'; import path from 'path'; 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 {parse} from 'url'; import {makeSocketPath, checkSocketInUse} from './utilities'; @@ -28,6 +28,7 @@ type Config = { port: number; staticDir: string; entry: string; + tcp: boolean; }; type ReadyForConnections = ( @@ -108,6 +109,12 @@ async function startProxyServer( // On Windows, a proxy is not created and the server starts // listening at the specified port. 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) => { console.log(`Starting server on http://localhost:${config.port}`); const readyForIncomingConnections = (): Promise => { @@ -129,17 +136,22 @@ async function startProxyServer( await fs.rm(socketPath, {force: true}); } - const proxyServer = proxy.createProxyServer({ - target: {host: 'localhost', port: 0, socketPath}, - autoRewrite: true, - ws: true, - }); + const proxyServer: proxy | undefined = config.tcp + ? proxy.createProxyServer({ + target: {host: 'localhost', port: 0, socketPath}, + autoRewrite: true, + ws: true, + }) + : undefined; + 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(() => { console.log('Shutdown server'); - proxyServer.close(); + proxyServer?.close(); server.close(); console.log('Cleaning up socket on exit:', socketPath); @@ -148,7 +160,7 @@ async function startProxyServer( fs.rmSync(socketPath, {force: true}); }); - proxyServer.on('error', (err, _req, res) => { + proxyServer?.on('error', (err, _req, res) => { console.warn('Error in proxy server:', err); if (res instanceof ServerResponse) { res.writeHead(502, 'Failed to proxy request'); @@ -163,7 +175,7 @@ async function startProxyServer( ): Promise => { attachSocketServer(socket, serverImpl, companionEnv); return new Promise((resolve) => { - proxyServer.listen(config.port); + proxyServer?.listen(config.port); 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, 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) { const {pathname} = parse(request.url!); diff --git a/desktop/flipper-server/src/index.tsx b/desktop/flipper-server/src/index.tsx index 5d98ffb4b..f5d98c593 100644 --- a/desktop/flipper-server/src/index.tsx +++ b/desktop/flipper-server/src/index.tsx @@ -23,7 +23,7 @@ const argv = yargs .usage('yarn flipper-server [args]') .options({ port: { - describe: 'Port to serve on', + describe: 'TCP port to serve on', type: 'number', default: 52342, }, @@ -55,6 +55,12 @@ const argv = yargs type: 'boolean', 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') .help() @@ -94,9 +100,10 @@ async function start() { } const {app, server, socket, readyForIncomingConnections} = await startServer({ - port: argv.port, staticDir, entry: `index.web${argv.bundler ? '.dev' : ''}.html`, + port: argv.port, + tcp: argv.tcp, }); const flipperServer = await startFlipperServer( @@ -150,6 +157,10 @@ process.on('unhandledRejection', (reason, promise) => { start() .then(() => { + if (!argv.tcp) { + console.log('Flipper server started and listening'); + return; + } console.log( 'Flipper server started and listening at port ' + chalk.green(argv.port), ); diff --git a/desktop/scripts/build-flipper-server-release.tsx b/desktop/scripts/build-flipper-server-release.tsx index 69c2d7349..36ad2a3c2 100644 --- a/desktop/scripts/build-flipper-server-release.tsx +++ b/desktop/scripts/build-flipper-server-release.tsx @@ -71,6 +71,11 @@ const argv = yargs type: 'boolean', default: false, }, + tcp: { + describe: 'Enable TCP connections on flipper-server.', + type: 'boolean', + default: true, + }, 'rebuild-plugins': { 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.', @@ -296,16 +301,28 @@ async function runPostBuildAction(archive: string, dir: string) { // didn't change console.log(`⚙️ Installing flipper-server.tgz using npx`); await fs.remove(path.join(homedir(), '.npm', '_npx')); - await spawn('npx', [archive, argv.open ? '--open' : '--no-open'], { - stdio: 'inherit', - }); + await spawn( + 'npx', + [ + archive, + argv.open ? '--open' : '--no-open', + argv.tcp ? '--tcp' : '--no-tcp', + ], + { + stdio: 'inherit', + }, + ); } else if (argv.start) { console.log(`⚙️ Starting flipper-server from build dir`); await yarnInstall(dir); - await spawn('./server.js', [argv.open ? '--open' : '--no-open'], { - cwd: dir, - stdio: 'inherit', - }); + await spawn( + './server.js', + [argv.open ? '--open' : '--no-open', argv.tcp ? '--tcp' : '--no-tcp'], + { + cwd: dir, + stdio: 'inherit', + }, + ); } } diff --git a/desktop/scripts/build-utils.tsx b/desktop/scripts/build-utils.tsx index 6f0ea6771..65e212781 100644 --- a/desktop/scripts/build-utils.tsx +++ b/desktop/scripts/build-utils.tsx @@ -603,7 +603,11 @@ export function sleep(ms: number) { 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) { console.log('⚙️ Killing old flipper-server...'); proc.kill(9); @@ -617,6 +621,7 @@ export async function launchServer(startBundler: boolean, open: boolean) { `../flipper-server/server.js`, startBundler ? `--bundler` : `--no-bundler`, open ? `--open` : `--no-open`, + tcp ? `--tcp` : `--no-tcp`, ], { cwd: serverDir, diff --git a/desktop/scripts/start-flipper-server-dev.tsx b/desktop/scripts/start-flipper-server-dev.tsx index e48954809..f86d0c505 100644 --- a/desktop/scripts/start-flipper-server-dev.tsx +++ b/desktop/scripts/start-flipper-server-dev.tsx @@ -64,6 +64,11 @@ const argv = yargs type: 'boolean', default: true, }, + tcp: { + describe: 'Enable TCP connections on flipper-server.', + type: 'boolean', + default: true, + }, channel: { description: 'Release channel for the build', choices: ['stable', 'insiders'], @@ -131,7 +136,7 @@ let startCount = 0; async function restartServer() { try { 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) { console.error( chalk.red( diff --git a/desktop/yarn.lock b/desktop/yarn.lock index 04c2699f6..44024b0a3 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -14605,6 +14605,11 @@ write-json-file@^4.1.1: sort-keys "^4.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: version "7.5.7" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67"