chore: remove separate network tethering client connection (#20267)
This commit is contained in:
parent
b700c08dc5
commit
71798d658f
|
|
@ -38,7 +38,7 @@ PW_UUID=$(cat /proc/sys/kernel/random/uuid)
|
||||||
# Make sure to re-start playwright server if something goes wrong.
|
# Make sure to re-start playwright server if something goes wrong.
|
||||||
# The approach taken from: https://stackoverflow.com/a/697064/314883
|
# 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
|
echo "Server crashed with exit code $?. Respawning.." >&2
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ export class AndroidServerLauncherImpl {
|
||||||
const path = options.wsPath ? (options.wsPath.startsWith('/') ? options.wsPath : `/${options.wsPath}`) : `/${createGuid()}`;
|
const path = options.wsPath ? (options.wsPath.startsWith('/') ? options.wsPath : `/${options.wsPath}`) : `/${createGuid()}`;
|
||||||
|
|
||||||
// 2. Start the server
|
// 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);
|
const wsEndpoint = await server.listen(options.port);
|
||||||
|
|
||||||
// 3. Return the BrowserServer interface
|
// 3. Return the BrowserServer interface
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ export class BrowserServerLauncherImpl implements BrowserServerLauncher {
|
||||||
const path = options.wsPath ? (options.wsPath.startsWith('/') ? options.wsPath : `/${options.wsPath}`) : `/${createGuid()}`;
|
const path = options.wsPath ? (options.wsPath.startsWith('/') ? options.wsPath : `/${options.wsPath}`) : `/${createGuid()}`;
|
||||||
|
|
||||||
// 2. Start the server
|
// 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);
|
const wsEndpoint = await server.listen(options.port);
|
||||||
|
|
||||||
// 3. Return the BrowserServer interface
|
// 3. Return the BrowserServer interface
|
||||||
|
|
|
||||||
|
|
@ -270,14 +270,11 @@ program
|
||||||
.option('--port <port>', 'Server port')
|
.option('--port <port>', 'Server port')
|
||||||
.option('--path <path>', 'Endpoint Path', '/')
|
.option('--path <path>', 'Endpoint Path', '/')
|
||||||
.option('--max-clients <maxClients>', 'Maximum clients')
|
.option('--max-clients <maxClients>', 'Maximum clients')
|
||||||
.option('--proxy-mode <mode>', 'Either `client` or `tether`. Defaults to `client`.', 'client')
|
|
||||||
.action(function(options) {
|
.action(function(options) {
|
||||||
runServer({
|
runServer({
|
||||||
port: options.port ? +options.port : undefined,
|
port: options.port ? +options.port : undefined,
|
||||||
path: options.path,
|
path: options.path,
|
||||||
maxConnections: options.maxClients ? +options.maxClients : Infinity,
|
maxConnections: options.maxClients ? +options.maxClients : Infinity,
|
||||||
browserProxyMode: options.proxyMode,
|
|
||||||
ownedByTetherClient: !!process.env.PW_OWNED_BY_TETHER_CLIENT,
|
|
||||||
}).catch(logErrorAndExit);
|
}).catch(logErrorAndExit);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,10 +59,8 @@ export async function runServer(options: RunServerOptions) {
|
||||||
port,
|
port,
|
||||||
path = '/',
|
path = '/',
|
||||||
maxConnections = Infinity,
|
maxConnections = Infinity,
|
||||||
browserProxyMode = 'client',
|
|
||||||
ownedByTetherClient = false,
|
|
||||||
} = options;
|
} = options;
|
||||||
const server = new PlaywrightServer({ path, maxConnections, browserProxyMode, ownedByTetherClient });
|
const server = new PlaywrightServer({ path, maxConnections });
|
||||||
const wsEndpoint = await server.listen(port);
|
const wsEndpoint = await server.listen(port);
|
||||||
process.on('exit', () => server.close().catch(console.error));
|
process.on('exit', () => server.close().catch(console.error));
|
||||||
console.log('Listening on ' + wsEndpoint); // eslint-disable-line no-console
|
console.log('Listening on ' + wsEndpoint); // eslint-disable-line no-console
|
||||||
|
|
|
||||||
|
|
@ -223,7 +223,6 @@ export async function ensurePlaywrightContainerOrDie(port: number): Promise<Cont
|
||||||
}
|
}
|
||||||
|
|
||||||
const env: Record<string, string | undefined> = {
|
const env: Record<string, string | undefined> = {
|
||||||
PW_OWNED_BY_TETHER_CLIENT: '1',
|
|
||||||
DEBUG: process.env.DEBUG,
|
DEBUG: process.env.DEBUG,
|
||||||
};
|
};
|
||||||
for (const [key, value] of Object.entries(process.env)) {
|
for (const [key, value] of Object.entries(process.env)) {
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import type { LaunchOptions } from '../server/types';
|
||||||
import { AndroidDevice } from '../server/android/android';
|
import { AndroidDevice } from '../server/android/android';
|
||||||
import { DebugControllerDispatcher } from '../server/dispatchers/debugControllerDispatcher';
|
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 = {
|
type Options = {
|
||||||
socksProxyPattern: string | undefined,
|
socksProxyPattern: string | undefined,
|
||||||
|
|
@ -37,8 +37,7 @@ type PreLaunched = {
|
||||||
playwright?: Playwright | undefined;
|
playwright?: Playwright | undefined;
|
||||||
browser?: Browser | undefined;
|
browser?: Browser | undefined;
|
||||||
androidDevice?: AndroidDevice | undefined;
|
androidDevice?: AndroidDevice | undefined;
|
||||||
ownedSocksProxy?: SocksProxy | undefined;
|
socksProxy?: SocksProxy | undefined;
|
||||||
sharedSocksProxy?: SocksProxy | undefined;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export class PlaywrightConnection {
|
export class PlaywrightConnection {
|
||||||
|
|
@ -91,20 +90,10 @@ export class PlaywrightConnection {
|
||||||
return await this._initLaunchBrowserMode(scope);
|
return await this._initLaunchBrowserMode(scope);
|
||||||
if (clientType === 'playwright')
|
if (clientType === 'playwright')
|
||||||
return await this._initPlaywrightConnectMode(scope);
|
return await this._initPlaywrightConnectMode(scope);
|
||||||
if (clientType === 'network-tethering')
|
|
||||||
return await this._initPlaywrightTetheringMode(scope);
|
|
||||||
throw new Error('Unsupported client type: ' + clientType);
|
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) {
|
private async _initPlaywrightConnectMode(scope: RootDispatcher) {
|
||||||
this._debugLog(`engaged playwright.connect mode`);
|
this._debugLog(`engaged playwright.connect mode`);
|
||||||
const playwright = createPlaywright('javascript');
|
const playwright = createPlaywright('javascript');
|
||||||
|
|
@ -113,14 +102,7 @@ export class PlaywrightConnection {
|
||||||
await Promise.all(playwright.allBrowsers().map(browser => browser.close()));
|
await Promise.all(playwright.allBrowsers().map(browser => browser.close()));
|
||||||
});
|
});
|
||||||
|
|
||||||
let ownedSocksProxy: SocksProxy | undefined;
|
const ownedSocksProxy = await this._createOwnedSocksProxy(playwright);
|
||||||
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);
|
|
||||||
}
|
|
||||||
return new PlaywrightDispatcher(scope, playwright, ownedSocksProxy);
|
return new PlaywrightDispatcher(scope, playwright, ownedSocksProxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,15 +110,7 @@ export class PlaywrightConnection {
|
||||||
this._debugLog(`engaged launch mode for "${this._options.browserName}"`);
|
this._debugLog(`engaged launch mode for "${this._options.browserName}"`);
|
||||||
const playwright = createPlaywright('javascript');
|
const playwright = createPlaywright('javascript');
|
||||||
|
|
||||||
let ownedSocksProxy: SocksProxy | undefined;
|
const ownedSocksProxy = await this._createOwnedSocksProxy(playwright);
|
||||||
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 browser = await playwright[this._options.browserName as 'chromium'].launch(serverSideCallMetadata(), this._options.launchOptions);
|
const browser = await playwright[this._options.browserName as 'chromium'].launch(serverSideCallMetadata(), this._options.launchOptions);
|
||||||
|
|
||||||
this._cleanups.push(async () => {
|
this._cleanups.push(async () => {
|
||||||
|
|
@ -156,8 +130,7 @@ export class PlaywrightConnection {
|
||||||
const playwright = this._preLaunched.playwright!;
|
const playwright = this._preLaunched.playwright!;
|
||||||
|
|
||||||
// Note: connected client owns the socks proxy and configures the pattern.
|
// Note: connected client owns the socks proxy and configures the pattern.
|
||||||
playwright.options.socksProxyPort = this._preLaunched.ownedSocksProxy?.port();
|
this._preLaunched.socksProxy?.setPattern(this._options.socksProxyPattern);
|
||||||
this._preLaunched.ownedSocksProxy?.setPattern(this._options.socksProxyPattern);
|
|
||||||
|
|
||||||
const browser = this._preLaunched.browser!;
|
const browser = this._preLaunched.browser!;
|
||||||
browser.on(Browser.Events.Disconnected, () => {
|
browser.on(Browser.Events.Disconnected, () => {
|
||||||
|
|
@ -165,7 +138,7 @@ export class PlaywrightConnection {
|
||||||
this.close({ code: 1001, reason: 'Browser closed' });
|
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.
|
// In pre-launched mode, keep only the pre-launched browser.
|
||||||
for (const b of playwright.allBrowsers()) {
|
for (const b of playwright.allBrowsers()) {
|
||||||
if (b !== browser)
|
if (b !== browser)
|
||||||
|
|
@ -196,13 +169,12 @@ export class PlaywrightConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _initReuseBrowsersMode(scope: RootDispatcher) {
|
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}`);
|
this._debugLog(`engaged reuse browsers mode for ${this._options.browserName}`);
|
||||||
const playwright = this._preLaunched.playwright!;
|
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);
|
const requestedOptions = launchOptionsHash(this._options.launchOptions);
|
||||||
let browser = playwright.allBrowsers().find(b => {
|
let browser = playwright.allBrowsers().find(b => {
|
||||||
if (b.options.name !== this._options.browserName)
|
if (b.options.name !== this._options.browserName)
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import type { ClientType } from './playwrightConnection';
|
||||||
import type { LaunchOptions } from '../server/types';
|
import type { LaunchOptions } from '../server/types';
|
||||||
import { ManualPromise } from '../utils/manualPromise';
|
import { ManualPromise } from '../utils/manualPromise';
|
||||||
import type { AndroidDevice } from '../server/android/android';
|
import type { AndroidDevice } from '../server/android/android';
|
||||||
import { SocksProxy } from '../common/socksProxy';
|
import { type SocksProxy } from '../common/socksProxy';
|
||||||
|
|
||||||
const debugLog = debug('pw:server');
|
const debugLog = debug('pw:server');
|
||||||
|
|
||||||
|
|
@ -43,16 +43,12 @@ type ServerOptions = {
|
||||||
preLaunchedBrowser?: Browser;
|
preLaunchedBrowser?: Browser;
|
||||||
preLaunchedAndroidDevice?: AndroidDevice;
|
preLaunchedAndroidDevice?: AndroidDevice;
|
||||||
preLaunchedSocksProxy?: SocksProxy;
|
preLaunchedSocksProxy?: SocksProxy;
|
||||||
browserProxyMode: 'client' | 'tether';
|
|
||||||
ownedByTetherClient?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export class PlaywrightServer {
|
export class PlaywrightServer {
|
||||||
private _preLaunchedPlaywright: Playwright | undefined;
|
private _preLaunchedPlaywright: Playwright | undefined;
|
||||||
private _wsServer: WebSocketServer | undefined;
|
private _wsServer: WebSocketServer | undefined;
|
||||||
private _networkTetheringSocksProxy: SocksProxy | undefined;
|
|
||||||
private _options: ServerOptions;
|
private _options: ServerOptions;
|
||||||
private _networkTetheringClientTimeout: NodeJS.Timeout | undefined;
|
|
||||||
|
|
||||||
constructor(options: ServerOptions) {
|
constructor(options: ServerOptions) {
|
||||||
this._options = options;
|
this._options = options;
|
||||||
|
|
@ -86,24 +82,12 @@ export class PlaywrightServer {
|
||||||
resolve(wsEndpoint);
|
resolve(wsEndpoint);
|
||||||
}).on('error', reject);
|
}).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);
|
debugLog('Listening at ' + wsEndpoint);
|
||||||
if (this._options.ownedByTetherClient) {
|
|
||||||
this._networkTetheringClientTimeout = setTimeout(() => {
|
|
||||||
this.close();
|
|
||||||
}, 30_000);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._wsServer = new wsServer({ server, path: this._options.path });
|
this._wsServer = new wsServer({ server, path: this._options.path });
|
||||||
const browserSemaphore = new Semaphore(this._options.maxConnections);
|
const browserSemaphore = new Semaphore(this._options.maxConnections);
|
||||||
const controllerSemaphore = new Semaphore(1);
|
const controllerSemaphore = new Semaphore(1);
|
||||||
const reuseBrowserSemaphore = new Semaphore(1);
|
const reuseBrowserSemaphore = new Semaphore(1);
|
||||||
const networkTetheringSemaphore = new Semaphore(1);
|
|
||||||
this._wsServer.on('connection', (ws, request) => {
|
this._wsServer.on('connection', (ws, request) => {
|
||||||
const url = new URL('http://localhost' + (request.url || ''));
|
const url = new URL('http://localhost' + (request.url || ''));
|
||||||
const browserHeader = request.headers['x-playwright-browser'];
|
const browserHeader = request.headers['x-playwright-browser'];
|
||||||
|
|
@ -121,11 +105,10 @@ export class PlaywrightServer {
|
||||||
const log = newLogger();
|
const log = newLogger();
|
||||||
log(`serving connection: ${request.url}`);
|
log(`serving connection: ${request.url}`);
|
||||||
const isDebugControllerClient = !!request.headers['x-playwright-debug-controller'];
|
const isDebugControllerClient = !!request.headers['x-playwright-debug-controller'];
|
||||||
const isNetworkTetheringClient = !!request.headers['x-playwright-network-tethering'];
|
|
||||||
const shouldReuseBrowser = !!request.headers['x-playwright-reuse-context'];
|
const shouldReuseBrowser = !!request.headers['x-playwright-reuse-context'];
|
||||||
|
|
||||||
// If we started in the legacy reuse-browser mode, create this._preLaunchedPlaywright.
|
// 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 (isDebugControllerClient || shouldReuseBrowser) {
|
||||||
if (!this._preLaunchedPlaywright)
|
if (!this._preLaunchedPlaywright)
|
||||||
this._preLaunchedPlaywright = createPlaywright('javascript');
|
this._preLaunchedPlaywright = createPlaywright('javascript');
|
||||||
|
|
@ -133,10 +116,7 @@ export class PlaywrightServer {
|
||||||
|
|
||||||
let clientType: ClientType = 'playwright';
|
let clientType: ClientType = 'playwright';
|
||||||
let semaphore: Semaphore = browserSemaphore;
|
let semaphore: Semaphore = browserSemaphore;
|
||||||
if (isNetworkTetheringClient) {
|
if (isDebugControllerClient) {
|
||||||
clientType = 'network-tethering';
|
|
||||||
semaphore = networkTetheringSemaphore;
|
|
||||||
} else if (isDebugControllerClient) {
|
|
||||||
clientType = 'controller';
|
clientType = 'controller';
|
||||||
semaphore = controllerSemaphore;
|
semaphore = controllerSemaphore;
|
||||||
} else if (shouldReuseBrowser) {
|
} else if (shouldReuseBrowser) {
|
||||||
|
|
@ -150,9 +130,6 @@ export class PlaywrightServer {
|
||||||
semaphore = browserSemaphore;
|
semaphore = browserSemaphore;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clientType === 'network-tethering' && this._options.ownedByTetherClient)
|
|
||||||
clearTimeout(this._networkTetheringClientTimeout);
|
|
||||||
|
|
||||||
const connection = new PlaywrightConnection(
|
const connection = new PlaywrightConnection(
|
||||||
semaphore.aquire(),
|
semaphore.aquire(),
|
||||||
clientType, ws,
|
clientType, ws,
|
||||||
|
|
@ -161,14 +138,9 @@ export class PlaywrightServer {
|
||||||
playwright: this._preLaunchedPlaywright,
|
playwright: this._preLaunchedPlaywright,
|
||||||
browser: this._options.preLaunchedBrowser,
|
browser: this._options.preLaunchedBrowser,
|
||||||
androidDevice: this._options.preLaunchedAndroidDevice,
|
androidDevice: this._options.preLaunchedAndroidDevice,
|
||||||
ownedSocksProxy: this._options.preLaunchedSocksProxy,
|
socksProxy: this._options.preLaunchedSocksProxy,
|
||||||
sharedSocksProxy: this._networkTetheringSocksProxy,
|
|
||||||
},
|
},
|
||||||
log, () => {
|
log, () => semaphore.release());
|
||||||
semaphore.release();
|
|
||||||
if (this._options.ownedByTetherClient && clientType === 'network-tethering')
|
|
||||||
this.close();
|
|
||||||
});
|
|
||||||
(ws as any)[kConnectionSymbol] = connection;
|
(ws as any)[kConnectionSymbol] = connection;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -179,7 +151,6 @@ export class PlaywrightServer {
|
||||||
const server = this._wsServer;
|
const server = this._wsServer;
|
||||||
if (!server)
|
if (!server)
|
||||||
return;
|
return;
|
||||||
await this._networkTetheringSocksProxy?.close();
|
|
||||||
debugLog('closing websocket server');
|
debugLog('closing websocket server');
|
||||||
const waitForClose = new Promise(f => server.close(f));
|
const waitForClose = new Promise(f => server.close(f));
|
||||||
// First disconnect all remaining clients.
|
// First disconnect all remaining clients.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue