mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
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});
|
var browser = new Browser({headless: false});
|
||||||
browser.newPage().then(async page => {
|
browser.newPage().then(async page => {
|
||||||
page.setRequestInterceptor(request => {
|
page.setRequestInterceptor(request => {
|
||||||
if (request.url().endsWith('.css'))
|
if (request.url.endsWith('.css'))
|
||||||
request.abort();
|
request.abort();
|
||||||
else
|
else
|
||||||
request.continue();
|
request.continue();
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
let fs = require('fs');
|
let fs = require('fs');
|
||||||
let EventEmitter = require('events');
|
let EventEmitter = require('events');
|
||||||
let mime = require('mime');
|
let mime = require('mime');
|
||||||
let Request = require('./Request');
|
let {InterceptedRequest} = require('./Request');
|
||||||
let Navigator = require('./Navigator');
|
let Navigator = require('./Navigator');
|
||||||
let Dialog = require('./Dialog');
|
let Dialog = require('./Dialog');
|
||||||
let FrameManager = require('./FrameManager');
|
let FrameManager = require('./FrameManager');
|
||||||
@ -57,7 +57,7 @@ class Page extends EventEmitter {
|
|||||||
this._extraHeaders = {};
|
this._extraHeaders = {};
|
||||||
/** @type {!Map<string, function>} */
|
/** @type {!Map<string, function>} */
|
||||||
this._inPageCallbacks = new Map();
|
this._inPageCallbacks = new Map();
|
||||||
/** @type {?function(!Request)} */
|
/** @type {?function(!InterceptedRequest)} */
|
||||||
this._requestInterceptor = null;
|
this._requestInterceptor = null;
|
||||||
/** @type {?Promise<number>} */
|
/** @type {?Promise<number>} */
|
||||||
this._rootNodeIdPromise = null;
|
this._rootNodeIdPromise = null;
|
||||||
@ -94,7 +94,7 @@ class Page extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {?function(!Request)} interceptor
|
* @param {?function(!InterceptedRequest)} interceptor
|
||||||
*/
|
*/
|
||||||
async setRequestInterceptor(interceptor) {
|
async setRequestInterceptor(interceptor) {
|
||||||
this._requestInterceptor = interceptor;
|
this._requestInterceptor = interceptor;
|
||||||
@ -105,7 +105,7 @@ class Page extends EventEmitter {
|
|||||||
* @param {!Object} event
|
* @param {!Object} event
|
||||||
*/
|
*/
|
||||||
_onRequestIntercepted(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);
|
this._requestInterceptor(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
181
lib/Request.js
181
lib/Request.js
@ -14,86 +14,116 @@
|
|||||||
* limitations under the License.
|
* 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 {
|
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 {!Connection} client
|
||||||
* @param {string} interceptionId
|
* @param {string} interceptionId
|
||||||
* @param {!Object} payload
|
* @param {!Object} payload
|
||||||
*/
|
*/
|
||||||
constructor(client, interceptionId, payload) {
|
constructor(client, interceptionId, payload) {
|
||||||
|
super(payload);
|
||||||
this._client = client;
|
this._client = client;
|
||||||
this._interceptionId = interceptionId;
|
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;
|
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() {
|
abort() {
|
||||||
console.assert(!this._handled, 'This request is already handled!');
|
console.assert(!this._handled, 'This request is already handled!');
|
||||||
this._handled = true;
|
this._handled = true;
|
||||||
@ -106,21 +136,24 @@ class Request {
|
|||||||
continue() {
|
continue() {
|
||||||
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 = {};
|
||||||
|
for (let entry of this.headers.entries())
|
||||||
|
headers[entry[0]] = entry[1];
|
||||||
this._client.send('Network.continueInterceptedRequest', {
|
this._client.send('Network.continueInterceptedRequest', {
|
||||||
interceptionId: this._interceptionId,
|
interceptionId: this._interceptionId,
|
||||||
url: this._urlOverride,
|
url: this.url,
|
||||||
method: this._methodOverride,
|
method: this.method,
|
||||||
postData: this._postDataOverride,
|
postData: this.postData,
|
||||||
headers: this._headersOverride
|
headers: headers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
handled() {
|
isHandled() {
|
||||||
return this._handled;
|
return this._handled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Request;
|
module.exports = {Request, InterceptedRequest};
|
||||||
|
@ -79,15 +79,15 @@ class WebPage {
|
|||||||
this._page.setRequestInterceptor(callback ? resourceInterceptor : null);
|
this._page.setRequestInterceptor(callback ? resourceInterceptor : null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {!Request} request
|
* @param {!InterceptedRequest} request
|
||||||
*/
|
*/
|
||||||
function resourceInterceptor(request) {
|
function resourceInterceptor(request) {
|
||||||
let requestData = {
|
let requestData = new RequestData(request);
|
||||||
url: request.url(),
|
let phantomRequest = new PhantomRequest(request);
|
||||||
headers: request.headers()
|
callback(requestData, phantomRequest);
|
||||||
};
|
if (phantomRequest._aborted)
|
||||||
callback(requestData, request);
|
request.abort();
|
||||||
if (!request.handled())
|
else
|
||||||
request.continue();
|
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
|
// To prevent reenterability, eventemitters should emit events
|
||||||
// only being in a consistent state.
|
// only being in a consistent state.
|
||||||
// This is not the case for 'ws' npm module: https://goo.gl/sy3dJY
|
// 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() {
|
describe('Page.setRequestInterceptor', function() {
|
||||||
it('should intercept', SX(async function() {
|
it('should intercept', SX(async function() {
|
||||||
page.setRequestInterceptor(request => {
|
page.setRequestInterceptor(request => {
|
||||||
expect(request.url()).toContain('empty.html');
|
expect(request.url).toContain('empty.html');
|
||||||
expect(request.headers()['User-Agent']).toBeTruthy();
|
expect(request.headers.has('User-Agent')).toBeTruthy();
|
||||||
expect(request.method()).toBe('GET');
|
expect(request.method).toBe('GET');
|
||||||
expect(request.postData()).toBe(undefined);
|
expect(request.postData).toBe(undefined);
|
||||||
request.continue();
|
request.continue();
|
||||||
});
|
});
|
||||||
let success = await page.navigate(EMPTY_PAGE);
|
let success = await page.navigate(EMPTY_PAGE);
|
||||||
@ -204,7 +204,7 @@ describe('Puppeteer', function() {
|
|||||||
foo: 'bar'
|
foo: 'bar'
|
||||||
});
|
});
|
||||||
page.setRequestInterceptor(request => {
|
page.setRequestInterceptor(request => {
|
||||||
expect(request.headers()['foo']).toBe('bar');
|
expect(request.headers.get('foo')).toBe('bar');
|
||||||
request.continue();
|
request.continue();
|
||||||
});
|
});
|
||||||
let success = await page.navigate(EMPTY_PAGE);
|
let success = await page.navigate(EMPTY_PAGE);
|
||||||
@ -212,7 +212,7 @@ describe('Puppeteer', function() {
|
|||||||
}));
|
}));
|
||||||
it('should be abortable', SX(async function() {
|
it('should be abortable', SX(async function() {
|
||||||
page.setRequestInterceptor(request => {
|
page.setRequestInterceptor(request => {
|
||||||
if (request.url().endsWith('.css'))
|
if (request.url.endsWith('.css'))
|
||||||
request.abort();
|
request.abort();
|
||||||
else
|
else
|
||||||
request.continue();
|
request.continue();
|
||||||
@ -223,6 +223,19 @@ describe('Puppeteer', function() {
|
|||||||
expect(success).toBe(true);
|
expect(success).toBe(true);
|
||||||
expect(failedResources).toBe(1);
|
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() {
|
describe('Page.Events.Dialog', function() {
|
||||||
|
Loading…
Reference in New Issue
Block a user