Implement Request object
This patch does a step towards Fetch API: - implements Request object to some extend. The Request object will be sent in RequestWillBeSent event. - implements InterceptedRequest which extends from Request and allows for request modification. The InterceptedRequest does not conform to Fetch API spec - there seems to be nothing related to amending in-flight request. - adds test to make sure that request can change headers. References #26
This commit is contained in:
parent
5ed71fcb8f
commit
7b59a89695
@ -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();
|
||||
|
@ -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<string, function>} */
|
||||
this._inPageCallbacks = new Map();
|
||||
/** @type {?function(!Request)} */
|
||||
/** @type {?function(!InterceptedRequest)} */
|
||||
this._requestInterceptor = null;
|
||||
/** @type {?Promise<number>} */
|
||||
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);
|
||||
}
|
||||
|
||||
|
181
lib/Request.js
181
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<string, string>} */
|
||||
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};
|
||||
|
@ -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
|
||||
|
25
test/test.js
25
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() {
|
||||
|
Loading…
Reference in New Issue
Block a user