fix(chromium): get headers from browser process when intercepting (#1809)

This commit is contained in:
Joel Einbinder 2020-04-15 23:18:16 -07:00 committed by GitHub
parent ba36860d79
commit 1b0467fb86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 126 additions and 28 deletions

View File

@ -33,7 +33,7 @@ export class CRNetworkManager {
private _attemptedAuthentications = new Set<string>(); private _attemptedAuthentications = new Set<string>();
private _userRequestInterceptionEnabled = false; private _userRequestInterceptionEnabled = false;
private _protocolRequestInterceptionEnabled = false; private _protocolRequestInterceptionEnabled = false;
private _requestIdToInterceptionId = new Map<string, string>(); private _requestIdToRequestPausedEvent = new Map<string, Protocol.Fetch.requestPausedPayload>();
private _eventListeners: RegisteredListener[]; private _eventListeners: RegisteredListener[];
constructor(client: CRSession, page: Page) { constructor(client: CRSession, page: Page) {
@ -106,10 +106,10 @@ export class CRNetworkManager {
// 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;
const interceptionId = this._requestIdToInterceptionId.get(requestId); const requestPausedEvent = this._requestIdToRequestPausedEvent.get(requestId);
if (interceptionId) { if (requestPausedEvent) {
this._onRequest(workerFrame, event, interceptionId); this._onRequest(workerFrame, event, requestPausedEvent);
this._requestIdToInterceptionId.delete(requestId); this._requestIdToRequestPausedEvent.delete(requestId);
} else { } else {
this._requestIdToRequestWillBeSentEvent.set(event.requestId, event); this._requestIdToRequestWillBeSentEvent.set(event.requestId, event);
} }
@ -143,49 +143,56 @@ export class CRNetworkManager {
return; return;
const requestId = event.networkId; const requestId = event.networkId;
const interceptionId = event.requestId;
const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(requestId); const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(requestId);
if (requestWillBeSentEvent) { if (requestWillBeSentEvent) {
this._onRequest(workerFrame, requestWillBeSentEvent, interceptionId); this._onRequest(workerFrame, requestWillBeSentEvent, event);
this._requestIdToRequestWillBeSentEvent.delete(requestId); this._requestIdToRequestWillBeSentEvent.delete(requestId);
} else { } else {
this._requestIdToInterceptionId.set(requestId, interceptionId); this._requestIdToRequestPausedEvent.set(requestId, event);
} }
} }
_onRequest(workerFrame: frames.Frame | undefined, event: Protocol.Network.requestWillBeSentPayload, interceptionId: string | null) { _onRequest(workerFrame: frames.Frame | undefined, requestWillBeSentEvent: Protocol.Network.requestWillBeSentPayload, requestPausedEvent: Protocol.Fetch.requestPausedPayload | null) {
if (event.request.url.startsWith('data:')) if (requestWillBeSentEvent.request.url.startsWith('data:'))
return; return;
let redirectedFrom: network.Request | null = null; let redirectedFrom: network.Request | null = null;
if (event.redirectResponse) { if (requestWillBeSentEvent.redirectResponse) {
const request = this._requestIdToRequest.get(event.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, event.redirectResponse); this._handleRequestRedirect(request, requestWillBeSentEvent.redirectResponse);
redirectedFrom = request.request; redirectedFrom = request.request;
} }
} }
let frame = event.frameId ? this._page._frameManager.frame(event.frameId) : workerFrame; let frame = requestWillBeSentEvent.frameId ? this._page._frameManager.frame(requestWillBeSentEvent.frameId) : workerFrame;
// Check if it's main resource request interception (targetId === main frame id). // Check if it's main resource request interception (targetId === main frame id).
if (!frame && interceptionId && event.frameId === (this._page._delegate as CRPage)._targetId) { if (!frame && requestPausedEvent && requestWillBeSentEvent.frameId === (this._page._delegate as CRPage)._targetId) {
// Main resource request for the page is being intercepted so the Frame is not created // Main resource request for the page is being intercepted so the Frame is not created
// yet. Precreate it here for the purposes of request interception. It will be updated // yet. Precreate it here for the purposes of request interception. It will be updated
// later as soon as the request contnues and we receive frame tree from the page. // later as soon as the request contnues and we receive frame tree from the page.
frame = this._page._frameManager.frameAttached(event.frameId, null); frame = this._page._frameManager.frameAttached(requestWillBeSentEvent.frameId, null);
} }
if (!frame) { if (!frame) {
if (interceptionId) if (requestPausedEvent)
this._client.send('Fetch.continueRequest', { requestId: interceptionId }).catch(debugError); this._client.send('Fetch.continueRequest', { requestId: requestPausedEvent.requestId }).catch(debugError);
return; return;
} }
const isNavigationRequest = event.requestId === event.loaderId && event.type === 'Document'; const isNavigationRequest = requestWillBeSentEvent.requestId === requestWillBeSentEvent.loaderId && requestWillBeSentEvent.type === 'Document';
const documentId = isNavigationRequest ? event.loaderId : undefined; const documentId = isNavigationRequest ? requestWillBeSentEvent.loaderId : undefined;
if (isNavigationRequest) if (isNavigationRequest)
this._page._frameManager.frameUpdatedDocumentIdForNavigation(event.frameId!, documentId!); this._page._frameManager.frameUpdatedDocumentIdForNavigation(requestWillBeSentEvent.frameId!, documentId!);
const request = new InterceptableRequest(this._client, frame, interceptionId, documentId, this._userRequestInterceptionEnabled, event, redirectedFrom); const request = new InterceptableRequest({
this._requestIdToRequest.set(event.requestId, request); client: this._client,
frame,
documentId,
allowInterception: this._userRequestInterceptionEnabled,
requestWillBeSentEvent,
requestPausedEvent,
redirectedFrom
});
this._requestIdToRequest.set(requestWillBeSentEvent.requestId, request);
this._page._frameManager.requestStarted(request.request); this._page._frameManager.requestStarted(request.request);
} }
@ -258,14 +265,29 @@ class InterceptableRequest implements network.RouteDelegate {
_documentId: string | undefined; _documentId: string | undefined;
private _client: CRSession; private _client: CRSession;
constructor(client: CRSession, frame: frames.Frame, interceptionId: string | null, documentId: string | undefined, allowInterception: boolean, event: Protocol.Network.requestWillBeSentPayload, redirectedFrom: network.Request | null) { constructor(options: {
client: CRSession;
frame: frames.Frame;
documentId?: string;
allowInterception: boolean;
requestWillBeSentEvent: Protocol.Network.requestWillBeSentPayload;
requestPausedEvent: Protocol.Fetch.requestPausedPayload | null;
redirectedFrom: network.Request | null;
}) {
const { client, frame, documentId, allowInterception, requestWillBeSentEvent, requestPausedEvent, redirectedFrom } = options;
this._client = client; this._client = client;
this._requestId = event.requestId; this._requestId = requestWillBeSentEvent.requestId;
this._interceptionId = interceptionId; this._interceptionId = requestPausedEvent && requestPausedEvent.requestId;
this._documentId = documentId; this._documentId = documentId;
this.request = new network.Request(allowInterception ? this : null, frame, redirectedFrom, documentId, const {
event.request.url, (event.type || '').toLowerCase(), event.request.method, event.request.postData || null, headersObject(event.request.headers)); headers,
method,
url,
postData = null,
} = requestPausedEvent ? requestPausedEvent.request : requestWillBeSentEvent.request;
const type = (requestWillBeSentEvent.type || '').toLowerCase();
this.request = new network.Request(allowInterception ? this : null, frame, redirectedFrom, documentId, url, type, method, postData, headersObject(headers));
} }
async continue(overrides: { method?: string; headers?: network.Headers; postData?: string } = {}) { async continue(overrides: { method?: string; headers?: network.Headers; postData?: string } = {}) {

View File

@ -511,6 +511,65 @@ describe('Request.fulfill', function() {
expect(headers.foo).toBe('true'); expect(headers.foo).toBe('true');
expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!'); expect(await page.evaluate(() => document.body.textContent)).toBe('Yo, page!');
}); });
it('should not modify the headers sent to the server', async({page, server}) => {
await page.goto(server.PREFIX + '/empty.html');
const interceptedRequests = [];
//this is just to enable request interception, which disables caching in chromium
await page.route(server.PREFIX + '/unused');
server.setRoute('/something', (request, response) => {
interceptedRequests.push(request);
response.writeHead(200, { 'Access-Control-Allow-Origin': '*' });
response.end('done');
});
const text = await page.evaluate(async url => {
const data = await fetch(url);
return data.text();
}, server.CROSS_PROCESS_PREFIX + '/something');
expect(text).toBe('done');
let playwrightRequest;
await page.route(server.CROSS_PROCESS_PREFIX + '/something', (route, request) => {
playwrightRequest = request;
route.continue({
headers: {
...request.headers()
}
});
});
const textAfterRoute = await page.evaluate(async url => {
const data = await fetch(url);
return data.text();
}, server.CROSS_PROCESS_PREFIX + '/something');
expect(textAfterRoute).toBe('done');
expect(interceptedRequests.length).toBe(2);
expect(interceptedRequests[1].headers).toEqual(interceptedRequests[0].headers);
});
it('should include the origin header', async({page, server}) => {
await page.goto(server.PREFIX + '/empty.html');
let interceptedRequest;
await page.route(server.CROSS_PROCESS_PREFIX + '/something', (route, request) => {
interceptedRequest = request;
route.fulfill({
headers: {
'Access-Control-Allow-Origin': '*',
},
contentType: 'text/plain',
body: 'done'
});
});
const text = await page.evaluate(async url => {
const data = await fetch(url);
return data.text();
}, server.CROSS_PROCESS_PREFIX + '/something');
expect(text).toBe('done');
expect(interceptedRequest.headers()['origin']).toEqual(server.PREFIX);
});
}); });
describe('Interception vs isNavigationRequest', () => { describe('Interception vs isNavigationRequest', () => {

View File

@ -80,6 +80,23 @@ describe('Request.headers', function() {
else if (WEBKIT) else if (WEBKIT)
expect(response.request().headers()['user-agent']).toContain('WebKit'); expect(response.request().headers()['user-agent']).toContain('WebKit');
}); });
it.fail(CHROMIUM||WEBKIT)('should get the same headers as the server', async({page, server}) => {
await page.goto(server.PREFIX + '/empty.html');
let serverRequest;
await server.setRoute('/something', (request, response) => {
serverRequest = request;
response.writeHead(200, { 'Access-Control-Allow-Origin': '*' });
response.end('done');
});
const requestPromise = page.waitForEvent('request');
const text = await page.evaluate(async url => {
const data = await fetch(url);
return data.text();
}, server.CROSS_PROCESS_PREFIX + '/something');
const request = await requestPromise;
expect(text).toBe('done');
expect(request.headers()).toEqual(serverRequest.headers);
});
}); });
describe('Response.headers', function() { describe('Response.headers', function() {