From fd6209c8455a168aff84d92ff9de39af4ee288be Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Fri, 5 May 2023 11:36:37 -0700 Subject: [PATCH] [https-proxy-agent] Emit "proxyConnect" event on HTTP `req` (#154) Part of #153. I'd like to also emit this event on the `agent` instance itself, but this is currently blocked by https://github.com/DefinitelyTyped/DefinitelyTyped/pull/65408. --- .changeset/blue-buses-build.md | 5 ++ packages/https-proxy-agent/src/index.ts | 7 +-- .../src/parse-proxy-response.ts | 48 ++++++++++++++----- packages/https-proxy-agent/test/test.ts | 8 +++- 4 files changed, 53 insertions(+), 15 deletions(-) create mode 100644 .changeset/blue-buses-build.md diff --git a/.changeset/blue-buses-build.md b/.changeset/blue-buses-build.md new file mode 100644 index 00000000..481fb71f --- /dev/null +++ b/.changeset/blue-buses-build.md @@ -0,0 +1,5 @@ +--- +'https-proxy-agent': minor +--- + +Emit "proxyConnect" event on HTTP `request` object (part of #153) diff --git a/packages/https-proxy-agent/src/index.ts b/packages/https-proxy-agent/src/index.ts index 7ff413f3..8dcab323 100644 --- a/packages/https-proxy-agent/src/index.ts +++ b/packages/https-proxy-agent/src/index.ts @@ -5,7 +5,7 @@ import assert from 'assert'; import createDebug from 'debug'; import { OutgoingHttpHeaders } from 'http'; import { Agent, AgentConnectOpts } from 'agent-base'; -import parseProxyResponse from './parse-proxy-response'; +import { parseProxyResponse } from './parse-proxy-response'; const debug = createDebug('https-proxy-agent'); @@ -130,9 +130,10 @@ export class HttpsProxyAgent extends Agent { socket.write(`${payload}\r\n`); - const { statusCode, buffered } = await proxyResponsePromise; + const { connect, buffered } = await proxyResponsePromise; + req.emit('proxyConnect', connect); - if (statusCode === 200) { + if (connect.statusCode === 200) { req.once('socket', resume); if (opts.secureEndpoint) { diff --git a/packages/https-proxy-agent/src/parse-proxy-response.ts b/packages/https-proxy-agent/src/parse-proxy-response.ts index fc57ff6a..5215260c 100644 --- a/packages/https-proxy-agent/src/parse-proxy-response.ts +++ b/packages/https-proxy-agent/src/parse-proxy-response.ts @@ -1,16 +1,18 @@ import createDebug from 'debug'; +import { IncomingHttpHeaders } from 'http'; import { Readable } from 'stream'; const debug = createDebug('https-proxy-agent:parse-proxy-response'); -export interface ProxyResponse { +export interface ConnectResponse { statusCode: number; - buffered: Buffer; + statusText: string; + headers: IncomingHttpHeaders; } -export default function parseProxyResponse( +export function parseProxyResponse( socket: Readable -): Promise { +): Promise<{ connect: ConnectResponse; buffered: Buffer }> { return new Promise((resolve, reject) => { // we need to buffer any HTTP traffic that happens with the proxy before we get // the CONNECT response, so that if the response is anything other than an "200" @@ -60,16 +62,40 @@ export default function parseProxyResponse( return; } - const firstLine = buffered.toString( - 'ascii', - 0, - buffered.indexOf('\r\n') - ); - const statusCode = +firstLine.split(' ')[1]; + const headerParts = buffered.toString('ascii').split('\r\n'); + const firstLine = headerParts.shift(); + if (!firstLine) { + throw new Error('No header received'); + } + const firstLineParts = firstLine.split(' '); + const statusCode = +firstLineParts[1]; + const statusText = firstLineParts.slice(2).join(' '); + const headers: IncomingHttpHeaders = {}; + for (const header of headerParts) { + if (!header) continue; + const firstColon = header.indexOf(':'); + if (firstColon === -1) { + throw new Error(`Invalid header: "${header}"`); + } + const key = header.slice(0, firstColon).toLowerCase(); + const value = header.slice(firstColon + 1).trimStart(); + const current = headers[key]; + if (typeof current === 'string') { + headers[key] = [current, value]; + } else if (Array.isArray(current)) { + current.push(value); + } else { + headers[key] = value; + } + } debug('got proxy server response: %o', firstLine); cleanup(); resolve({ - statusCode, + connect: { + statusCode, + statusText, + headers, + }, buffered, }); } diff --git a/packages/https-proxy-agent/test/test.ts b/packages/https-proxy-agent/test/test.ts index dad550f1..1da41a14 100644 --- a/packages/https-proxy-agent/test/test.ts +++ b/packages/https-proxy-agent/test/test.ts @@ -106,7 +106,13 @@ describe('HttpsProxyAgent', () => { const agent = new HttpsProxyAgent(proxyUrl); - const res = await req(serverUrl, { agent }); + const r = req(serverUrl, { agent }); + const [connect] = await once(r, 'proxyConnect'); + expect(connect.statusCode).toEqual(200); + expect(connect.statusText).toEqual('Connection established'); + expect('date' in connect.headers).toBe(true); + + const res = await r; const body = await json(res); assert.equal(serverUrl.host, body.host); });