feat(remote): let client enable/disable sock proxy (#12086)

This commit is contained in:
Dmitry Gozman 2022-02-14 15:10:58 -08:00 committed by GitHub
parent e9ca11d91b
commit 5a0445b8da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 25 additions and 29 deletions

View File

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

View File

@ -129,10 +129,8 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
const deadline = params.timeout ? monotonicTime() + params.timeout : 0; const deadline = params.timeout ? monotonicTime() + params.timeout : 0;
let browser: Browser; let browser: Browser;
const connectParams: channels.BrowserTypeConnectParams = { wsEndpoint, headers: params.headers, slowMo: params.slowMo, timeout: params.timeout }; const connectParams: channels.BrowserTypeConnectParams = { wsEndpoint, headers: params.headers, slowMo: params.slowMo, timeout: params.timeout };
if ((params as any).__testHookPortForwarding) { if ((params as any).__testHookRedirectPortForwarding)
connectParams.enableSocksProxy = true; connectParams.socksProxyRedirectPortForTest = (params as any).__testHookRedirectPortForwarding;
connectParams.socksProxyRedirectPortForTest = (params as any).__testHookPortForwarding.redirectPortForTest;
}
const { pipe } = await this._channel.connect(connectParams); const { pipe } = await this._channel.connect(connectParams);
const closePipe = () => pipe.close().catch(() => {}); const closePipe = () => pipe.close().catch(() => {});
const connection = new Connection(); const connection = new Connection();

View File

@ -93,7 +93,7 @@ export class BrowserTypeDispatcher extends Dispatcher<BrowserType, channels.Brow
waitForNextTask(() => { waitForNextTask(() => {
try { try {
const json = JSON.parse(event.data as string); const json = JSON.parse(event.data as string);
if (params.enableSocksProxy && json.method === '__create__' && json.params.type === 'SocksSupport') if (json.method === '__create__' && json.params.type === 'SocksSupport')
socksInterceptor = new SocksInterceptor(ws, params.socksProxyRedirectPortForTest, json.params.guid); socksInterceptor = new SocksInterceptor(ws, params.socksProxyRedirectPortForTest, json.params.guid);
if (!socksInterceptor?.interceptMessage(json)) if (!socksInterceptor?.interceptMessage(json))
pipe.dispatch(json); pipe.dispatch(json);

View File

@ -603,14 +603,12 @@ export type BrowserTypeConnectParams = {
headers?: any, headers?: any,
slowMo?: number, slowMo?: number,
timeout?: number, timeout?: number,
enableSocksProxy?: boolean,
socksProxyRedirectPortForTest?: number, socksProxyRedirectPortForTest?: number,
}; };
export type BrowserTypeConnectOptions = { export type BrowserTypeConnectOptions = {
headers?: any, headers?: any,
slowMo?: number, slowMo?: number,
timeout?: number, timeout?: number,
enableSocksProxy?: boolean,
socksProxyRedirectPortForTest?: number, socksProxyRedirectPortForTest?: number,
}; };
export type BrowserTypeConnectResult = { export type BrowserTypeConnectResult = {

View File

@ -596,7 +596,6 @@ BrowserType:
headers: json? headers: json?
slowMo: number? slowMo: number?
timeout: number? timeout: number?
enableSocksProxy: boolean?
socksProxyRedirectPortForTest: number? socksProxyRedirectPortForTest: number?
returns: returns:
pipe: JsonPipe pipe: JsonPipe

View File

@ -249,7 +249,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
headers: tOptional(tAny), headers: tOptional(tAny),
slowMo: tOptional(tNumber), slowMo: tOptional(tNumber),
timeout: tOptional(tNumber), timeout: tOptional(tNumber),
enableSocksProxy: tOptional(tBoolean),
socksProxyRedirectPortForTest: tOptional(tNumber), socksProxyRedirectPortForTest: tOptional(tNumber),
}); });
scheme.BrowserTypeLaunchParams = tObject({ scheme.BrowserTypeLaunchParams = tObject({

View File

@ -31,18 +31,20 @@ const debugLog = debug('pw:server');
export class PlaywrightServer { export class PlaywrightServer {
private _path: string; private _path: string;
private _maxClients: number; private _maxClients: number;
private _enableSocksProxy: boolean;
private _browser: Browser | undefined; private _browser: Browser | undefined;
private _wsServer: WebSocket.Server | undefined; private _wsServer: WebSocket.Server | undefined;
private _clientsCount = 0; private _clientsCount = 0;
static async startDefault(options: { path?: string, maxClients?: number } = {}): Promise<PlaywrightServer> { static async startDefault(options: { path?: string, maxClients?: number, enableSocksProxy?: boolean } = {}): Promise<PlaywrightServer> {
const { path = '/ws', maxClients = 1 } = options; const { path = '/ws', maxClients = 1, enableSocksProxy = true } = options;
return new PlaywrightServer(path, maxClients); return new PlaywrightServer(path, maxClients, enableSocksProxy);
} }
constructor(path: string, maxClients: number, browser?: Browser) { constructor(path: string, maxClients: number, enableSocksProxy: boolean, browser?: Browser) {
this._path = path; this._path = path;
this._maxClients = maxClients; this._maxClients = maxClients;
this._enableSocksProxy = enableSocksProxy;
this._browser = browser; this._browser = browser;
} }
@ -75,7 +77,7 @@ export class PlaywrightServer {
return; return;
} }
this._clientsCount++; this._clientsCount++;
const connection = new Connection(ws, request, this._browser, () => this._clientsCount--); const connection = new Connection(ws, request, this._enableSocksProxy, this._browser, () => this._clientsCount--);
(ws as any)[kConnectionSymbol] = connection; (ws as any)[kConnectionSymbol] = connection;
}); });
@ -117,7 +119,7 @@ class Connection {
private _id: number; private _id: number;
private _disconnected = false; private _disconnected = false;
constructor(ws: WebSocket, request: http.IncomingMessage, browser: Browser | undefined, onClose: () => void) { constructor(ws: WebSocket, request: http.IncomingMessage, enableSocksProxy: boolean, browser: Browser | undefined, onClose: () => void) {
this._ws = ws; this._ws = ws;
this._onClose = onClose; this._onClose = onClose;
this._id = ++lastConnectionId; this._id = ++lastConnectionId;
@ -139,32 +141,34 @@ class Connection {
if (browser) if (browser)
return await this._initPreLaunchedBrowserMode(scope, browser); return await this._initPreLaunchedBrowserMode(scope, browser);
const url = new URL('http://localhost' + (request.url || '')); const url = new URL('http://localhost' + (request.url || ''));
const header = request.headers['X-Playwright-Browser']; const browserHeader = request.headers['X-Playwright-Browser'];
const browserAlias = url.searchParams.get('browser') || (Array.isArray(header) ? header[0] : header); const browserAlias = url.searchParams.get('browser') || (Array.isArray(browserHeader) ? browserHeader[0] : browserHeader);
const proxyHeader = request.headers['X-Playwright-Proxy'];
const proxyValue = url.searchParams.get('proxy') || (Array.isArray(proxyHeader) ? proxyHeader[0] : proxyHeader);
if (!browserAlias) if (!browserAlias)
return await this._initPlaywrightConnectMode(scope); return await this._initPlaywrightConnectMode(scope, enableSocksProxy && proxyValue === '*');
return await this._initLaunchBrowserMode(scope, browserAlias); return await this._initLaunchBrowserMode(scope, enableSocksProxy && proxyValue === '*', browserAlias);
}); });
} }
private async _initPlaywrightConnectMode(scope: DispatcherScope) { private async _initPlaywrightConnectMode(scope: DispatcherScope, enableSocksProxy: boolean) {
debugLog(`[id=${this._id}] engaged playwright.connect mode`); debugLog(`[id=${this._id}] engaged playwright.connect mode`);
const playwright = createPlaywright('javascript'); const playwright = createPlaywright('javascript');
// Close all launched browsers on disconnect. // Close all launched browsers on disconnect.
this._cleanups.push(() => gracefullyCloseAll()); this._cleanups.push(() => gracefullyCloseAll());
const socksProxy = await this._enableSocksProxyIfNeeded(playwright); const socksProxy = enableSocksProxy ? await this._enableSocksProxy(playwright) : undefined;
return new PlaywrightDispatcher(scope, playwright, socksProxy); return new PlaywrightDispatcher(scope, playwright, socksProxy);
} }
private async _initLaunchBrowserMode(scope: DispatcherScope, browserAlias: string) { private async _initLaunchBrowserMode(scope: DispatcherScope, enableSocksProxy: boolean, browserAlias: string) {
debugLog(`[id=${this._id}] engaged launch mode for "${browserAlias}"`); debugLog(`[id=${this._id}] engaged launch mode for "${browserAlias}"`);
const executable = registry.findExecutable(browserAlias); const executable = registry.findExecutable(browserAlias);
if (!executable || !executable.browserName) if (!executable || !executable.browserName)
throw new Error(`Unsupported browser "${browserAlias}`); throw new Error(`Unsupported browser "${browserAlias}`);
const playwright = createPlaywright('javascript'); const playwright = createPlaywright('javascript');
const socksProxy = await this._enableSocksProxyIfNeeded(playwright); const socksProxy = enableSocksProxy ? await this._enableSocksProxy(playwright) : undefined;
const browser = await playwright[executable.browserName].launch(internalCallMetadata(), { const browser = await playwright[executable.browserName].launch(internalCallMetadata(), {
channel: executable.type === 'browser' ? undefined : executable.name, channel: executable.type === 'browser' ? undefined : executable.name,
}); });
@ -194,9 +198,7 @@ class Connection {
return playwrightDispatcher; return playwrightDispatcher;
} }
private async _enableSocksProxyIfNeeded(playwright: Playwright) { private async _enableSocksProxy(playwright: Playwright) {
if (!process.env.PW_SOCKS_PROXY_PORT)
return;
const socksProxy = new SocksProxy(); const socksProxy = new SocksProxy();
playwright.options.socksProxyPort = await socksProxy.listen(0); playwright.options.socksProxyPort = await socksProxy.listen(0);
debugLog(`[id=${this._id}] started socks proxy on port ${playwright.options.socksProxyPort}`); debugLog(`[id=${this._id}] started socks proxy on port ${playwright.options.socksProxyPort}`);

View File

@ -67,8 +67,8 @@ const it = contextTest.extend<{ pageFactory: (redirectPortForTest?: number) => P
const server = new OutOfProcessPlaywrightServer(0, 3200 + testInfo.workerIndex); const server = new OutOfProcessPlaywrightServer(0, 3200 + testInfo.workerIndex);
playwrightServers.push(server); playwrightServers.push(server);
const browser = await browserType.connect({ const browser = await browserType.connect({
wsEndpoint: await server.wsEndpoint() + '?browser=' + (channel || browserName), wsEndpoint: await server.wsEndpoint() + '?proxy=*&browser=' + (channel || browserName),
__testHookPortForwarding: { redirectPortForTest }, __testHookRedirectPortForwarding: redirectPortForTest,
} as any); } as any);
browsers.push(browser); browsers.push(browser);
return await browser.newPage(); return await browser.newPage();