From 2265974ce579c2a07ec3f1632a37eefaec24a844 Mon Sep 17 00:00:00 2001 From: Andrey Lushnikov Date: Thu, 11 Apr 2019 15:02:06 -0400 Subject: [PATCH] refactor: migrate onto Fetch domain (#4265) Request interception in Network domain is deprecated. Move on onto Fetch domain - the new bright future. --- lib/NetworkManager.js | 176 +++++++++++++++--------------------------- 1 file changed, 64 insertions(+), 112 deletions(-) diff --git a/lib/NetworkManager.js b/lib/NetworkManager.js index f5d45046299..edca664d7a4 100644 --- a/lib/NetworkManager.js +++ b/lib/NetworkManager.js @@ -45,8 +45,9 @@ class NetworkManager extends EventEmitter { /** @type {!Map} */ this._requestIdToInterceptionId = new Map(); + this._client.on('Fetch.requestPaused', this._onRequestPaused.bind(this)); + this._client.on('Fetch.authRequired', this._onAuthRequired.bind(this)); this._client.on('Network.requestWillBeSent', this._onRequestWillBeSent.bind(this)); - this._client.on('Network.requestIntercepted', this._onRequestIntercepted.bind(this)); this._client.on('Network.requestServedFromCache', this._onRequestServedFromCache.bind(this)); this._client.on('Network.responseReceived', this._onResponseReceived.bind(this)); this._client.on('Network.loadingFinished', this._onLoadingFinished.bind(this)); @@ -138,11 +139,20 @@ class NetworkManager extends EventEmitter { if (enabled === this._protocolRequestInterceptionEnabled) return; this._protocolRequestInterceptionEnabled = enabled; - const patterns = enabled ? [{urlPattern: '*'}] : []; - await Promise.all([ - this._updateProtocolCacheDisabled(), - this._client.send('Network.setRequestInterception', {patterns}) - ]); + if (enabled) { + await Promise.all([ + this._updateProtocolCacheDisabled(), + this._client.send('Fetch.enable', { + handleAuthRequests: true, + patterns: [{urlPattern: '*'}], + }), + ]); + } else { + await Promise.all([ + this._updateProtocolCacheDisabled(), + this._client.send('Fetch.disable') + ]); + } } async _updateProtocolCacheDisabled() { @@ -171,38 +181,42 @@ class NetworkManager extends EventEmitter { } /** - * @param {!Protocol.Network.requestInterceptedPayload} event + * @param {!Protocol.Fetch.authRequiredPayload} event */ - _onRequestIntercepted(event) { - if (event.authChallenge) { - /** @type {"Default"|"CancelAuth"|"ProvideCredentials"} */ - let response = 'Default'; - if (this._attemptedAuthentications.has(event.interceptionId)) { - response = 'CancelAuth'; - } else if (this._credentials) { - response = 'ProvideCredentials'; - this._attemptedAuthentications.add(event.interceptionId); - } - const {username, password} = this._credentials || {username: undefined, password: undefined}; - this._client.send('Network.continueInterceptedRequest', { - interceptionId: event.interceptionId, - authChallengeResponse: { response, username, password } - }).catch(debugError); - return; + _onAuthRequired(event) { + /** @type {"Default"|"CancelAuth"|"ProvideCredentials"} */ + let response = 'Default'; + if (this._attemptedAuthentications.has(event.requestId)) { + response = 'CancelAuth'; + } else if (this._credentials) { + response = 'ProvideCredentials'; + this._attemptedAuthentications.add(event.requestId); } + const {username, password} = this._credentials || {username: undefined, password: undefined}; + this._client.send('Fetch.continueWithAuth', { + requestId: event.requestId, + authChallengeResponse: { response, username, password }, + }).catch(debugError); + } + + /** + * @param {!Protocol.Fetch.requestPausedPayload} event + */ + _onRequestPaused(event) { if (!this._userRequestInterceptionEnabled && this._protocolRequestInterceptionEnabled) { - this._client.send('Network.continueInterceptedRequest', { - interceptionId: event.interceptionId + this._client.send('Fetch.continueRequest', { + requestId: event.requestId }).catch(debugError); } - const requestId = event.requestId; + const requestId = event.networkId; + const interceptionId = event.requestId; if (requestId && this._requestIdToRequestWillBeSentEvent.has(requestId)) { const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(requestId); - this._onRequest(requestWillBeSentEvent, event.interceptionId); + this._onRequest(requestWillBeSentEvent, interceptionId); this._requestIdToRequestWillBeSentEvent.delete(requestId); } else { - this._requestIdToInterceptionId.set(requestId, event.interceptionId); + this._requestIdToInterceptionId.set(requestId, interceptionId); } } @@ -424,12 +438,12 @@ class Request { headers } = overrides; this._interceptionHandled = true; - await this._client.send('Network.continueInterceptedRequest', { - interceptionId: this._interceptionId, + await this._client.send('Fetch.continueRequest', { + requestId: this._interceptionId, url, method, postData, - headers, + headers: headers ? headersArray(headers) : undefined, }).catch(error => { // In certain cases, protocol will return error if the request was already canceled // or the page was closed. We should tolerate these errors. @@ -450,6 +464,7 @@ class Request { const responseBody = response.body && helper.isString(response.body) ? Buffer.from(/** @type {string} */(response.body)) : /** @type {?Buffer} */(response.body || null); + /** @type {!Object} */ const responseHeaders = {}; if (response.headers) { for (const header of Object.keys(response.headers)) @@ -458,24 +473,13 @@ class Request { if (response.contentType) responseHeaders['content-type'] = response.contentType; if (responseBody && !('content-length' in responseHeaders)) - responseHeaders['content-length'] = Buffer.byteLength(responseBody); + responseHeaders['content-length'] = String(Buffer.byteLength(responseBody)); - const statusCode = response.status || 200; - const statusText = statusTexts[statusCode] || ''; - const statusLine = `HTTP/1.1 ${statusCode} ${statusText}`; - - const CRLF = '\r\n'; - let text = statusLine + CRLF; - for (const header of Object.keys(responseHeaders)) - text += header + ': ' + responseHeaders[header] + CRLF; - text += CRLF; - let responseBuffer = Buffer.from(text, 'utf8'); - if (responseBody) - responseBuffer = Buffer.concat([responseBuffer, responseBody]); - - await this._client.send('Network.continueInterceptedRequest', { - interceptionId: this._interceptionId, - rawResponse: responseBuffer.toString('base64') + await this._client.send('Fetch.fulfillRequest', { + requestId: this._interceptionId, + responseCode: response.status || 200, + responseHeaders: headersArray(responseHeaders), + body: responseBody ? responseBody.toString('base64') : undefined, }).catch(error => { // In certain cases, protocol will return error if the request was already canceled // or the page was closed. We should tolerate these errors. @@ -495,8 +499,8 @@ class Request { assert(this._allowInterception, 'Request Interception is not enabled!'); assert(!this._interceptionHandled, 'Request is already handled!'); this._interceptionHandled = true; - await this._client.send('Network.continueInterceptedRequest', { - interceptionId: this._interceptionId, + await this._client.send('Fetch.failRequest', { + requestId: this._interceptionId, errorReason }).catch(error => { // In certain cases, protocol will return error if the request was already canceled @@ -712,67 +716,15 @@ class SecurityDetails { } } -const statusTexts = { - '100': 'Continue', - '101': 'Switching Protocols', - '102': 'Processing', - '200': 'OK', - '201': 'Created', - '202': 'Accepted', - '203': 'Non-Authoritative Information', - '204': 'No Content', - '206': 'Partial Content', - '207': 'Multi-Status', - '208': 'Already Reported', - '209': 'IM Used', - '300': 'Multiple Choices', - '301': 'Moved Permanently', - '302': 'Found', - '303': 'See Other', - '304': 'Not Modified', - '305': 'Use Proxy', - '306': 'Switch Proxy', - '307': 'Temporary Redirect', - '308': 'Permanent Redirect', - '400': 'Bad Request', - '401': 'Unauthorized', - '402': 'Payment Required', - '403': 'Forbidden', - '404': 'Not Found', - '405': 'Method Not Allowed', - '406': 'Not Acceptable', - '407': 'Proxy Authentication Required', - '408': 'Request Timeout', - '409': 'Conflict', - '410': 'Gone', - '411': 'Length Required', - '412': 'Precondition Failed', - '413': 'Payload Too Large', - '414': 'URI Too Long', - '415': 'Unsupported Media Type', - '416': 'Range Not Satisfiable', - '417': 'Expectation Failed', - '418': 'I\'m a teapot', - '421': 'Misdirected Request', - '422': 'Unprocessable Entity', - '423': 'Locked', - '424': 'Failed Dependency', - '426': 'Upgrade Required', - '428': 'Precondition Required', - '429': 'Too Many Requests', - '431': 'Request Header Fields Too Large', - '451': 'Unavailable For Legal Reasons', - '500': 'Internal Server Error', - '501': 'Not Implemented', - '502': 'Bad Gateway', - '503': 'Service Unavailable', - '504': 'Gateway Timeout', - '505': 'HTTP Version Not Supported', - '506': 'Variant Also Negotiates', - '507': 'Insufficient Storage', - '508': 'Loop Detected', - '510': 'Not Extended', - '511': 'Network Authentication Required', -}; +/** + * @param {Object} headers + * @return {!Array<{name: string, value: string}>} + */ +function headersArray(headers) { + const result = []; + for (const name in headers) + result.push({name, value: headers[name]}); + return result; +} module.exports = {Request, Response, NetworkManager, SecurityDetails};