Skip to content

Commit 1efbedd

Browse files
authored
cherry-pick(#34535): Revert "Reapply "fix(har timing): record connect timing for proxied connections" (#32855) (#33003)" (#34538)
1 parent 1e258e0 commit 1efbedd

File tree

10 files changed

+423
-403
lines changed

10 files changed

+423
-403
lines changed

packages/playwright-core/ThirdPartyNotices.txt

+380-12
Large diffs are not rendered by default.

packages/playwright-core/bundles/utils/package-lock.json

+22-329
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/playwright-core/bundles/utils/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"diff": "^7.0.0",
1616
"dotenv": "^16.4.5",
1717
"graceful-fs": "4.2.10",
18-
"https-proxy-agent": "7.0.6",
18+
"https-proxy-agent": "5.0.1",
1919
"jpeg-js": "0.4.4",
2020
"mime": "^3.0.0",
2121
"minimatch": "^3.1.2",
@@ -25,7 +25,7 @@
2525
"proxy-from-env": "1.1.0",
2626
"retry": "0.12.0",
2727
"signal-exit": "3.0.7",
28-
"socks-proxy-agent": "8.0.5",
28+
"socks-proxy-agent": "6.1.1",
2929
"stack-utils": "2.0.5",
3030
"ws": "8.17.1",
3131
"yaml": "^2.6.0"

packages/playwright-core/src/server/fetch.ts

+13-21
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import http from 'http';
2020
import https from 'https';
2121
import type { Readable, TransformCallback } from 'stream';
2222
import { pipeline, Transform } from 'stream';
23+
import url from 'url';
2324
import zlib from 'zlib';
2425
import type { HTTPCredentials } from '../../types/types';
2526
import { TimeoutSettings } from '../common/timeoutSettings';
@@ -499,12 +500,12 @@ export abstract class APIRequestContext extends SdkObject {
499500
// happy eyeballs don't emit lookup and connect events, so we use our custom ones
500501
const happyEyeBallsTimings = timingForSocket(socket);
501502
dnsLookupAt = happyEyeBallsTimings.dnsLookupAt;
502-
tcpConnectionAt ??= happyEyeBallsTimings.tcpConnectionAt;
503+
tcpConnectionAt = happyEyeBallsTimings.tcpConnectionAt;
503504

504505
// non-happy-eyeballs sockets
505506
listeners.push(
506507
eventsHelper.addEventListener(socket, 'lookup', () => { dnsLookupAt = monotonicTime(); }),
507-
eventsHelper.addEventListener(socket, 'connect', () => { tcpConnectionAt ??= monotonicTime(); }),
508+
eventsHelper.addEventListener(socket, 'connect', () => { tcpConnectionAt = monotonicTime(); }),
508509
eventsHelper.addEventListener(socket, 'secureConnect', () => {
509510
tlsHandshakeAt = monotonicTime();
510511

@@ -521,21 +522,11 @@ export abstract class APIRequestContext extends SdkObject {
521522
}),
522523
);
523524

524-
// when using socks proxy, having the socket means the connection got established
525-
if (agent instanceof SocksProxyAgent)
526-
tcpConnectionAt ??= monotonicTime();
527-
528525
serverIPAddress = socket.remoteAddress;
529526
serverPort = socket.remotePort;
530527
});
531528
request.on('finish', () => { requestFinishAt = monotonicTime(); });
532529

533-
// http proxy
534-
request.on('proxyConnect', () => {
535-
tcpConnectionAt ??= monotonicTime();
536-
});
537-
538-
539530
progress.log(`→ ${options.method} ${url.toString()}`);
540531
if (options.headers) {
541532
for (const [name, value] of Object.entries(options.headers))
@@ -702,16 +693,17 @@ export class GlobalAPIRequestContext extends APIRequestContext {
702693
}
703694

704695
export function createProxyAgent(proxy: types.ProxySettings) {
705-
const proxyURL = new URL(proxy.server);
706-
if (proxyURL.protocol?.startsWith('socks'))
707-
return new SocksProxyAgent(proxyURL);
708-
696+
const proxyOpts = url.parse(proxy.server);
697+
if (proxyOpts.protocol?.startsWith('socks')) {
698+
return new SocksProxyAgent({
699+
host: proxyOpts.hostname,
700+
port: proxyOpts.port || undefined,
701+
});
702+
}
709703
if (proxy.username)
710-
proxyURL.username = proxy.username;
711-
if (proxy.password)
712-
proxyURL.password = proxy.password;
713-
// TODO: We should use HttpProxyAgent conditional on proxyURL.protocol instead of always using CONNECT method.
714-
return new HttpsProxyAgent(proxyURL);
704+
proxyOpts.auth = `${proxy.username}:${proxy.password || ''}`;
705+
// TODO: We should use HttpProxyAgent conditional on proxyOpts.protocol instead of always using CONNECT method.
706+
return new HttpsProxyAgent(proxyOpts);
715707
}
716708

717709
function toHeadersArray(rawHeaders: string[]): types.HeadersArray {

packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ class SocksProxyConnection {
9898

9999
async connect() {
100100
if (this.socksProxy.proxyAgentFromOptions)
101-
this.target = await this.socksProxy.proxyAgentFromOptions.connect(new EventEmitter() as any, { host: rewriteToLocalhostIfNeeded(this.host), port: this.port, secureEndpoint: false });
101+
this.target = await this.socksProxy.proxyAgentFromOptions.callback(new EventEmitter() as any, { host: rewriteToLocalhostIfNeeded(this.host), port: this.port, secureEndpoint: false });
102102
else
103103
this.target = await createSocket(rewriteToLocalhostIfNeeded(this.host), this.port);
104104

packages/playwright-core/src/utils/network.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function httpRequest(params: HTTPRequestParams, onResponse: (r: http.Inco
5050

5151
const proxyURL = getProxyForUrl(params.url);
5252
if (proxyURL) {
53-
const parsedProxyURL = new URL(proxyURL);
53+
const parsedProxyURL = url.parse(proxyURL);
5454
if (params.url.startsWith('http:')) {
5555
options = {
5656
path: parsedUrl.href,

tests/config/proxy.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ export async function setupSocksForwardingServer({
138138
const socksProxy = new SocksProxy();
139139
socksProxy.setPattern('*');
140140
socksProxy.addListener(SocksProxy.Events.SocksRequested, async (payload: SocksSocketRequestedPayload) => {
141-
if (!['127.0.0.1', '0:0:0:0:0:0:0:1', 'fake-localhost-127-0-0-1.nip.io', 'localhost'].includes(payload.host) || payload.port !== allowedTargetPort) {
141+
if (!['127.0.0.1', 'fake-localhost-127-0-0-1.nip.io', 'localhost'].includes(payload.host) || payload.port !== allowedTargetPort) {
142142
socksProxy.sendSocketError({ uid: payload.uid, error: 'ECONNREFUSED' });
143143
return;
144144
}

tests/library/browsertype-connect.spec.ts

-1
Original file line numberDiff line numberDiff line change
@@ -844,7 +844,6 @@ for (const kind of ['launchServer', 'run-server'] as const) {
844844
});
845845

846846
test('should proxy requests from fetch api', async ({ startRemoteServer, server, browserName, connect, channel, platform, dummyServerPort }, workerInfo) => {
847-
test.fixme(true, 'broken because of socks proxy agent error: Socks5 proxy rejected connection - ConnectionRefused');
848847
test.skip(browserName === 'webkit' && platform === 'darwin', 'no localhost proxying');
849848

850849
let reachedOriginalTarget = false;

tests/library/client-certificates.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ test.describe('browser', () => {
390390
});
391391
expect(connectHosts).toEqual([]);
392392
await page.goto(serverURL);
393-
const host = browserName === 'webkit' && isMac ? '0:0:0:0:0:0:0:1' : '127.0.0.1';
393+
const host = browserName === 'webkit' && isMac ? 'localhost' : '127.0.0.1';
394394
expect(connectHosts).toEqual([`${host}:${serverPort}`]);
395395
await expect(page.getByTestId('message')).toHaveText('Hello Alice, your certificate was issued by localhost!');
396396
await page.close();

tests/library/har.spec.ts

+2-34
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ import type { Log } from '../../packages/trace/src/har';
2424
import { parseHar } from '../config/utils';
2525
const { createHttp2Server } = require('../../packages/playwright-core/lib/utils');
2626

27-
async function pageWithHar(contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>, testInfo: any, options: { outputPath?: string, proxy?: BrowserContextOptions['proxy'] } & Partial<Pick<BrowserContextOptions['recordHar'], 'content' | 'omitContent' | 'mode'>> = {}) {
27+
async function pageWithHar(contextFactory: (options?: BrowserContextOptions) => Promise<BrowserContext>, testInfo: any, options: { outputPath?: string } & Partial<Pick<BrowserContextOptions['recordHar'], 'content' | 'omitContent' | 'mode'>> = {}) {
2828
const harPath = testInfo.outputPath(options.outputPath || 'test.har');
29-
const context = await contextFactory({ recordHar: { path: harPath, ...options }, ignoreHTTPSErrors: true, proxy: options.proxy });
29+
const context = await contextFactory({ recordHar: { path: harPath, ...options }, ignoreHTTPSErrors: true });
3030
const page = await context.newPage();
3131
return {
3232
page,
@@ -861,38 +861,6 @@ it('should respect minimal mode for API Requests', async ({ contextFactory, serv
861861
expect(entry.response.bodySize).toBe(-1);
862862
});
863863

864-
it('should include timings when using http proxy', async ({ contextFactory, server, proxyServer }, testInfo) => {
865-
proxyServer.forwardTo(server.PORT, { allowConnectRequests: true });
866-
const { page, getLog } = await pageWithHar(contextFactory, testInfo, { proxy: { server: `localhost:${proxyServer.PORT}` } });
867-
const response = await page.request.get(server.EMPTY_PAGE);
868-
expect(proxyServer.connectHosts).toEqual([`localhost:${server.PORT}`]);
869-
await expect(response).toBeOK();
870-
const log = await getLog();
871-
expect(log.entries[0].timings.connect).toBeGreaterThan(0);
872-
});
873-
874-
it('should include timings when using socks proxy', async ({ contextFactory, server, socksPort }, testInfo) => {
875-
const { page, getLog } = await pageWithHar(contextFactory, testInfo, { proxy: { server: `socks5://localhost:${socksPort}` } });
876-
const response = await page.request.get(server.EMPTY_PAGE);
877-
expect(await response.text()).toContain('Served by the SOCKS proxy');
878-
await expect(response).toBeOK();
879-
const log = await getLog();
880-
expect(log.entries[0].timings.connect).toBeGreaterThan(0);
881-
});
882-
883-
it('should not have connect and dns timings when socket is reused', async ({ contextFactory, server }, testInfo) => {
884-
const { page, getLog } = await pageWithHar(contextFactory, testInfo);
885-
await page.request.get(server.EMPTY_PAGE);
886-
await page.request.get(server.EMPTY_PAGE);
887-
888-
const log = await getLog();
889-
expect(log.entries).toHaveLength(2);
890-
const request2 = log.entries[1];
891-
expect.soft(request2.timings.connect).toBe(-1);
892-
expect.soft(request2.timings.dns).toBe(-1);
893-
expect.soft(request2.timings.blocked).toBeGreaterThan(0);
894-
});
895-
896864
it('should include redirects from API request', async ({ contextFactory, server }, testInfo) => {
897865
server.setRedirect('/redirect-me', '/simple.json');
898866
const { page, getLog } = await pageWithHar(contextFactory, testInfo);

0 commit comments

Comments
 (0)