fix(network): make allHeaders wait until all headers are available (#15094)
fix(network): make allHeaders wait until all header are available
Before, calling `allHeaders()` from `page.on('request')` would yield
provisional headers instead.
With these changes:
- In Firefox, all headers are available immediately.
- In Chromium, all headers are available upon requestWillBeSentExtraInfo.
- In WebKit, all headers are available upon responseReceived.
- In all browsers, intercepted requests use "provisional" headers
as all headers, since there is no network stack to change the headers.
Drive-by: migrated Chromium to `hasExtraInfo` flags that simplifies
the logic quite a bit.
This commit is contained in:
parent
2f11807552
commit
660516d22a
|
|
@ -54,7 +54,6 @@ export class CRNetworkManager {
|
||||||
eventsHelper.addEventListener(session, 'Fetch.authRequired', this._onAuthRequired.bind(this)),
|
eventsHelper.addEventListener(session, 'Fetch.authRequired', this._onAuthRequired.bind(this)),
|
||||||
eventsHelper.addEventListener(session, 'Network.requestWillBeSent', this._onRequestWillBeSent.bind(this, workerFrame)),
|
eventsHelper.addEventListener(session, 'Network.requestWillBeSent', this._onRequestWillBeSent.bind(this, workerFrame)),
|
||||||
eventsHelper.addEventListener(session, 'Network.requestWillBeSentExtraInfo', this._onRequestWillBeSentExtraInfo.bind(this)),
|
eventsHelper.addEventListener(session, 'Network.requestWillBeSentExtraInfo', this._onRequestWillBeSentExtraInfo.bind(this)),
|
||||||
eventsHelper.addEventListener(session, 'Network.requestServedFromCache', this._onRequestServedFromCache.bind(this)),
|
|
||||||
eventsHelper.addEventListener(session, 'Network.responseReceived', this._onResponseReceived.bind(this)),
|
eventsHelper.addEventListener(session, 'Network.responseReceived', this._onResponseReceived.bind(this)),
|
||||||
eventsHelper.addEventListener(session, 'Network.responseReceivedExtraInfo', this._onResponseReceivedExtraInfo.bind(this)),
|
eventsHelper.addEventListener(session, 'Network.responseReceivedExtraInfo', this._onResponseReceivedExtraInfo.bind(this)),
|
||||||
eventsHelper.addEventListener(session, 'Network.loadingFinished', this._onLoadingFinished.bind(this)),
|
eventsHelper.addEventListener(session, 'Network.loadingFinished', this._onLoadingFinished.bind(this)),
|
||||||
|
|
@ -119,8 +118,6 @@ export class CRNetworkManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onRequestWillBeSent(workerFrame: frames.Frame | undefined, event: Protocol.Network.requestWillBeSentPayload) {
|
_onRequestWillBeSent(workerFrame: frames.Frame | undefined, event: Protocol.Network.requestWillBeSentPayload) {
|
||||||
this._responseExtraInfoTracker.requestWillBeSent(event);
|
|
||||||
|
|
||||||
// Request interception doesn't happen for data URLs with Network Service.
|
// Request interception doesn't happen for data URLs with Network Service.
|
||||||
if (this._protocolRequestInterceptionEnabled && !event.request.url.startsWith('data:')) {
|
if (this._protocolRequestInterceptionEnabled && !event.request.url.startsWith('data:')) {
|
||||||
const requestId = event.requestId;
|
const requestId = event.requestId;
|
||||||
|
|
@ -136,10 +133,6 @@ export class CRNetworkManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onRequestServedFromCache(event: Protocol.Network.requestServedFromCachePayload) {
|
|
||||||
this._responseExtraInfoTracker.requestServedFromCache(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
_onRequestWillBeSentExtraInfo(event: Protocol.Network.requestWillBeSentExtraInfoPayload) {
|
_onRequestWillBeSentExtraInfo(event: Protocol.Network.requestWillBeSentExtraInfoPayload) {
|
||||||
this._responseExtraInfoTracker.requestWillBeSentExtraInfo(event);
|
this._responseExtraInfoTracker.requestWillBeSentExtraInfo(event);
|
||||||
}
|
}
|
||||||
|
|
@ -160,13 +153,6 @@ export class CRNetworkManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onRequestPaused(workerFrame: frames.Frame | undefined, event: Protocol.Fetch.requestPausedPayload) {
|
_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 (!event.networkId) {
|
if (!event.networkId) {
|
||||||
// Fetch without networkId means that request was not recongnized by inspector, and
|
// Fetch without networkId means that request was not recongnized by inspector, and
|
||||||
// it will never receive Network.requestWillBeSent. Most likely, this is an internal request
|
// it will never receive Network.requestWillBeSent. Most likely, this is an internal request
|
||||||
|
|
@ -198,7 +184,7 @@ export class CRNetworkManager {
|
||||||
const request = this._requestIdToRequest.get(requestWillBeSentEvent.requestId);
|
const request = this._requestIdToRequest.get(requestWillBeSentEvent.requestId);
|
||||||
// If we connect late to the target, we could have missed the requestWillBeSent event.
|
// If we connect late to the target, we could have missed the requestWillBeSent event.
|
||||||
if (request) {
|
if (request) {
|
||||||
this._handleRequestRedirect(request, requestWillBeSentEvent.redirectResponse, requestWillBeSentEvent.timestamp);
|
this._handleRequestRedirect(request, requestWillBeSentEvent.redirectResponse, requestWillBeSentEvent.timestamp, requestWillBeSentEvent.redirectHasExtraInfo);
|
||||||
redirectedFrom = request;
|
redirectedFrom = request;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -266,10 +252,16 @@ export class CRNetworkManager {
|
||||||
redirectedFrom
|
redirectedFrom
|
||||||
});
|
});
|
||||||
this._requestIdToRequest.set(requestWillBeSentEvent.requestId, request);
|
this._requestIdToRequest.set(requestWillBeSentEvent.requestId, request);
|
||||||
|
if (requestPausedEvent && !requestPausedEvent.responseStatusCode && !requestPausedEvent.responseErrorReason) {
|
||||||
|
// We will not receive extra info when intercepting the request.
|
||||||
|
// Use the headers from the Fetch.requestPausedPayload and release the allHeaders()
|
||||||
|
// right away, so that client can call it from the route handler.
|
||||||
|
request.request.setRawRequestHeaders(headersObjectToArray(requestPausedEvent.request.headers, '\n'));
|
||||||
|
}
|
||||||
this._page._frameManager.requestStarted(request.request, route || undefined);
|
this._page._frameManager.requestStarted(request.request, route || undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
_createResponse(request: InterceptableRequest, responsePayload: Protocol.Network.Response): network.Response {
|
_createResponse(request: InterceptableRequest, responsePayload: Protocol.Network.Response, hasExtraInfo: boolean): network.Response {
|
||||||
const getResponseBody = async () => {
|
const getResponseBody = async () => {
|
||||||
const contentLengthHeader = Object.entries(responsePayload.headers).find(header => header[0].toLowerCase() === 'content-length');
|
const contentLengthHeader = Object.entries(responsePayload.headers).find(header => header[0].toLowerCase() === 'content-length');
|
||||||
const expectedLength = contentLengthHeader ? +contentLengthHeader[1] : undefined;
|
const expectedLength = contentLengthHeader ? +contentLengthHeader[1] : undefined;
|
||||||
|
|
@ -332,12 +324,18 @@ export class CRNetworkManager {
|
||||||
validFrom: responsePayload?.securityDetails?.validFrom,
|
validFrom: responsePayload?.securityDetails?.validFrom,
|
||||||
validTo: responsePayload?.securityDetails?.validTo,
|
validTo: responsePayload?.securityDetails?.validTo,
|
||||||
});
|
});
|
||||||
this._responseExtraInfoTracker.processResponse(request._requestId, response, request.wasFulfilled());
|
if (hasExtraInfo) {
|
||||||
|
this._responseExtraInfoTracker.processResponse(request._requestId, response);
|
||||||
|
} else {
|
||||||
|
// Use "provisional" headers as "raw" ones.
|
||||||
|
response.request().setRawRequestHeaders(null);
|
||||||
|
response.setRawResponseHeaders(null);
|
||||||
|
}
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleRequestRedirect(request: InterceptableRequest, responsePayload: Protocol.Network.Response, timestamp: number) {
|
_handleRequestRedirect(request: InterceptableRequest, responsePayload: Protocol.Network.Response, timestamp: number, hasExtraInfo: boolean) {
|
||||||
const response = this._createResponse(request, responsePayload);
|
const response = this._createResponse(request, responsePayload, hasExtraInfo);
|
||||||
response._requestFinished((timestamp - request._timestamp) * 1000);
|
response._requestFinished((timestamp - request._timestamp) * 1000);
|
||||||
this._requestIdToRequest.delete(request._requestId);
|
this._requestIdToRequest.delete(request._requestId);
|
||||||
if (request._interceptionId)
|
if (request._interceptionId)
|
||||||
|
|
@ -351,13 +349,11 @@ export class CRNetworkManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onResponseReceived(event: Protocol.Network.responseReceivedPayload) {
|
_onResponseReceived(event: Protocol.Network.responseReceivedPayload) {
|
||||||
this._responseExtraInfoTracker.responseReceived(event);
|
|
||||||
|
|
||||||
const request = this._requestIdToRequest.get(event.requestId);
|
const request = this._requestIdToRequest.get(event.requestId);
|
||||||
// FileUpload sends a response without a matching request.
|
// FileUpload sends a response without a matching request.
|
||||||
if (!request)
|
if (!request)
|
||||||
return;
|
return;
|
||||||
const response = this._createResponse(request, event.response);
|
const response = this._createResponse(request, event.response, event.hasExtraInfo);
|
||||||
this._page._frameManager.requestReceivedResponse(response);
|
this._page._frameManager.requestReceivedResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -484,16 +480,11 @@ class InterceptableRequest {
|
||||||
request = request._redirectedFrom;
|
request = request._redirectedFrom;
|
||||||
return request._route;
|
return request._route;
|
||||||
}
|
}
|
||||||
|
|
||||||
wasFulfilled() {
|
|
||||||
return this._routeForRedirectChain()?._wasFulfilled || false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class RouteImpl implements network.RouteDelegate {
|
class RouteImpl implements network.RouteDelegate {
|
||||||
private readonly _client: CRSession;
|
private readonly _client: CRSession;
|
||||||
private _interceptionId: string;
|
private _interceptionId: string;
|
||||||
_wasFulfilled = false;
|
|
||||||
|
|
||||||
constructor(client: CRSession, interceptionId: string) {
|
constructor(client: CRSession, interceptionId: string) {
|
||||||
this._client = client;
|
this._client = client;
|
||||||
|
|
@ -513,7 +504,6 @@ class RouteImpl implements network.RouteDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fulfill(response: types.NormalizedFulfillResponse) {
|
async fulfill(response: types.NormalizedFulfillResponse) {
|
||||||
this._wasFulfilled = true;
|
|
||||||
const body = response.isBase64 ? response.body : Buffer.from(response.body).toString('base64');
|
const body = response.isBase64 ? response.body : Buffer.from(response.body).toString('base64');
|
||||||
|
|
||||||
const responseHeaders = splitSetCookieHeader(response.headers);
|
const responseHeaders = splitSetCookieHeader(response.headers);
|
||||||
|
|
@ -573,70 +563,39 @@ const errorReasons: { [reason: string]: Protocol.Network.ErrorReason } = {
|
||||||
|
|
||||||
type RequestInfo = {
|
type RequestInfo = {
|
||||||
requestId: string,
|
requestId: string,
|
||||||
requestWillBeSentExtraInfo: Protocol.Network.requestWillBeSentExtraInfoPayload[],
|
// Events are replaced with "undefined" to avoid updating the same headers twice.
|
||||||
responseReceivedExtraInfo: Protocol.Network.responseReceivedExtraInfoPayload[],
|
requestWillBeSentExtraInfo: (Protocol.Network.requestWillBeSentExtraInfoPayload | undefined)[],
|
||||||
|
responseReceivedExtraInfo: (Protocol.Network.responseReceivedExtraInfoPayload | undefined)[],
|
||||||
|
// Note: we only put the responses that expect extra info in this list.
|
||||||
|
// Since the order of responses and extraInfo events is the same, each response
|
||||||
|
// will get a pair of matching request/response extraInfo events in this list.
|
||||||
responses: network.Response[],
|
responses: network.Response[],
|
||||||
loadingFinished?: Protocol.Network.loadingFinishedPayload,
|
loadingFinished?: Protocol.Network.loadingFinishedPayload,
|
||||||
loadingFailed?: Protocol.Network.loadingFailedPayload,
|
loadingFailed?: Protocol.Network.loadingFailedPayload,
|
||||||
sawResponseWithoutConnectionId: boolean
|
|
||||||
requestServedFromCache: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// This class aligns responses with response headers from extra info:
|
// This class aligns responses with response headers from extra info:
|
||||||
// - Network.requestWillBeSent, Network.responseReceived, Network.loadingFinished/loadingFailed are
|
// - Network.requestWillBeSent, Network.responseReceived, Network.loadingFinished/loadingFailed are
|
||||||
// dispatched using one channel.
|
// dispatched using one channel.
|
||||||
// - Network.requestWillBeSentExtraInfo and Network.responseReceivedExtraInfo are dispatches on
|
// - Network.requestWillBeSentExtraInfo and Network.responseReceivedExtraInfo are dispatched on
|
||||||
// another channel. Those channels are not associated, so events come in random order.
|
// another channel. Those channels are not associated, so events come in random order.
|
||||||
//
|
//
|
||||||
// This class will associate responses with the new headers. These extra info headers will become
|
// This class will associate responses with the new headers. These extra info headers will become
|
||||||
// available to client reliably upon requestfinished event only. It consumes CDP
|
// available to client reliably upon requestfinished event only. It consumes CDP
|
||||||
// signals on one end and processResponse(network.Response) signals on the other hands. It then makes
|
// signals on one end and processResponse(network.Response) signals on the other hands. It then makes
|
||||||
// sure that responses have all the extra headers in place by the time request finises.
|
// sure that responses have all the extra headers in place by the time request finishes.
|
||||||
//
|
//
|
||||||
// The shape of the instrumentation API is deliberately following the CDP, so that it
|
// The shape of the instrumentation API is deliberately following the CDP, so that it
|
||||||
// what clear what is called when and what this means to the tracker without extra
|
// is clear what is called when and what this means to the tracker without extra
|
||||||
// documentation.
|
// documentation.
|
||||||
class ResponseExtraInfoTracker {
|
class ResponseExtraInfoTracker {
|
||||||
private _requests = new Map<string, RequestInfo>();
|
private _requests = new Map<string, RequestInfo>();
|
||||||
|
|
||||||
requestWillBeSent(event: Protocol.Network.requestWillBeSentPayload) {
|
|
||||||
const info = this._requests.get(event.requestId);
|
|
||||||
if (info && event.redirectResponse)
|
|
||||||
this._innerResponseReceived(info, event.redirectResponse);
|
|
||||||
else
|
|
||||||
this._getOrCreateEntry(event.requestId);
|
|
||||||
}
|
|
||||||
|
|
||||||
requestWillBeSentExtraInfo(event: Protocol.Network.requestWillBeSentExtraInfoPayload) {
|
requestWillBeSentExtraInfo(event: Protocol.Network.requestWillBeSentExtraInfoPayload) {
|
||||||
const info = this._getOrCreateEntry(event.requestId);
|
const info = this._getOrCreateEntry(event.requestId);
|
||||||
info.requestWillBeSentExtraInfo.push(event);
|
info.requestWillBeSentExtraInfo.push(event);
|
||||||
this._patchHeaders(info, info.requestWillBeSentExtraInfo.length - 1);
|
this._patchHeaders(info, info.requestWillBeSentExtraInfo.length - 1);
|
||||||
}
|
this._checkFinished(info);
|
||||||
|
|
||||||
requestServedFromCache(event: Protocol.Network.requestServedFromCachePayload) {
|
|
||||||
const info = this._getOrCreateEntry(event.requestId);
|
|
||||||
info.requestServedFromCache = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
responseReceived(event: Protocol.Network.responseReceivedPayload) {
|
|
||||||
const info = this._requests.get(event.requestId);
|
|
||||||
if (!info)
|
|
||||||
return;
|
|
||||||
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.
|
|
||||||
info.sawResponseWithoutConnectionId = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
responseReceivedExtraInfo(event: Protocol.Network.responseReceivedExtraInfoPayload) {
|
responseReceivedExtraInfo(event: Protocol.Network.responseReceivedExtraInfoPayload) {
|
||||||
|
|
@ -646,19 +605,8 @@ class ResponseExtraInfoTracker {
|
||||||
this._checkFinished(info);
|
this._checkFinished(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
processResponse(requestId: string, response: network.Response, wasFulfilled: boolean) {
|
processResponse(requestId: string, response: network.Response) {
|
||||||
// We are not interested in ExtraInfo tracking for fulfilled requests, our Blink
|
const info = this._getOrCreateEntry(requestId);
|
||||||
// headers are the ones that contain fulfilled headers.
|
|
||||||
if (wasFulfilled) {
|
|
||||||
this._stopTracking(requestId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const info = this._requests.get(requestId);
|
|
||||||
if (!info || info.sawResponseWithoutConnectionId)
|
|
||||||
return;
|
|
||||||
if (!info.requestServedFromCache)
|
|
||||||
response.setWillReceiveExtraHeaders();
|
|
||||||
info.responses.push(response);
|
info.responses.push(response);
|
||||||
this._patchHeaders(info, info.responses.length - 1);
|
this._patchHeaders(info, info.responses.length - 1);
|
||||||
}
|
}
|
||||||
|
|
@ -687,8 +635,6 @@ class ResponseExtraInfoTracker {
|
||||||
requestWillBeSentExtraInfo: [],
|
requestWillBeSentExtraInfo: [],
|
||||||
responseReceivedExtraInfo: [],
|
responseReceivedExtraInfo: [],
|
||||||
responses: [],
|
responses: [],
|
||||||
sawResponseWithoutConnectionId: false,
|
|
||||||
requestServedFromCache: false
|
|
||||||
};
|
};
|
||||||
this._requests.set(requestId, info);
|
this._requests.set(requestId, info);
|
||||||
}
|
}
|
||||||
|
|
@ -698,12 +644,15 @@ class ResponseExtraInfoTracker {
|
||||||
private _patchHeaders(info: RequestInfo, index: number) {
|
private _patchHeaders(info: RequestInfo, index: number) {
|
||||||
const response = info.responses[index];
|
const response = info.responses[index];
|
||||||
const requestExtraInfo = info.requestWillBeSentExtraInfo[index];
|
const requestExtraInfo = info.requestWillBeSentExtraInfo[index];
|
||||||
if (response && requestExtraInfo)
|
if (response && requestExtraInfo) {
|
||||||
response.request().setRawRequestHeaders(headersObjectToArray(requestExtraInfo.headers, '\n'));
|
response.request().setRawRequestHeaders(headersObjectToArray(requestExtraInfo.headers, '\n'));
|
||||||
|
info.requestWillBeSentExtraInfo[index] = undefined;
|
||||||
|
}
|
||||||
const responseExtraInfo = info.responseReceivedExtraInfo[index];
|
const responseExtraInfo = info.responseReceivedExtraInfo[index];
|
||||||
if (response && responseExtraInfo) {
|
if (response && responseExtraInfo) {
|
||||||
response.setRawResponseHeaders(headersObjectToArray(responseExtraInfo.headers, '\n'));
|
response.setRawResponseHeaders(headersObjectToArray(responseExtraInfo.headers, '\n'));
|
||||||
response.request().responseSize.responseHeadersSize = responseExtraInfo.headersText?.length || 0;
|
response.request().responseSize.responseHeadersSize = responseExtraInfo.headersText?.length || 0;
|
||||||
|
info.responseReceivedExtraInfo[index] = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -713,7 +662,6 @@ class ResponseExtraInfoTracker {
|
||||||
|
|
||||||
if (info.responses.length <= info.responseReceivedExtraInfo.length) {
|
if (info.responses.length <= info.responseReceivedExtraInfo.length) {
|
||||||
// We have extra info for each response.
|
// We have extra info for each response.
|
||||||
// We could have more extra infos because we stopped collecting responses at some point.
|
|
||||||
this._stopTracking(info.requestId);
|
this._stopTracking(info.requestId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,8 @@ export class FFNetworkManager {
|
||||||
validFrom: event?.securityDetails?.validFrom,
|
validFrom: event?.securityDetails?.validFrom,
|
||||||
validTo: event?.securityDetails?.validTo,
|
validTo: event?.securityDetails?.validTo,
|
||||||
});
|
});
|
||||||
|
// "raw" headers are the same as "provisional" headers in Firefox.
|
||||||
|
response.setRawResponseHeaders(null);
|
||||||
this._page._frameManager.requestReceivedResponse(response);
|
this._page._frameManager.requestReceivedResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -194,6 +196,8 @@ class InterceptableRequest {
|
||||||
postDataBuffer = Buffer.from(payload.postData, 'base64');
|
postDataBuffer = Buffer.from(payload.postData, 'base64');
|
||||||
this.request = new network.Request(frame, redirectedFrom ? redirectedFrom.request : null, payload.navigationId,
|
this.request = new network.Request(frame, redirectedFrom ? redirectedFrom.request : null, payload.navigationId,
|
||||||
payload.url, internalCauseToResourceType[payload.internalCause] || causeToResourceType[payload.cause] || 'other', payload.method, postDataBuffer, payload.headers);
|
payload.url, internalCauseToResourceType[payload.internalCause] || causeToResourceType[payload.cause] || 'other', payload.method, postDataBuffer, payload.headers);
|
||||||
|
// "raw" headers are the same as "provisional" headers in Firefox.
|
||||||
|
this.request.setRawRequestHeaders(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
_finalRequest(): InterceptableRequest {
|
_finalRequest(): InterceptableRequest {
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ export class Request extends SdkObject {
|
||||||
private _postData: Buffer | null;
|
private _postData: Buffer | null;
|
||||||
readonly _headers: types.HeadersArray;
|
readonly _headers: types.HeadersArray;
|
||||||
private _headersMap = new Map<string, string>();
|
private _headersMap = new Map<string, string>();
|
||||||
private _rawRequestHeadersPromise: ManualPromise<types.HeadersArray> | undefined;
|
private _rawRequestHeadersPromise = new ManualPromise<types.HeadersArray>();
|
||||||
private _frame: frames.Frame;
|
private _frame: frames.Frame;
|
||||||
private _waitForResponsePromise = new ManualPromise<Response | null>();
|
private _waitForResponsePromise = new ManualPromise<Response | null>();
|
||||||
_responseEndTiming = -1;
|
_responseEndTiming = -1;
|
||||||
|
|
@ -131,6 +131,8 @@ export class Request extends SdkObject {
|
||||||
_setFailureText(failureText: string) {
|
_setFailureText(failureText: string) {
|
||||||
this._failureText = failureText;
|
this._failureText = failureText;
|
||||||
this._waitForResponsePromise.resolve(null);
|
this._waitForResponsePromise.resolve(null);
|
||||||
|
// If we didn't get raw headers, declare them equal to provisional.
|
||||||
|
this.setRawRequestHeaders(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
url(): string {
|
url(): string {
|
||||||
|
|
@ -157,22 +159,13 @@ export class Request extends SdkObject {
|
||||||
return this._headersMap.get(name);
|
return this._headersMap.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
setWillReceiveExtraHeaders() {
|
// "null" means no raw headers available - we'll use provisional headers as raw headers.
|
||||||
if (!this._rawRequestHeadersPromise)
|
setRawRequestHeaders(headers: types.HeadersArray | null) {
|
||||||
this._rawRequestHeadersPromise = new ManualPromise();
|
if (!this._rawRequestHeadersPromise.isDone())
|
||||||
}
|
this._rawRequestHeadersPromise.resolve(headers || this._headers);
|
||||||
|
|
||||||
setRawRequestHeaders(headers: types.HeadersArray) {
|
|
||||||
if (!this._rawRequestHeadersPromise)
|
|
||||||
this._rawRequestHeadersPromise = new ManualPromise();
|
|
||||||
this._rawRequestHeadersPromise!.resolve(headers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async rawRequestHeaders(): Promise<NameValue[]> {
|
async rawRequestHeaders(): Promise<NameValue[]> {
|
||||||
return this._rawRequestHeadersPromise || Promise.resolve(this._headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
rawRequestHeadersPromise(): Promise<types.HeadersArray> | undefined {
|
|
||||||
return this._rawRequestHeadersPromise;
|
return this._rawRequestHeadersPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -222,7 +215,7 @@ export class Request extends SdkObject {
|
||||||
headersSize += this.method().length;
|
headersSize += this.method().length;
|
||||||
headersSize += (new URL(this.url())).pathname.length;
|
headersSize += (new URL(this.url())).pathname.length;
|
||||||
headersSize += 8; // httpVersion
|
headersSize += 8; // httpVersion
|
||||||
const headers = this.rawRequestHeadersPromise() ? await this.rawRequestHeadersPromise()! : this._headers;
|
const headers = await this.rawRequestHeaders();
|
||||||
for (const header of headers)
|
for (const header of headers)
|
||||||
headersSize += header.name.length + header.value.length + 4; // 4 = ': ' + '\r\n'
|
headersSize += header.name.length + header.value.length + 4; // 4 = ': ' + '\r\n'
|
||||||
return headersSize;
|
return headersSize;
|
||||||
|
|
@ -344,11 +337,11 @@ export type RemoteAddr = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SecurityDetails = {
|
export type SecurityDetails = {
|
||||||
protocol?: string;
|
protocol?: string;
|
||||||
subjectName?: string;
|
subjectName?: string;
|
||||||
issuer?: string;
|
issuer?: string;
|
||||||
validFrom?: number;
|
validFrom?: number;
|
||||||
validTo?: number;
|
validTo?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Response extends SdkObject {
|
export class Response extends SdkObject {
|
||||||
|
|
@ -364,7 +357,7 @@ export class Response extends SdkObject {
|
||||||
private _timing: ResourceTiming;
|
private _timing: ResourceTiming;
|
||||||
private _serverAddrPromise = new ManualPromise<RemoteAddr | undefined>();
|
private _serverAddrPromise = new ManualPromise<RemoteAddr | undefined>();
|
||||||
private _securityDetailsPromise = new ManualPromise<SecurityDetails | undefined>();
|
private _securityDetailsPromise = new ManualPromise<SecurityDetails | undefined>();
|
||||||
private _rawResponseHeadersPromise: ManualPromise<types.HeadersArray> | undefined;
|
private _rawResponseHeadersPromise = new ManualPromise<types.HeadersArray>();
|
||||||
private _httpVersion: string | undefined;
|
private _httpVersion: string | undefined;
|
||||||
private _fromServiceWorker: boolean;
|
private _fromServiceWorker: boolean;
|
||||||
|
|
||||||
|
|
@ -393,6 +386,9 @@ export class Response extends SdkObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
_requestFinished(responseEndTiming: number) {
|
_requestFinished(responseEndTiming: number) {
|
||||||
|
// If we didn't get raw headers, declare them equal to provisional.
|
||||||
|
this.setRawResponseHeaders(null);
|
||||||
|
this._request.setRawRequestHeaders(null);
|
||||||
this._request._responseEndTiming = Math.max(responseEndTiming, this._timing.responseStart);
|
this._request._responseEndTiming = Math.max(responseEndTiming, this._timing.responseStart);
|
||||||
this._finishedPromise.resolve();
|
this._finishedPromise.resolve();
|
||||||
}
|
}
|
||||||
|
|
@ -422,18 +418,13 @@ export class Response extends SdkObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
async rawResponseHeaders(): Promise<NameValue[]> {
|
async rawResponseHeaders(): Promise<NameValue[]> {
|
||||||
return this._rawResponseHeadersPromise || Promise.resolve(this._headers);
|
return this._rawResponseHeadersPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
setWillReceiveExtraHeaders() {
|
// "null" means no raw headers available - we'll use provisional headers as raw headers.
|
||||||
this._request.setWillReceiveExtraHeaders();
|
setRawResponseHeaders(headers: types.HeadersArray | null) {
|
||||||
this._rawResponseHeadersPromise = new ManualPromise();
|
if (!this._rawResponseHeadersPromise.isDone())
|
||||||
}
|
this._rawResponseHeadersPromise.resolve(headers || this._headers);
|
||||||
|
|
||||||
setRawResponseHeaders(headers: types.HeadersArray) {
|
|
||||||
if (!this._rawResponseHeadersPromise)
|
|
||||||
this._rawResponseHeadersPromise = new ManualPromise();
|
|
||||||
this._rawResponseHeadersPromise!.resolve(headers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
timing(): ResourceTiming {
|
timing(): ResourceTiming {
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,10 @@ export class WKInterceptableRequest {
|
||||||
responseStart: timingPayload ? wkMillisToRoundishMillis(timingPayload.responseStart) : -1,
|
responseStart: timingPayload ? wkMillisToRoundishMillis(timingPayload.responseStart) : -1,
|
||||||
};
|
};
|
||||||
const setCookieSeparator = process.platform === 'darwin' ? ',' : '\n';
|
const setCookieSeparator = process.platform === 'darwin' ? ',' : '\n';
|
||||||
return new network.Response(this.request, responsePayload.status, responsePayload.statusText, headersObjectToArray(responsePayload.headers, ',', setCookieSeparator), timing, getResponseBody, responsePayload.source === 'service-worker');
|
const response = new network.Response(this.request, responsePayload.status, responsePayload.statusText, headersObjectToArray(responsePayload.headers, ',', setCookieSeparator), timing, getResponseBody, responsePayload.source === 'service-worker');
|
||||||
|
// No raw response headers in WebKit, use "provisional" ones.
|
||||||
|
response.setRawResponseHeaders(null);
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1034,6 +1034,9 @@ export class WKPage implements PageDelegate {
|
||||||
session.sendMayFail('Network.interceptRequestWithError', { errorType: 'Cancellation', requestId: event.requestId });
|
session.sendMayFail('Network.interceptRequestWithError', { errorType: 'Cancellation', requestId: event.requestId });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// There is no point in waiting for the raw headers in Network.responseReceived when intercepting.
|
||||||
|
// Use provisional headers as raw headers, so that client can call allHeaders() from the route handler.
|
||||||
|
request.request.setRawRequestHeaders(null);
|
||||||
if (!request._route) {
|
if (!request._route) {
|
||||||
// Intercepted, although we do not intend to allow interception.
|
// Intercepted, although we do not intend to allow interception.
|
||||||
// Just continue.
|
// Just continue.
|
||||||
|
|
@ -1055,6 +1058,9 @@ export class WKPage implements PageDelegate {
|
||||||
if (!headers['host'])
|
if (!headers['host'])
|
||||||
headers['Host'] = new URL(request.request.url()).host;
|
headers['Host'] = new URL(request.request.url()).host;
|
||||||
request.request.setRawRequestHeaders(headersObjectToArray(headers));
|
request.request.setRawRequestHeaders(headersObjectToArray(headers));
|
||||||
|
} else {
|
||||||
|
// No raw headers avaialable, use provisional ones.
|
||||||
|
request.request.setRawRequestHeaders(null);
|
||||||
}
|
}
|
||||||
this._page._frameManager.requestReceivedResponse(response);
|
this._page._frameManager.requestReceivedResponse(response);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,30 @@ it('should get the same headers as the server', async ({ page, server, browserNa
|
||||||
expect(headers).toEqual(serverRequest.headers);
|
expect(headers).toEqual(serverRequest.headers);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not return allHeaders() until they are available', async ({ page, server, browserName, platform }) => {
|
||||||
|
it.fail(browserName === 'webkit' && platform === 'win32', 'Curl does not show accept-encoding and accept-language');
|
||||||
|
|
||||||
|
let requestHeadersPromise;
|
||||||
|
page.on('request', request => requestHeadersPromise = request.allHeaders());
|
||||||
|
let responseHeadersPromise;
|
||||||
|
page.on('response', response => responseHeadersPromise = response.allHeaders());
|
||||||
|
|
||||||
|
let serverRequest;
|
||||||
|
server.setRoute('/empty.html', async (request, response) => {
|
||||||
|
serverRequest = request;
|
||||||
|
response.writeHead(200, { 'foo': 'bar' });
|
||||||
|
await new Promise(f => setTimeout(f, 3000));
|
||||||
|
response.end('done');
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.goto(server.PREFIX + '/empty.html');
|
||||||
|
const requestHeaders = await requestHeadersPromise;
|
||||||
|
expect(requestHeaders).toEqual(serverRequest.headers);
|
||||||
|
|
||||||
|
const responseHeaders = await responseHeadersPromise;
|
||||||
|
expect(responseHeaders['foo']).toBe('bar');
|
||||||
|
});
|
||||||
|
|
||||||
it('should get the same headers as the server CORS', async ({ page, server, browserName, platform }) => {
|
it('should get the same headers as the server CORS', async ({ page, server, browserName, platform }) => {
|
||||||
it.fail(browserName === 'webkit' && platform === 'win32', 'Curl does not show accept-encoding and accept-language');
|
it.fail(browserName === 'webkit' && platform === 'win32', 'Curl does not show accept-encoding and accept-language');
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue