Make interception work with redirects (#218)

This patch:
- changes interception API so that it better aligns with what we'd like to see
  in #121
- fixes the issue with redirect interception

Fixes #217.
This commit is contained in:
Andrey Lushnikov 2017-08-07 17:48:52 -07:00 committed by GitHub
parent e1c5b8d244
commit 34b0095c10
4 changed files with 52 additions and 37 deletions

View File

@ -113,7 +113,7 @@
+ [response.url](#responseurl) + [response.url](#responseurl)
* [class: InterceptedRequest](#class-interceptedrequest) * [class: InterceptedRequest](#class-interceptedrequest)
+ [interceptedRequest.abort()](#interceptedrequestabort) + [interceptedRequest.abort()](#interceptedrequestabort)
+ [interceptedRequest.continue()](#interceptedrequestcontinue) + [interceptedRequest.continue([overrides])](#interceptedrequestcontinueoverrides)
+ [interceptedRequest.headers](#interceptedrequestheaders) + [interceptedRequest.headers](#interceptedrequestheaders)
+ [interceptedRequest.method](#interceptedrequestmethod) + [interceptedRequest.method](#interceptedrequestmethod)
+ [interceptedRequest.postData](#interceptedrequestpostdata) + [interceptedRequest.postData](#interceptedrequestpostdata)
@ -1112,40 +1112,38 @@ Contains the URL of the response.
### class: InterceptedRequest ### class: InterceptedRequest
[InterceptedRequest] represents an intercepted request, which can be mutated and either continued or aborted. [InterceptedRequest] which is not continued or aborted will be in a 'hanging' state. [InterceptedRequest] represents an intercepted request, which can be either continued or aborted. [InterceptedRequest] which is not continued or aborted will be in a 'hanging' state.
#### interceptedRequest.abort() #### interceptedRequest.abort()
Aborts request. Aborts request.
#### interceptedRequest.continue() #### interceptedRequest.continue([overrides])
- `overrides` <[Object]> Optional request overwrites, which could be one of the following:
- `url` <[string]> If set, the request url will be changed
- `method` <[string]> If set changes the request method (e.g. `GET` or `POST`)
- `postData` <[string]> If set changes the post data of request
- `headers` <[Map]> If set changes the request HTTP headers
Continues request. Continues request with optional request overrides.
#### interceptedRequest.headers #### interceptedRequest.headers
- <[Map]> A map of HTTP headers associated with the request. - <[Map]> A map of HTTP headers associated with the request.
Headers could be mutated. Must not be changed in response to an authChallenge.
#### interceptedRequest.method #### interceptedRequest.method
- <[string]> - <[string]>
Contains the request's method (GET, POST, etc.) Contains the request's method (GET, POST, etc.)
If set this allows the request method to be overridden. Must not be changed in response to an authChallenge.
#### interceptedRequest.postData #### interceptedRequest.postData
- <[string]> - <[string]>
Contains `POST` data for `POST` requests. Contains `POST` data for `POST` requests.
`request.postData` is mutable and could be written to. Must not be changed in response to an authChallenge.
#### interceptedRequest.url #### interceptedRequest.url
- <[string]> - <[string]>
If changed, the request url will be modified in a way that's not observable by page. Must not be changed in response to an authChallenge.
[Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array "Array" [Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array "Array"
[boolean]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type "Boolean" [boolean]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type "Boolean"

View File

@ -247,17 +247,23 @@ class InterceptedRequest {
}); });
} }
continue() { /**
* @param {!Object} overrides
*/
continue(overrides = {}) {
console.assert(!this._handled, 'This request is already handled!'); console.assert(!this._handled, 'This request is already handled!');
this._handled = true; this._handled = true;
let headers = {}; let headers = undefined;
for (let entry of this.headers.entries()) if (overrides.headers) {
headers = {};
for (let entry of overrides.headers.entries())
headers[entry[0]] = entry[1]; headers[entry[0]] = entry[1];
}
this._client.send('Network.continueInterceptedRequest', { this._client.send('Network.continueInterceptedRequest', {
interceptionId: this._interceptionId, interceptionId: this._interceptionId,
url: this.url, url: overrides.url,
method: this.method, method: overrides.method,
postData: this.postData, postData: overrides.postData,
headers: headers headers: headers
}); });
} }

View File

@ -236,12 +236,16 @@ class WebPage {
*/ */
function resourceInterceptor(request) { function resourceInterceptor(request) {
let requestData = new RequestData(request); let requestData = new RequestData(request);
let phantomRequest = new PhantomRequest(request); let phantomRequest = new PhantomRequest();
callback(requestData, phantomRequest); callback(requestData, phantomRequest);
if (phantomRequest._aborted) if (phantomRequest._aborted) {
request.abort(); request.abort();
else } else {
request.continue(); request.continue({
url: phantomRequest._url,
headers: phantomRequest._headers,
});
}
} }
} }
@ -608,11 +612,9 @@ class WebPageSettings {
} }
class PhantomRequest { class PhantomRequest {
/** constructor() {
* @param {!InterceptedRequest} request this._url = undefined;
*/ this._headers = undefined;
constructor(request) {
this._request = request;
} }
/** /**
@ -620,7 +622,9 @@ class PhantomRequest {
* @param {string} value * @param {string} value
*/ */
setHeader(key, value) { setHeader(key, value) {
this._request.headers.set(key, value); if (!this._headers)
this._headers = new Map();
this._headers.set(key, value);
} }
abort() { abort() {
@ -631,7 +635,7 @@ class PhantomRequest {
* @param {string} url * @param {string} url
*/ */
changeUrl(newUrl) { changeUrl(newUrl) {
this._request.url = newUrl; this._url = newUrl;
} }
} }

View File

@ -724,7 +724,7 @@ describe('Page', function() {
describe('Page.setRequestInterceptor', function() { describe('Page.setRequestInterceptor', function() {
it('should intercept', SX(async function() { it('should intercept', SX(async function() {
page.setRequestInterceptor(request => { await page.setRequestInterceptor(request => {
expect(request.url).toContain('empty.html'); expect(request.url).toContain('empty.html');
expect(request.headers.has('User-Agent')).toBeTruthy(); expect(request.headers.has('User-Agent')).toBeTruthy();
expect(request.method).toBe('GET'); expect(request.method).toBe('GET');
@ -738,7 +738,7 @@ describe('Page', function() {
await page.setExtraHTTPHeaders(new Map(Object.entries({ await page.setExtraHTTPHeaders(new Map(Object.entries({
foo: 'bar' foo: 'bar'
}))); })));
page.setRequestInterceptor(request => { await page.setRequestInterceptor(request => {
expect(request.headers.get('foo')).toBe('bar'); expect(request.headers.get('foo')).toBe('bar');
request.continue(); request.continue();
}); });
@ -746,7 +746,7 @@ describe('Page', function() {
expect(response.ok).toBe(true); expect(response.ok).toBe(true);
})); }));
it('should be abortable', SX(async function() { it('should be abortable', SX(async function() {
page.setRequestInterceptor(request => { await page.setRequestInterceptor(request => {
if (request.url.endsWith('.css')) if (request.url.endsWith('.css'))
request.abort(); request.abort();
else else
@ -759,11 +759,12 @@ describe('Page', function() {
expect(failedRequests).toBe(1); expect(failedRequests).toBe(1);
})); }));
it('should amend HTTP headers', SX(async function() { it('should amend HTTP headers', SX(async function() {
await page.navigate(EMPTY_PAGE); await page.setRequestInterceptor(request => {
page.setRequestInterceptor(request => { let headers = new Map(request.headers);
request.headers.set('foo', 'bar'); headers.set('foo', 'bar');
request.continue(); request.continue({headers});
}); });
await page.navigate(EMPTY_PAGE);
const [request] = await Promise.all([ const [request] = await Promise.all([
server.waitForRequest('/sleep.zzz'), server.waitForRequest('/sleep.zzz'),
page.evaluate(() => fetch('/sleep.zzz')) page.evaluate(() => fetch('/sleep.zzz'))
@ -771,7 +772,7 @@ describe('Page', function() {
expect(request.headers['foo']).toBe('bar'); expect(request.headers['foo']).toBe('bar');
})); }));
it('should fail navigation when aborting main resource', SX(async function() { it('should fail navigation when aborting main resource', SX(async function() {
page.setRequestInterceptor(request => request.abort()); await page.setRequestInterceptor(request => request.abort());
let error = null; let error = null;
try { try {
await page.navigate(EMPTY_PAGE); await page.navigate(EMPTY_PAGE);
@ -781,6 +782,12 @@ describe('Page', function() {
expect(error).toBeTruthy(); expect(error).toBeTruthy();
expect(error.message).toContain('Failed to navigate'); expect(error.message).toContain('Failed to navigate');
})); }));
it('should work with redirects', SX(async function() {
server.setRedirect('/non-existing-page.html', '/empty.html');
await page.setRequestInterceptor(request => request.continue());
let response = await page.navigate(PREFIX + '/non-existing-page.html');
expect(response.status).toBe(200);
}));
}); });
describe('Page.Events.Dialog', function() { describe('Page.Events.Dialog', function() {