mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat(tether): always enable socks proxy on the server (#19363)
This commit is contained in:
parent
92b3995101
commit
465278a54f
@ -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: 'disabled' });
|
||||
const server = new PlaywrightServer({ path, maxConnections: 1, preLaunchedAndroidDevice: device, browserProxyMode: 'client' });
|
||||
const wsEndpoint = await server.listen(options.port);
|
||||
|
||||
// 3. Return the BrowserServer interface
|
||||
|
||||
@ -26,6 +26,7 @@ import { createPlaywright } from './server/playwright';
|
||||
import { PlaywrightServer } from './remote/playwrightServer';
|
||||
import { helper } from './server/helper';
|
||||
import { rewriteErrorMessage } from './utils/stackTrace';
|
||||
import { SocksProxy } from './common/socksProxy';
|
||||
|
||||
export class BrowserServerLauncherImpl implements BrowserServerLauncher {
|
||||
private _browserName: 'chromium' | 'firefox' | 'webkit';
|
||||
@ -36,6 +37,9 @@ export class BrowserServerLauncherImpl implements BrowserServerLauncher {
|
||||
|
||||
async launchServer(options: LaunchServerOptions = {}): Promise<BrowserServer> {
|
||||
const playwright = createPlaywright('javascript');
|
||||
const socksProxy = new SocksProxy();
|
||||
playwright.options.socksProxyPort = await socksProxy.listen(0);
|
||||
|
||||
// 1. Pre-launch the browser
|
||||
const metadata = serverSideCallMetadata();
|
||||
const browser = await playwright[this._browserName].launch(metadata, {
|
||||
@ -52,7 +56,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: 'disabled', preLaunchedBrowser: browser });
|
||||
const server = new PlaywrightServer({ path, maxConnections: Infinity, browserProxyMode: 'client', preLaunchedBrowser: browser, preLaunchedSocksProxy: socksProxy });
|
||||
const wsEndpoint = await server.listen(options.port);
|
||||
|
||||
// 3. Return the BrowserServer interface
|
||||
@ -63,7 +67,8 @@ export class BrowserServerLauncherImpl implements BrowserServerLauncher {
|
||||
browserServer.kill = () => browser.options.browserProcess.kill();
|
||||
(browserServer as any)._disconnectForTest = () => server.close();
|
||||
(browserServer as any)._userDataDirForTest = (browser as any)._userDataDirForTest;
|
||||
browser.options.browserProcess.onclose = async (exitCode, signal) => {
|
||||
browser.options.browserProcess.onclose = (exitCode, signal) => {
|
||||
socksProxy.close().catch(() => {});
|
||||
server.close();
|
||||
browserServer.emit('close', exitCode, signal);
|
||||
};
|
||||
|
||||
@ -270,7 +270,7 @@ program
|
||||
.option('--port <port>', 'Server port')
|
||||
.option('--path <path>', 'Endpoint Path', '/')
|
||||
.option('--max-clients <maxClients>', 'Maximum clients')
|
||||
.option('--proxy-mode <mode>', 'Either `client`, `tether` or `disabled`. Defaults to `client`.', 'client')
|
||||
.option('--proxy-mode <mode>', 'Either `client` or `tether`. Defaults to `client`.', 'client')
|
||||
.action(function(options) {
|
||||
runServer({
|
||||
port: options.port ? +options.port : undefined,
|
||||
|
||||
@ -50,7 +50,7 @@ export type RunServerOptions = {
|
||||
port?: number,
|
||||
path?: string,
|
||||
maxConnections?: number,
|
||||
browserProxyMode?: 'client' | 'tether' | 'disabled',
|
||||
browserProxyMode?: 'client' | 'tether',
|
||||
ownedByTetherClient?: boolean,
|
||||
};
|
||||
|
||||
|
||||
@ -317,7 +317,7 @@ export class SocksProxy extends EventEmitter implements SocksConnectionClient {
|
||||
});
|
||||
}
|
||||
|
||||
setProxyPattern(pattern: string | undefined) {
|
||||
setPattern(pattern: string | undefined) {
|
||||
this._pattern = pattern;
|
||||
}
|
||||
|
||||
@ -364,6 +364,8 @@ export class SocksProxy extends EventEmitter implements SocksConnectionClient {
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (this._closed)
|
||||
return;
|
||||
this._closed = true;
|
||||
for (const socket of this._sockets)
|
||||
socket.destroy();
|
||||
|
||||
@ -23,7 +23,7 @@ function launchGridBrowserWorker(gridURL: string, agentId: string, workerId: str
|
||||
const log = debug(`pw:grid:worker:${workerId}`);
|
||||
log('created');
|
||||
const ws = new WebSocket(gridURL.replace('http://', 'ws://') + `/registerWorker?agentId=${agentId}&workerId=${workerId}`);
|
||||
new PlaywrightConnection(Promise.resolve(), 'launch-browser', ws, { socksProxy: '*', browserName, launchOptions: {} }, { }, log, async () => {
|
||||
new PlaywrightConnection(Promise.resolve(), 'launch-browser', ws, { socksProxyPattern: '*', browserName, launchOptions: {} }, { }, log, async () => {
|
||||
log('exiting process');
|
||||
setTimeout(() => process.exit(0), 30000);
|
||||
// Meanwhile, try to gracefully close all browsers.
|
||||
|
||||
@ -25,10 +25,10 @@ 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' | 'network-tethering';
|
||||
export type ClientType = 'controller' | 'playwright' | 'launch-browser' | 'reuse-browser' | 'pre-launched-browser-or-android' | 'network-tethering';
|
||||
|
||||
type Options = {
|
||||
socksProxy: string | undefined,
|
||||
socksProxyPattern: string | undefined,
|
||||
browserName: string | null,
|
||||
launchOptions: LaunchOptions,
|
||||
};
|
||||
@ -37,7 +37,8 @@ type PreLaunched = {
|
||||
playwright?: Playwright | undefined;
|
||||
browser?: Browser | undefined;
|
||||
androidDevice?: AndroidDevice | undefined;
|
||||
networkTetheringSocksProxy?: SocksProxy | undefined;
|
||||
ownedSocksProxy?: SocksProxy | undefined;
|
||||
sharedSocksProxy?: SocksProxy | undefined;
|
||||
};
|
||||
|
||||
export class PlaywrightConnection {
|
||||
@ -55,9 +56,9 @@ export class PlaywrightConnection {
|
||||
this._ws = ws;
|
||||
this._preLaunched = preLaunched;
|
||||
this._options = options;
|
||||
if (clientType === 'reuse-browser' || clientType === 'pre-launched-browser')
|
||||
if (clientType === 'reuse-browser' || clientType === 'pre-launched-browser-or-android')
|
||||
assert(preLaunched.playwright);
|
||||
if (clientType === 'pre-launched-browser')
|
||||
if (clientType === 'pre-launched-browser-or-android')
|
||||
assert(preLaunched.browser || preLaunched.androidDevice);
|
||||
this._onClose = onClose;
|
||||
this._debugLog = log;
|
||||
@ -84,7 +85,7 @@ export class PlaywrightConnection {
|
||||
this._root = new RootDispatcher(this._dispatcherConnection, async scope => {
|
||||
if (clientType === 'reuse-browser')
|
||||
return await this._initReuseBrowsersMode(scope);
|
||||
if (clientType === 'pre-launched-browser')
|
||||
if (clientType === 'pre-launched-browser-or-android')
|
||||
return this._preLaunched.browser ? await this._initPreLaunchedBrowserMode(scope) : await this._initPreLaunchedAndroidMode(scope);
|
||||
if (clientType === 'launch-browser')
|
||||
return await this._initLaunchBrowserMode(scope);
|
||||
@ -99,8 +100,9 @@ export class PlaywrightConnection {
|
||||
private async _initPlaywrightTetheringMode(scope: RootDispatcher) {
|
||||
this._debugLog(`engaged playwright.tethering mode`);
|
||||
const playwright = createPlaywright('javascript');
|
||||
this._preLaunched.networkTetheringSocksProxy?.setProxyPattern(this._options.socksProxy);
|
||||
return new PlaywrightDispatcher(scope, playwright, this._preLaunched.networkTetheringSocksProxy);
|
||||
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) {
|
||||
@ -111,15 +113,30 @@ export class PlaywrightConnection {
|
||||
await Promise.all(playwright.allBrowsers().map(browser => browser.close()));
|
||||
});
|
||||
|
||||
const socksProxy = await this._configureSocksProxy(playwright);
|
||||
return new PlaywrightDispatcher(scope, playwright, socksProxy);
|
||||
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);
|
||||
}
|
||||
return new PlaywrightDispatcher(scope, playwright, ownedSocksProxy);
|
||||
}
|
||||
|
||||
private async _initLaunchBrowserMode(scope: RootDispatcher) {
|
||||
this._debugLog(`engaged launch mode for "${this._options.browserName}"`);
|
||||
|
||||
const playwright = createPlaywright('javascript');
|
||||
const socksProxy = await this._configureSocksProxy(playwright);
|
||||
|
||||
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 browser = await playwright[this._options.browserName as 'chromium'].launch(serverSideCallMetadata(), this._options.launchOptions);
|
||||
|
||||
this._cleanups.push(async () => {
|
||||
@ -131,18 +148,24 @@ export class PlaywrightConnection {
|
||||
this.close({ code: 1001, reason: 'Browser closed' });
|
||||
});
|
||||
|
||||
return new PlaywrightDispatcher(scope, playwright, socksProxy, browser);
|
||||
return new PlaywrightDispatcher(scope, playwright, ownedSocksProxy, browser);
|
||||
}
|
||||
|
||||
private async _initPreLaunchedBrowserMode(scope: RootDispatcher) {
|
||||
this._debugLog(`engaged pre-launched (browser) mode`);
|
||||
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);
|
||||
|
||||
const browser = this._preLaunched.browser!;
|
||||
browser.on(Browser.Events.Disconnected, () => {
|
||||
// Underlying browser did close for some reason - force disconnect the client.
|
||||
this.close({ code: 1001, reason: 'Browser closed' });
|
||||
});
|
||||
const playwrightDispatcher = new PlaywrightDispatcher(scope, playwright, undefined, browser);
|
||||
|
||||
const playwrightDispatcher = new PlaywrightDispatcher(scope, playwright, this._preLaunched.ownedSocksProxy, browser);
|
||||
// In pre-launched mode, keep only the pre-launched browser.
|
||||
for (const b of playwright.allBrowsers()) {
|
||||
if (b !== browser)
|
||||
@ -175,6 +198,11 @@ export class PlaywrightConnection {
|
||||
private async _initReuseBrowsersMode(scope: RootDispatcher) {
|
||||
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)
|
||||
@ -221,16 +249,9 @@ export class PlaywrightConnection {
|
||||
return playwrightDispatcher;
|
||||
}
|
||||
|
||||
private async _configureSocksProxy(playwright: Playwright): Promise<undefined|SocksProxy> {
|
||||
if (!this._options.socksProxy)
|
||||
return undefined;
|
||||
if (this._preLaunched.networkTetheringSocksProxy) {
|
||||
playwright.options.socksProxyPort = this._preLaunched.networkTetheringSocksProxy.port();
|
||||
this._debugLog(`using network tether proxy on port ${playwright.options.socksProxyPort}`);
|
||||
return undefined;
|
||||
}
|
||||
private async _createOwnedSocksProxy(playwright: Playwright): Promise<SocksProxy | undefined> {
|
||||
const socksProxy = new SocksProxy();
|
||||
socksProxy.setProxyPattern(this._options.socksProxy);
|
||||
socksProxy.setPattern(this._options.socksProxyPattern);
|
||||
playwright.options.socksProxyPort = await socksProxy.listen(0);
|
||||
this._debugLog(`started socks proxy on port ${playwright.options.socksProxyPort}`);
|
||||
this._cleanups.push(() => socksProxy.close());
|
||||
|
||||
@ -40,9 +40,10 @@ function newLogger() {
|
||||
type ServerOptions = {
|
||||
path: string;
|
||||
maxConnections: number;
|
||||
preLaunchedBrowser?: Browser
|
||||
preLaunchedAndroidDevice?: AndroidDevice
|
||||
browserProxyMode: 'client' | 'tether' | 'disabled',
|
||||
preLaunchedBrowser?: Browser;
|
||||
preLaunchedAndroidDevice?: AndroidDevice;
|
||||
preLaunchedSocksProxy?: SocksProxy;
|
||||
browserProxyMode: 'client' | 'tether';
|
||||
ownedByTetherClient?: boolean;
|
||||
};
|
||||
|
||||
@ -61,12 +62,6 @@ export class PlaywrightServer {
|
||||
this._preLaunchedPlaywright = options.preLaunchedAndroidDevice._android._playwrightOptions.rootSdkObject as Playwright;
|
||||
}
|
||||
|
||||
preLaunchedPlaywright(): Playwright {
|
||||
if (!this._preLaunchedPlaywright)
|
||||
this._preLaunchedPlaywright = createPlaywright('javascript');
|
||||
return this._preLaunchedPlaywright;
|
||||
}
|
||||
|
||||
async listen(port: number = 0): Promise<string> {
|
||||
const server = http.createServer((request: http.IncomingMessage, response: http.ServerResponse) => {
|
||||
if (request.method === 'GET' && request.url === '/json') {
|
||||
@ -115,7 +110,6 @@ export class PlaywrightServer {
|
||||
const browserName = url.searchParams.get('browser') || (Array.isArray(browserHeader) ? browserHeader[0] : browserHeader) || null;
|
||||
const proxyHeader = request.headers['x-playwright-proxy'];
|
||||
const proxyValue = url.searchParams.get('proxy') || (Array.isArray(proxyHeader) ? proxyHeader[0] : proxyHeader);
|
||||
const socksProxy = this._options.browserProxyMode !== 'disabled' ? proxyValue : undefined;
|
||||
|
||||
const launchOptionsHeader = request.headers['x-playwright-launch-options'] || '';
|
||||
let launchOptions: LaunchOptions = {};
|
||||
@ -131,9 +125,11 @@ export class PlaywrightServer {
|
||||
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 (isDebugControllerClient || shouldReuseBrowser)
|
||||
this.preLaunchedPlaywright();
|
||||
// If we get a reuse-controller request, create this._preLaunchedPlaywright.
|
||||
if (isDebugControllerClient || shouldReuseBrowser) {
|
||||
if (!this._preLaunchedPlaywright)
|
||||
this._preLaunchedPlaywright = createPlaywright('javascript');
|
||||
}
|
||||
|
||||
let clientType: ClientType = 'playwright';
|
||||
let semaphore: Semaphore = browserSemaphore;
|
||||
@ -147,7 +143,7 @@ export class PlaywrightServer {
|
||||
clientType = 'reuse-browser';
|
||||
semaphore = reuseBrowserSemaphore;
|
||||
} else if (this._options.preLaunchedBrowser || this._options.preLaunchedAndroidDevice) {
|
||||
clientType = 'pre-launched-browser';
|
||||
clientType = 'pre-launched-browser-or-android';
|
||||
semaphore = browserSemaphore;
|
||||
} else if (browserName) {
|
||||
clientType = 'launch-browser';
|
||||
@ -160,12 +156,13 @@ export class PlaywrightServer {
|
||||
const connection = new PlaywrightConnection(
|
||||
semaphore.aquire(),
|
||||
clientType, ws,
|
||||
{ socksProxy, browserName, launchOptions },
|
||||
{ socksProxyPattern: proxyValue, browserName, launchOptions },
|
||||
{
|
||||
playwright: this._preLaunchedPlaywright,
|
||||
browser: this._options.preLaunchedBrowser,
|
||||
androidDevice: this._options.preLaunchedAndroidDevice,
|
||||
networkTetheringSocksProxy: this._networkTetheringSocksProxy,
|
||||
ownedSocksProxy: this._options.preLaunchedSocksProxy,
|
||||
sharedSocksProxy: this._networkTetheringSocksProxy,
|
||||
},
|
||||
log, () => {
|
||||
semaphore.release();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user