diff --git a/examples/loadurlwithoutcss.js b/examples/loadurlwithoutcss.js index e11f6a9c..ac2ce48a 100644 --- a/examples/loadurlwithoutcss.js +++ b/examples/loadurlwithoutcss.js @@ -26,7 +26,7 @@ var address = process.argv[2]; var browser = new Browser({headless: false}); browser.newPage().then(async page => { page.setRequestInterceptor(request => { - if (request.url().endsWith('.css')) + if (request.url.endsWith('.css')) request.abort(); else request.continue(); diff --git a/lib/Page.js b/lib/Page.js index b135bf34..6f79e04a 100644 --- a/lib/Page.js +++ b/lib/Page.js @@ -17,7 +17,7 @@ let fs = require('fs'); let EventEmitter = require('events'); let mime = require('mime'); -let Request = require('./Request'); +let {InterceptedRequest} = require('./Request'); let Navigator = require('./Navigator'); let Dialog = require('./Dialog'); let FrameManager = require('./FrameManager'); @@ -57,7 +57,7 @@ class Page extends EventEmitter { this._extraHeaders = {}; /** @type {!Map} */ this._inPageCallbacks = new Map(); - /** @type {?function(!Request)} */ + /** @type {?function(!InterceptedRequest)} */ this._requestInterceptor = null; /** @type {?Promise} */ this._rootNodeIdPromise = null; @@ -94,7 +94,7 @@ class Page extends EventEmitter { } /** - * @param {?function(!Request)} interceptor + * @param {?function(!InterceptedRequest)} interceptor */ async setRequestInterceptor(interceptor) { this._requestInterceptor = interceptor; @@ -105,7 +105,7 @@ class Page extends EventEmitter { * @param {!Object} event */ _onRequestIntercepted(event) { - let request = new Request(this._client, event.InterceptionId, event.request); + let request = new InterceptedRequest(this._client, event.InterceptionId, event.request); this._requestInterceptor(request); } diff --git a/lib/Request.js b/lib/Request.js index e74fc828..e302a272 100644 --- a/lib/Request.js +++ b/lib/Request.js @@ -14,86 +14,116 @@ * limitations under the License. */ +class Headers { + /** + * @param {?Object} payload + * @return {!Headers} + */ + static fromPayload(payload) { + let headers = new Headers(); + if (!payload) + return headers; + for (let key in payload) + headers.set(key, payload[key]); + return headers; + } + + constructor() { + /** @type {!Map} */ + this._headers = new Map(); + } + + /** + * @param {string} name + * @param {string} value + */ + append(name, value) { + name = name.toLowerCase(); + this._headers.set(name, value); + } + + /** + * @param {string} name + */ + delete(name) { + name = name.toLowerCase(); + this._headers.delete(name); + } + + /** + * @return {!Iterator} + */ + entries() { + return this._headers.entries(); + } + + /** + * @param {string} name + * @return {?string} + */ + get(name) { + name = name.toLowerCase(); + return this._headers.get(name); + } + + /** + * @param {string} name + * @return {boolean} + */ + has(name) { + name = name.toLowerCase(); + return this._headers.has(name); + } + + /** + * @return {!Iterator} + */ + keys() { + return this._headers.keys(); + } + + /** + * @return {!Iterator} + */ + values() { + return this._headers.values(); + } + + /** + * @param {string} name + * @param {string} value + */ + set(name, value) { + name = name.toLowerCase(); + this._headers.set(name, value); + } +} + class Request { + /** + * @param {!Object} payload + */ + constructor(payload) { + this.url = payload.url; + this.method = payload.method; + this.headers = Headers.fromPayload(payload.headers); + this.postData = payload.postData; + } +} + +class InterceptedRequest extends Request { /** * @param {!Connection} client * @param {string} interceptionId * @param {!Object} payload */ constructor(client, interceptionId, payload) { + super(payload); this._client = client; this._interceptionId = interceptionId; - this._url = payload.url; - this._method = payload.method; - this._headers = payload.headers; - this._postData = payload.postData; - - this._urlOverride = undefined; - this._methodOverride = undefined; - this._postDataOverride = undefined; - this._handled = false; } - /** - * @return {string} - */ - url() { - return this._urlOverride || this._url; - } - - /** - * @param {string} url - */ - setUrl(url) { - this._urlOverride = url; - } - - /** - * @return {string} - */ - method() { - return this._methodOverride || this._method; - } - - /** - * @param {string} method - */ - setMethod(method) { - this._methodOverride = method; - } - - /** - * @return {!Object} - */ - headers() { - return Object.assign({}, this._headersOverride || this._headers); - } - - /** - * @param {string} key - * @param {string} value - */ - setHeader(key, value) { - if (!this._headersOverride) - this._headersOverride = Object.assign({}, this._headers); - this._headersOverride[key] = value; - } - - /** - * @return {(string|undefined)} - */ - postData() { - return this._postDataOverride || this._postData; - } - - /** - * @return {(string|undefined)} - */ - setPostData(data) { - this._postDataOverride = data; - } - abort() { console.assert(!this._handled, 'This request is already handled!'); this._handled = true; @@ -106,21 +136,24 @@ class Request { continue() { console.assert(!this._handled, 'This request is already handled!'); this._handled = true; + let headers = {}; + for (let entry of this.headers.entries()) + headers[entry[0]] = entry[1]; this._client.send('Network.continueInterceptedRequest', { interceptionId: this._interceptionId, - url: this._urlOverride, - method: this._methodOverride, - postData: this._postDataOverride, - headers: this._headersOverride + url: this.url, + method: this.method, + postData: this.postData, + headers: headers }); } /** * @return {boolean} */ - handled() { + isHandled() { return this._handled; } } -module.exports = Request; +module.exports = {Request, InterceptedRequest}; diff --git a/phantom_shim/WebPage.js b/phantom_shim/WebPage.js index e37f5d49..acc2fb23 100644 --- a/phantom_shim/WebPage.js +++ b/phantom_shim/WebPage.js @@ -79,15 +79,15 @@ class WebPage { this._page.setRequestInterceptor(callback ? resourceInterceptor : null); /** - * @param {!Request} request + * @param {!InterceptedRequest} request */ function resourceInterceptor(request) { - let requestData = { - url: request.url(), - headers: request.headers() - }; - callback(requestData, request); - if (!request.handled()) + let requestData = new RequestData(request); + let phantomRequest = new PhantomRequest(request); + callback(requestData, phantomRequest); + if (phantomRequest._aborted) + request.abort(); + else request.continue(); } } @@ -338,6 +338,46 @@ class WebPageSettings { } } +class PhantomRequest { + /** + * @param {!InterceptedRequest} request + */ + constructor(request) { + this._request = request; + } + + /** + * @param {string} key + * @param {string} value + */ + setHeader(key, value) { + this._request.headers.set(key, value); + } + + abort() { + this._aborted = true; + } + + /** + * @param {string} url + */ + changeUrl(newUrl) { + this._request.url = newUrl; + } +} + +class RequestData { + /** + * @param {!InterceptedRequest} request + */ + constructor(request) { + this.url = request.url, + this.headers = {}; + for (let entry in request.headers.entries()) + this.headers[entry[0]] = entry[1]; + } +} + // To prevent reenterability, eventemitters should emit events // only being in a consistent state. // This is not the case for 'ws' npm module: https://goo.gl/sy3dJY diff --git a/test/test.js b/test/test.js index 6b4f76df..50e232a2 100644 --- a/test/test.js +++ b/test/test.js @@ -190,10 +190,10 @@ describe('Puppeteer', function() { describe('Page.setRequestInterceptor', function() { it('should intercept', SX(async function() { page.setRequestInterceptor(request => { - expect(request.url()).toContain('empty.html'); - expect(request.headers()['User-Agent']).toBeTruthy(); - expect(request.method()).toBe('GET'); - expect(request.postData()).toBe(undefined); + expect(request.url).toContain('empty.html'); + expect(request.headers.has('User-Agent')).toBeTruthy(); + expect(request.method).toBe('GET'); + expect(request.postData).toBe(undefined); request.continue(); }); let success = await page.navigate(EMPTY_PAGE); @@ -204,7 +204,7 @@ describe('Puppeteer', function() { foo: 'bar' }); page.setRequestInterceptor(request => { - expect(request.headers()['foo']).toBe('bar'); + expect(request.headers.get('foo')).toBe('bar'); request.continue(); }); let success = await page.navigate(EMPTY_PAGE); @@ -212,7 +212,7 @@ describe('Puppeteer', function() { })); it('should be abortable', SX(async function() { page.setRequestInterceptor(request => { - if (request.url().endsWith('.css')) + if (request.url.endsWith('.css')) request.abort(); else request.continue(); @@ -223,6 +223,19 @@ describe('Puppeteer', function() { expect(success).toBe(true); expect(failedResources).toBe(1); })); + it('should amend HTTP headers', SX(async function() { + await page.navigate(EMPTY_PAGE); + page.setRequestInterceptor(request => { + request.headers.set('foo', 'bar'); + request.continue(); + }); + let serverRequest = staticServer.waitForRequest('/sleep.zzz'); + page.evaluate(() => { + fetch('/sleep.zzz'); + }); + let request = await serverRequest; + expect(request.headers['foo']).toBe('bar'); + })); }); describe('Page.Events.Dialog', function() {