chore: remove separate network tethering client connection (#20267)

This commit is contained in:
Dmitry Gozman 2023-01-21 18:18:22 -08:00 committed by GitHub
parent b700c08dc5
commit 71798d658f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 18 additions and 81 deletions

View File

@ -38,7 +38,7 @@ PW_UUID=$(cat /proc/sys/kernel/random/uuid)
# Make sure to re-start playwright server if something goes wrong.
# The approach taken from: https://stackoverflow.com/a/697064/314883
until npx playwright run-server --port=5200 --path=/$PW_UUID --proxy-mode=tether; do
until npx playwright run-server --port=5200 --path=/$PW_UUID; do
echo "Server crashed with exit code $?. Respawning.." >&2
sleep 1
done

View File

@ -49,7 +49,7 @@ export class AndroidServerLauncherImpl {
const path = options.wsPath ? (options.wsPath.startsWith('/') ? options.wsPath : `/${options.wsPath}`) : `/${createGuid()}`;
// 2. Start the server
const server = new PlaywrightServer({ path, maxConnections: 1, preLaunchedAndroidDevice: device, browserProxyMode: 'client' });
const server = new PlaywrightServer({ path, maxConnections: 1, preLaunchedAndroidDevice: device });
const wsEndpoint = await server.listen(options.port);
// 3. Return the BrowserServer interface

View File

@ -57,7 +57,7 @@ export class BrowserServerLauncherImpl implements BrowserServerLauncher {
const path = options.wsPath ? (options.wsPath.startsWith('/') ? options.wsPath : `/${options.wsPath}`) : `/${createGuid()}`;
// 2. Start the server
const server = new PlaywrightServer({ path, maxConnections: Infinity, browserProxyMode: 'client', preLaunchedBrowser: browser, preLaunchedSocksProxy: socksProxy });
const server = new PlaywrightServer({ path, maxConnections: Infinity, preLaunchedBrowser: browser, preLaunchedSocksProxy: socksProxy });
const wsEndpoint = await server.listen(options.port);
// 3. Return the BrowserServer interface

View File

@ -270,14 +270,11 @@ program
.option('--port <port>', 'Server port')
.option('--path <path>', 'Endpoint Path', '/')
.option('--max-clients <maxClients>', 'Maximum clients')
.option('--proxy-mode <mode>', 'Either `client` or `tether`. Defaults to `client`.', 'client')
.action(function(options) {
runServer({
port: options.port ? +options.port : undefined,
path: options.path,
maxConnections: options.maxClients ? +options.maxClients : Infinity,
browserProxyMode: options.proxyMode,
ownedByTetherClient: !!process.env.PW_OWNED_BY_TETHER_CLIENT,
}).catch(logErrorAndExit);
});

View File

@ -59,10 +59,8 @@ export async function runServer(options: RunServerOptions) {
port,
path = '/',
maxConnections = Infinity,
browserProxyMode = 'client',
ownedByTetherClient = false,
} = options;
const server = new PlaywrightServer({ path, maxConnections, browserProxyMode, ownedByTetherClient });
const server = new PlaywrightServer({ path, maxConnections });
const wsEndpoint = await server.listen(port);
process.on('exit', () => server.close().catch(console.error));
console.log('Listening on ' + wsEndpoint); // eslint-disable-line no-console

View File

@ -223,7 +223,6 @@ export async function ensurePlaywrightContainerOrDie(port: number): Promise<Cont
}
const env: Record<string, string | undefined> = {
PW_OWNED_BY_TETHER_CLIENT: '1',
DEBUG: process.env.DEBUG,
};
for (const [key, value] of Object.entries(process.env)) {

View File

@ -25,7 +25,7 @@ import type { LaunchOptions } from '../server/types';
import { AndroidDevice } from '../server/android/android';
import { DebugControllerDispatcher } from '../server/dispatchers/debugControllerDispatcher';
export type ClientType = 'controller' | 'playwright' | 'launch-browser' | 'reuse-browser' | 'pre-launched-browser-or-android' | 'network-tethering';
export type ClientType = 'controller' | 'playwright' | 'launch-browser' | 'reuse-browser' | 'pre-launched-browser-or-android';
type Options = {
socksProxyPattern: string | undefined,
@ -37,8 +37,7 @@ type PreLaunched = {
playwright?: Playwright | undefined;
browser?: Browser | undefined;
androidDevice?: AndroidDevice | undefined;
ownedSocksProxy?: SocksProxy | undefined;
sharedSocksProxy?: SocksProxy | undefined;
socksProxy?: SocksProxy | undefined;
};
export class PlaywrightConnection {
@ -91,20 +90,10 @@ export class PlaywrightConnection {
return await this._initLaunchBrowserMode(scope);
if (clientType === 'playwright')
return await this._initPlaywrightConnectMode(scope);
if (clientType === 'network-tethering')
return await this._initPlaywrightTetheringMode(scope);
throw new Error('Unsupported client type: ' + clientType);
});
}
private async _initPlaywrightTetheringMode(scope: RootDispatcher) {
this._debugLog(`engaged playwright.tethering mode`);
const playwright = createPlaywright('javascript');
this._preLaunched.sharedSocksProxy?.setPattern(this._options.socksProxyPattern);
// Tethering client owns the shared socks proxy.
return new PlaywrightDispatcher(scope, playwright, this._preLaunched.sharedSocksProxy);
}
private async _initPlaywrightConnectMode(scope: RootDispatcher) {
this._debugLog(`engaged playwright.connect mode`);
const playwright = createPlaywright('javascript');
@ -113,14 +102,7 @@ export class PlaywrightConnection {
await Promise.all(playwright.allBrowsers().map(browser => browser.close()));
});
let ownedSocksProxy: SocksProxy | undefined;
if (this._preLaunched.sharedSocksProxy) {
// Note: tethering client configures the pattern, and connected client's pattern is ignored.
playwright.options.socksProxyPort = this._preLaunched.sharedSocksProxy.port();
this._debugLog(`using shared socks proxy on port ${playwright.options.socksProxyPort}`);
} else {
ownedSocksProxy = await this._createOwnedSocksProxy(playwright);
}
const ownedSocksProxy = await this._createOwnedSocksProxy(playwright);
return new PlaywrightDispatcher(scope, playwright, ownedSocksProxy);
}
@ -128,15 +110,7 @@ export class PlaywrightConnection {
this._debugLog(`engaged launch mode for "${this._options.browserName}"`);
const playwright = createPlaywright('javascript');
let ownedSocksProxy: SocksProxy | undefined;
if (this._preLaunched.sharedSocksProxy) {
// Note: tethering client configures the pattern, and connected client's pattern is ignored.
playwright.options.socksProxyPort = this._preLaunched.sharedSocksProxy.port();
this._debugLog(`using shared socks proxy on port ${playwright.options.socksProxyPort}`);
} else {
ownedSocksProxy = await this._createOwnedSocksProxy(playwright);
}
const ownedSocksProxy = await this._createOwnedSocksProxy(playwright);
const browser = await playwright[this._options.browserName as 'chromium'].launch(serverSideCallMetadata(), this._options.launchOptions);
this._cleanups.push(async () => {
@ -156,8 +130,7 @@ export class PlaywrightConnection {
const playwright = this._preLaunched.playwright!;
// Note: connected client owns the socks proxy and configures the pattern.
playwright.options.socksProxyPort = this._preLaunched.ownedSocksProxy?.port();
this._preLaunched.ownedSocksProxy?.setPattern(this._options.socksProxyPattern);
this._preLaunched.socksProxy?.setPattern(this._options.socksProxyPattern);
const browser = this._preLaunched.browser!;
browser.on(Browser.Events.Disconnected, () => {
@ -165,7 +138,7 @@ export class PlaywrightConnection {
this.close({ code: 1001, reason: 'Browser closed' });
});
const playwrightDispatcher = new PlaywrightDispatcher(scope, playwright, this._preLaunched.ownedSocksProxy, browser);
const playwrightDispatcher = new PlaywrightDispatcher(scope, playwright, this._preLaunched.socksProxy, browser);
// In pre-launched mode, keep only the pre-launched browser.
for (const b of playwright.allBrowsers()) {
if (b !== browser)
@ -196,13 +169,12 @@ export class PlaywrightConnection {
}
private async _initReuseBrowsersMode(scope: RootDispatcher) {
// Note: reuse browser mode does not support socks proxy, because
// clients come and go, while the browser stays the same.
this._debugLog(`engaged reuse browsers mode for ${this._options.browserName}`);
const playwright = this._preLaunched.playwright!;
// Note: connected client owns the socks proxy and configures the pattern.
playwright.options.socksProxyPort = this._preLaunched.sharedSocksProxy?.port();
this._debugLog(`using shared socks proxy on port ${playwright.options.socksProxyPort}`);
const requestedOptions = launchOptionsHash(this._options.launchOptions);
let browser = playwright.allBrowsers().find(b => {
if (b.options.name !== this._options.browserName)

View File

@ -25,7 +25,7 @@ import type { ClientType } from './playwrightConnection';
import type { LaunchOptions } from '../server/types';
import { ManualPromise } from '../utils/manualPromise';
import type { AndroidDevice } from '../server/android/android';
import { SocksProxy } from '../common/socksProxy';
import { type SocksProxy } from '../common/socksProxy';
const debugLog = debug('pw:server');
@ -43,16 +43,12 @@ type ServerOptions = {
preLaunchedBrowser?: Browser;
preLaunchedAndroidDevice?: AndroidDevice;
preLaunchedSocksProxy?: SocksProxy;
browserProxyMode: 'client' | 'tether';
ownedByTetherClient?: boolean;
};
export class PlaywrightServer {
private _preLaunchedPlaywright: Playwright | undefined;
private _wsServer: WebSocketServer | undefined;
private _networkTetheringSocksProxy: SocksProxy | undefined;
private _options: ServerOptions;
private _networkTetheringClientTimeout: NodeJS.Timeout | undefined;
constructor(options: ServerOptions) {
this._options = options;
@ -86,24 +82,12 @@ export class PlaywrightServer {
resolve(wsEndpoint);
}).on('error', reject);
});
if (this._options.browserProxyMode === 'tether') {
this._networkTetheringSocksProxy = new SocksProxy();
await this._networkTetheringSocksProxy.listen(0);
debugLog('Launched tethering proxy at ' + this._networkTetheringSocksProxy.port());
}
debugLog('Listening at ' + wsEndpoint);
if (this._options.ownedByTetherClient) {
this._networkTetheringClientTimeout = setTimeout(() => {
this.close();
}, 30_000);
}
this._wsServer = new wsServer({ server, path: this._options.path });
const browserSemaphore = new Semaphore(this._options.maxConnections);
const controllerSemaphore = new Semaphore(1);
const reuseBrowserSemaphore = new Semaphore(1);
const networkTetheringSemaphore = new Semaphore(1);
this._wsServer.on('connection', (ws, request) => {
const url = new URL('http://localhost' + (request.url || ''));
const browserHeader = request.headers['x-playwright-browser'];
@ -121,11 +105,10 @@ export class PlaywrightServer {
const log = newLogger();
log(`serving connection: ${request.url}`);
const isDebugControllerClient = !!request.headers['x-playwright-debug-controller'];
const isNetworkTetheringClient = !!request.headers['x-playwright-network-tethering'];
const shouldReuseBrowser = !!request.headers['x-playwright-reuse-context'];
// If we started in the legacy reuse-browser mode, create this._preLaunchedPlaywright.
// If we get a reuse-controller request, create this._preLaunchedPlaywright.
// If we get a debug-controller request, create this._preLaunchedPlaywright.
if (isDebugControllerClient || shouldReuseBrowser) {
if (!this._preLaunchedPlaywright)
this._preLaunchedPlaywright = createPlaywright('javascript');
@ -133,10 +116,7 @@ export class PlaywrightServer {
let clientType: ClientType = 'playwright';
let semaphore: Semaphore = browserSemaphore;
if (isNetworkTetheringClient) {
clientType = 'network-tethering';
semaphore = networkTetheringSemaphore;
} else if (isDebugControllerClient) {
if (isDebugControllerClient) {
clientType = 'controller';
semaphore = controllerSemaphore;
} else if (shouldReuseBrowser) {
@ -150,9 +130,6 @@ export class PlaywrightServer {
semaphore = browserSemaphore;
}
if (clientType === 'network-tethering' && this._options.ownedByTetherClient)
clearTimeout(this._networkTetheringClientTimeout);
const connection = new PlaywrightConnection(
semaphore.aquire(),
clientType, ws,
@ -161,14 +138,9 @@ export class PlaywrightServer {
playwright: this._preLaunchedPlaywright,
browser: this._options.preLaunchedBrowser,
androidDevice: this._options.preLaunchedAndroidDevice,
ownedSocksProxy: this._options.preLaunchedSocksProxy,
sharedSocksProxy: this._networkTetheringSocksProxy,
socksProxy: this._options.preLaunchedSocksProxy,
},
log, () => {
semaphore.release();
if (this._options.ownedByTetherClient && clientType === 'network-tethering')
this.close();
});
log, () => semaphore.release());
(ws as any)[kConnectionSymbol] = connection;
});
@ -179,7 +151,6 @@ export class PlaywrightServer {
const server = this._wsServer;
if (!server)
return;
await this._networkTetheringSocksProxy?.close();
debugLog('closing websocket server');
const waitForClose = new Promise(f => server.close(f));
// First disconnect all remaining clients.