You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
230 lines
5.9 KiB
230 lines
5.9 KiB
/**
|
|
* Copyright 2018 Google Inc. All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the 'License');
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an 'AS IS' BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const debugServer = require('debug')('carlo:server');
|
|
|
|
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',
|
|
};
|
|
|
|
/**
|
|
* Intercepted request instance that can be resolved to the client's liking.
|
|
*/
|
|
class HttpRequest {
|
|
/**
|
|
* @param {!CDPSession} session
|
|
* @param {!Object} params
|
|
*/
|
|
constructor(session, params, handlers) {
|
|
this.session_ = session;
|
|
this.params_ = params;
|
|
this.handlers_ = handlers;
|
|
this.done_ = false;
|
|
this.callNextHandler_();
|
|
}
|
|
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
url() {
|
|
return this.params_.request.url;
|
|
}
|
|
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
method() {
|
|
return this.params_.request.method;
|
|
}
|
|
|
|
/**
|
|
* @return {!Object<string, string>} HTTP request headers.
|
|
*/
|
|
headers() {
|
|
return this.params_.request.headers || {};
|
|
}
|
|
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
resourceType() {
|
|
return this.params_.resourceType;
|
|
}
|
|
|
|
/**
|
|
* Aborts the request.
|
|
*/
|
|
abort() {
|
|
debugServer('abort', this.url());
|
|
return this.resolve_({errorReason: 'Aborted'});
|
|
}
|
|
|
|
|
|
/**
|
|
* Fails the request.
|
|
*/
|
|
fail() {
|
|
debugServer('fail', this.url());
|
|
return this.resolve_({errorReason: 'Failed'});
|
|
}
|
|
|
|
/**
|
|
* Falls through to the next handler.
|
|
*/
|
|
continue() {
|
|
debugServer('continue', this.url());
|
|
return this.callNextHandler_();
|
|
}
|
|
|
|
/**
|
|
* Continues the request with the provided overrides to the url, method or
|
|
* headers.
|
|
*
|
|
* @param {{url: (string|undefined), method: (string|undefined),
|
|
* headers: (!Object<string, string>|undefined)}|undefined} overrides
|
|
* Overrides to apply to the request before it hits network.
|
|
*/
|
|
deferToBrowser(overrides) {
|
|
debugServer('deferToBrowser', this.url());
|
|
const params = {};
|
|
if (overrides && overrides.url) params.url = overrides.url;
|
|
if (overrides && overrides.method) params.method = overrides.method;
|
|
if (overrides && overrides.headers) params.headers = overrides.headers;
|
|
return this.resolve_(params);
|
|
}
|
|
|
|
/**
|
|
* Fulfills the request with the given data.
|
|
*
|
|
* @param {{status: number|undefined,
|
|
* headers: !Object<string,string>|undefined,
|
|
* body: !Buffer|undefined}} options
|
|
*/
|
|
fulfill({status, headers, body}) {
|
|
debugServer('fulfill', this.url());
|
|
status = status || 200;
|
|
const responseHeaders = {};
|
|
if (headers) {
|
|
for (const header of Object.keys(headers))
|
|
responseHeaders[header.toLowerCase()] = headers[header];
|
|
}
|
|
if (body && !('content-length' in responseHeaders))
|
|
responseHeaders['content-length'] = Buffer.byteLength(body);
|
|
|
|
const statusText = statusTexts[status] || '';
|
|
const statusLine = `HTTP/1.1 ${status} ${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 (body)
|
|
responseBuffer = Buffer.concat([responseBuffer, body]);
|
|
|
|
return this.resolve_({
|
|
interceptionId: this.interceptionId_,
|
|
rawResponse: responseBuffer.toString('base64')
|
|
});
|
|
}
|
|
|
|
callNextHandler_() {
|
|
debugServer('next handler', this.url());
|
|
const handler = this.handlers_.shift();
|
|
if (handler) {
|
|
handler(this);
|
|
return;
|
|
}
|
|
this.resolve_({});
|
|
}
|
|
|
|
/**
|
|
* Aborts the request.
|
|
* @param {!Object} params
|
|
*/
|
|
async resolve_(params) {
|
|
debugServer('resolve', this.url());
|
|
if (this.done_) throw new Error('Already resolved given request');
|
|
params.interceptionId = this.params_.interceptionId;
|
|
this.done_ = true;
|
|
return this.session_.send('Network.continueInterceptedRequest', params);
|
|
}
|
|
}
|
|
|
|
module.exports = { HttpRequest };
|