diff --git a/src/client/network.ts b/src/client/network.ts index 9f31cfced1..8ba5de14b6 100644 --- a/src/client/network.ts +++ b/src/client/network.ts @@ -142,13 +142,8 @@ export class Request extends ChannelOwner { if (!this._actualHeadersPromise) { - this._actualHeadersPromise = this.response().then(response => { - // there is no response, so should we return the headers we have now? - if (!response) - return this._provisionalHeaders; - return response._wrapApiCall(async (channel: channels.ResponseChannel) => { - return new RawHeaders((await channel.rawRequestHeaders()).headers); - }); + this._actualHeadersPromise = this._wrapApiCall(async (channel: channels.RequestChannel) => { + return new RawHeaders((await channel.rawRequestHeaders()).headers); }); } return this._actualHeadersPromise; diff --git a/src/dispatchers/networkDispatchers.ts b/src/dispatchers/networkDispatchers.ts index 3475001824..b7fe2dc5ac 100644 --- a/src/dispatchers/networkDispatchers.ts +++ b/src/dispatchers/networkDispatchers.ts @@ -46,6 +46,10 @@ export class RequestDispatcher extends Dispatcher { + return { headers: await this._object.rawRequestHeaders() }; + } + async response(): Promise { return { response: lookupNullableDispatcher(await this._object.response()) }; } @@ -86,10 +90,6 @@ export class ResponseDispatcher extends Dispatcher { - return { headers: await this._object.rawRequestHeaders() }; - } - async rawResponseHeaders(params?: channels.ResponseRawResponseHeadersParams): Promise { return { headers: await this._object.rawResponseHeaders() }; } diff --git a/src/protocol/channels.ts b/src/protocol/channels.ts index 06c1765393..79576b0d38 100644 --- a/src/protocol/channels.ts +++ b/src/protocol/channels.ts @@ -2770,12 +2770,18 @@ export type RequestInitializer = { }; export interface RequestChannel extends Channel { response(params?: RequestResponseParams, metadata?: Metadata): Promise; + rawRequestHeaders(params?: RequestRawRequestHeadersParams, metadata?: Metadata): Promise; } export type RequestResponseParams = {}; export type RequestResponseOptions = {}; export type RequestResponseResult = { response?: ResponseChannel, }; +export type RequestRawRequestHeadersParams = {}; +export type RequestRawRequestHeadersOptions = {}; +export type RequestRawRequestHeadersResult = { + headers: NameValue[], +}; export interface RequestEvents { } @@ -2864,7 +2870,6 @@ export interface ResponseChannel extends Channel { body(params?: ResponseBodyParams, metadata?: Metadata): Promise; securityDetails(params?: ResponseSecurityDetailsParams, metadata?: Metadata): Promise; serverAddr(params?: ResponseServerAddrParams, metadata?: Metadata): Promise; - rawRequestHeaders(params?: ResponseRawRequestHeadersParams, metadata?: Metadata): Promise; rawResponseHeaders(params?: ResponseRawResponseHeadersParams, metadata?: Metadata): Promise; sizes(params?: ResponseSizesParams, metadata?: Metadata): Promise; } @@ -2883,11 +2888,6 @@ export type ResponseServerAddrOptions = {}; export type ResponseServerAddrResult = { value?: RemoteAddr, }; -export type ResponseRawRequestHeadersParams = {}; -export type ResponseRawRequestHeadersOptions = {}; -export type ResponseRawRequestHeadersResult = { - headers: NameValue[], -}; export type ResponseRawResponseHeadersParams = {}; export type ResponseRawResponseHeadersOptions = {}; export type ResponseRawResponseHeadersResult = { diff --git a/src/protocol/protocol.yml b/src/protocol/protocol.yml index fa5b7614cb..008f7db05a 100644 --- a/src/protocol/protocol.yml +++ b/src/protocol/protocol.yml @@ -2324,6 +2324,11 @@ Request: returns: response: Response? + rawRequestHeaders: + returns: + headers: + type: array + items: NameValue Route: @@ -2407,12 +2412,6 @@ Response: returns: value: RemoteAddr? - rawRequestHeaders: - returns: - headers: - type: array - items: NameValue - rawResponseHeaders: returns: headers: diff --git a/src/protocol/validator.ts b/src/protocol/validator.ts index 8aee49aa20..8946764045 100644 --- a/src/protocol/validator.ts +++ b/src/protocol/validator.ts @@ -1096,6 +1096,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme { state: tOptional(tEnum(['attached', 'detached', 'visible', 'hidden'])), }); scheme.RequestResponseParams = tOptional(tObject({})); + scheme.RequestRawRequestHeadersParams = tOptional(tObject({})); scheme.RouteAbortParams = tObject({ errorCode: tOptional(tString), }); @@ -1128,7 +1129,6 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme { scheme.ResponseBodyParams = tOptional(tObject({})); scheme.ResponseSecurityDetailsParams = tOptional(tObject({})); scheme.ResponseServerAddrParams = tOptional(tObject({})); - scheme.ResponseRawRequestHeadersParams = tOptional(tObject({})); scheme.ResponseRawResponseHeadersParams = tOptional(tObject({})); scheme.ResponseSizesParams = tOptional(tObject({})); scheme.SecurityDetails = tObject({ diff --git a/src/server/chromium/crNetworkManager.ts b/src/server/chromium/crNetworkManager.ts index 6e3bfa5287..695003ecfb 100644 --- a/src/server/chromium/crNetworkManager.ts +++ b/src/server/chromium/crNetworkManager.ts @@ -154,6 +154,13 @@ export class CRNetworkManager { } _onRequestPaused(workerFrame: frames.Frame | undefined, event: Protocol.Fetch.requestPausedPayload) { + if (!event.responseStatusCode && !event.responseErrorReason) { + // Request intercepted, deliver signal to the tracker. + const request = this._requestIdToRequest.get(event.networkId!); + if (request) + this._responseExtraInfoTracker.requestPaused(request.request, event); + } + if (!this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled) { this._client._sendMayFail('Fetch.continueRequest', { requestId: event.requestId @@ -619,6 +626,13 @@ class ResponseExtraInfoTracker { this._innerResponseReceived(info, event.response); } + requestPaused(request: network.Request, event: Protocol.Fetch.requestPausedPayload) { + // requestWillBeSentExtraInfo is not being called when interception + // is enabled. But interception is mutually exclusive with the redirects. + // So we can use the headers from the Fetch.requestPausedPayload immediately. + request.setRawRequestHeaders(headersObjectToArray(event.request.headers, '\n')); + } + private _innerResponseReceived(info: RequestInfo, response: Protocol.Network.Response) { if (!response.connectionId) { // Starting with this response we no longer can guarantee that response and extra info correspond to the same index. @@ -684,7 +698,7 @@ class ResponseExtraInfoTracker { const response = info.responses[index]; const requestExtraInfo = info.requestWillBeSentExtraInfo[index]; if (response && requestExtraInfo) - response.setRawRequestHeaders(headersObjectToArray(requestExtraInfo.headers, '\n')); + response.request().setRawRequestHeaders(headersObjectToArray(requestExtraInfo.headers, '\n')); const responseExtraInfo = info.responseReceivedExtraInfo[index]; if (response && responseExtraInfo) { response.setRawResponseHeaders(headersObjectToArray(responseExtraInfo.headers, '\n')); diff --git a/src/server/network.ts b/src/server/network.ts index 3b3fd9bdc4..21ad84ebfc 100644 --- a/src/server/network.ts +++ b/src/server/network.ts @@ -100,6 +100,7 @@ export class Request extends SdkObject { private _postData: Buffer | null; readonly _headers: types.HeadersArray; private _headersMap = new Map(); + private _rawRequestHeadersPromise: ManualPromise | undefined; private _frame: frames.Frame; private _waitForResponsePromise = new ManualPromise(); _responseEndTiming = -1; @@ -153,8 +154,23 @@ export class Request extends SdkObject { return this._headersMap.get(name); } - async rawHeaders(): Promise { - return this._headers; + setWillReceiveExtraHeaders() { + if (!this._rawRequestHeadersPromise) + this._rawRequestHeadersPromise = new ManualPromise(); + } + + setRawRequestHeaders(headers: types.HeadersArray) { + if (!this._rawRequestHeadersPromise) + this._rawRequestHeadersPromise = new ManualPromise(); + this._rawRequestHeadersPromise!.resolve(headers); + } + + async rawRequestHeaders(): Promise { + return this._rawRequestHeadersPromise || Promise.resolve(this._headers); + } + + rawRequestHeadersPromise(): Promise | undefined { + return this._rawRequestHeadersPromise; } response(): PromiseLike { @@ -197,6 +213,17 @@ export class Request extends SdkObject { bodySize(): number { return this.postDataBuffer()?.length || 0; } + + async requestHeadersSize(): Promise { + let headersSize = 4; // 4 = 2 spaces + 2 line breaks (GET /path \r\n) + headersSize += this.method().length; + headersSize += (new URL(this.url())).pathname.length; + headersSize += 8; // httpVersion + const headers = this.rawRequestHeadersPromise() ? await this.rawRequestHeadersPromise()! : this._headers; + for (const header of headers) + headersSize += header.name.length + header.value.length + 4; // 4 = ': ' + '\r\n' + return headersSize; + } } export class Route extends SdkObject { @@ -316,7 +343,6 @@ export class Response extends SdkObject { private _timing: ResourceTiming; private _serverAddrPromise = new ManualPromise(); private _securityDetailsPromise = new ManualPromise(); - private _rawRequestHeadersPromise: ManualPromise | undefined; private _rawResponseHeadersPromise: ManualPromise | undefined; private _httpVersion: string | undefined; @@ -372,25 +398,15 @@ export class Response extends SdkObject { return this._headersMap.get(name); } - async rawRequestHeaders(): Promise { - return this._rawRequestHeadersPromise || Promise.resolve(this._request._headers); - } - async rawResponseHeaders(): Promise { return this._rawResponseHeadersPromise || Promise.resolve(this._headers); } setWillReceiveExtraHeaders() { - this._rawRequestHeadersPromise = new ManualPromise(); + this._request.setWillReceiveExtraHeaders(); this._rawResponseHeadersPromise = new ManualPromise(); } - setRawRequestHeaders(headers: types.HeadersArray) { - if (!this._rawRequestHeadersPromise) - this._rawRequestHeadersPromise = new ManualPromise(); - this._rawRequestHeadersPromise!.resolve(headers); - } - setRawResponseHeaders(headers: types.HeadersArray) { if (!this._rawResponseHeadersPromise) this._rawResponseHeadersPromise = new ManualPromise(); @@ -436,17 +452,6 @@ export class Response extends SdkObject { return this._httpVersion; } - private async _requestHeadersSize(): Promise { - let headersSize = 4; // 4 = 2 spaces + 2 line breaks (GET /path \r\n) - headersSize += this._request.method().length; - headersSize += (new URL(this.url())).pathname.length; - headersSize += 8; // httpVersion - const headers = this._rawRequestHeadersPromise ? await this._rawRequestHeadersPromise : this._request._headers; - for (const header of headers) - headersSize += header.name.length + header.value.length + 4; // 4 = ': ' + '\r\n' - return headersSize; - } - private async _responseHeadersSize(): Promise { if (this._request.responseSize.responseHeadersSize) return this._request.responseSize.responseHeadersSize; @@ -467,7 +472,7 @@ export class Response extends SdkObject { async sizes(): Promise { await this._finishedPromise; - const requestHeadersSize = await this._requestHeadersSize(); + const requestHeadersSize = await this._request.requestHeadersSize(); const responseHeadersSize = await this._responseHeadersSize(); let { encodedBodySize } = this._request.responseSize; if (!encodedBodySize) { diff --git a/src/server/supplements/har/harTracer.ts b/src/server/supplements/har/harTracer.ts index 88b8f7d776..cad5fc75f0 100644 --- a/src/server/supplements/har/harTracer.ts +++ b/src/server/supplements/har/harTracer.ts @@ -320,7 +320,7 @@ export class HarTracer { if (details) harEntry._securityDetails = details; })); - this._addBarrier(page, response.rawRequestHeaders().then(headers => { + this._addBarrier(page, request.rawRequestHeaders().then(headers => { for (const header of headers.filter(header => header.name.toLowerCase() === 'cookie')) harEntry.request.cookies.push(...header.value.split(';').map(parseCookie)); harEntry.request.headers = headers; diff --git a/src/server/webkit/wkPage.ts b/src/server/webkit/wkPage.ts index d278547a56..2339def513 100644 --- a/src/server/webkit/wkPage.ts +++ b/src/server/webkit/wkPage.ts @@ -1016,7 +1016,7 @@ export class WKPage implements PageDelegate { const headers = { ...event.response.requestHeaders }; if (!headers['host']) headers['Host'] = new URL(request.request.url()).host; - response.setRawRequestHeaders(headersObjectToArray(headers)); + request.request.setRawRequestHeaders(headersObjectToArray(headers)); } this._page._frameManager.requestReceivedResponse(response); diff --git a/tests/page/page-route.spec.ts b/tests/page/page-route.spec.ts index 540dd5574f..ca08fe9f85 100644 --- a/tests/page/page-route.spec.ts +++ b/tests/page/page-route.spec.ts @@ -672,3 +672,13 @@ it('should support the times parameter with route matching', async ({ page, serv await page.goto(server.EMPTY_PAGE); expect(intercepted).toHaveLength(1); }); + +it('should contain raw header', async ({ page, server }) => { + let headers: any; + await page.route('**/*', async route => { + headers = await route.request().allHeaders(); + route.continue(); + }); + await page.goto(server.PREFIX + '/empty.html'); + expect(headers.accept).toBeTruthy(); +});