419 lines
13 KiB
JavaScript
419 lines
13 KiB
JavaScript
var util = require('../util'),
|
|
_ = util.lodash,
|
|
PropertyBase = require('./property-base').PropertyBase,
|
|
Property = require('./property').Property,
|
|
Url = require('./url').Url,
|
|
ProxyConfig = require('./proxy-config').ProxyConfig,
|
|
Certificate = require('./certificate').Certificate,
|
|
HeaderList = require('./header-list').HeaderList,
|
|
RequestBody = require('./request-body').RequestBody,
|
|
RequestAuth = require('./request-auth').RequestAuth,
|
|
|
|
Request,
|
|
|
|
/**
|
|
* Default request method
|
|
*
|
|
* @private
|
|
* @const
|
|
* @type {String}
|
|
*/
|
|
DEFAULT_REQ_METHOD = 'GET',
|
|
|
|
/**
|
|
* Content length header name
|
|
*
|
|
* @private
|
|
* @const
|
|
* @type {String}
|
|
*/
|
|
CONTENT_LENGTH = 'Content-Length',
|
|
|
|
/**
|
|
* Single space
|
|
*
|
|
* @private
|
|
* @const
|
|
* @type {String}
|
|
*/
|
|
SP = ' ',
|
|
|
|
/**
|
|
* Carriage return + line feed
|
|
*
|
|
* @private
|
|
* @const
|
|
* @type {String}
|
|
*/
|
|
CRLF = '\r\n',
|
|
|
|
/**
|
|
* HTTP version
|
|
*
|
|
* @private
|
|
* @const
|
|
* @type {String}
|
|
*/
|
|
HTTP_X_X = 'HTTP/X.X',
|
|
|
|
/**
|
|
* @private
|
|
* @type {Boolean}
|
|
*/
|
|
supportsBuffer = (typeof Buffer !== undefined) && _.isFunction(Buffer.byteLength),
|
|
|
|
/**
|
|
* Source of request body size calculation.
|
|
* Either computed from body or used Content-Length header value.
|
|
*
|
|
* @private
|
|
* @const
|
|
* @type {Object}
|
|
*/
|
|
SIZE_SOURCE = {
|
|
computed: 'COMPUTED',
|
|
contentLength: 'CONTENT-LENGTH'
|
|
};
|
|
|
|
/**
|
|
* @typedef Request.definition
|
|
* @property {String|Url} url The URL of the request. This can be a {@link Url.definition} or a string.
|
|
* @property {String} method The request method, e.g: "GET" or "POST".
|
|
* @property {Array<Header.definition>} header The headers that should be sent as a part of this request.
|
|
* @property {RequestBody.definition} body The request body definition.
|
|
* @property {RequestAuth.definition} auth The authentication/signing information for this request.
|
|
* @property {ProxyConfig.definition} proxy The proxy information for this request.
|
|
* @property {Certificate.definition} certificate The certificate information for this request.
|
|
*/
|
|
_.inherit((
|
|
|
|
/**
|
|
* A Postman HTTP request object.
|
|
*
|
|
* @constructor
|
|
* @extends {Property}
|
|
* @param {Request.definition} options -
|
|
*/
|
|
Request = function PostmanRequest (options) {
|
|
// this constructor is intended to inherit and as such the super constructor is required to be executed
|
|
Request.super_.apply(this, arguments);
|
|
|
|
// if the definition is a string, it implies that this is a get of URL
|
|
(typeof options === 'string') && (options = {
|
|
url: options
|
|
});
|
|
|
|
// Create the default properties
|
|
_.assign(this, /** @lends Request.prototype */ {
|
|
/**
|
|
* @type {Url}
|
|
*/
|
|
url: new Url(),
|
|
|
|
/**
|
|
* @type {HeaderList}
|
|
*/
|
|
headers: new HeaderList(this, options && options.header),
|
|
|
|
// Although a similar check is being done in the .update call below, this handles falsy options as well.
|
|
/**
|
|
* @type {String}
|
|
* @todo: Clean this up
|
|
*/
|
|
// the negated condition is required to keep DEFAULT_REQ_METHOD as a fallback
|
|
method: _.has(options, 'method') && !_.isNil(options.method) ?
|
|
String(options.method).toUpperCase() : DEFAULT_REQ_METHOD
|
|
});
|
|
|
|
this.update(options);
|
|
}), Property);
|
|
|
|
_.assign(Request.prototype, /** @lends Request.prototype */ {
|
|
|
|
/**
|
|
* Updates the different properties of the request.
|
|
*
|
|
* @param {Request.definition} options -
|
|
*/
|
|
update: function (options) {
|
|
// Nothing to do
|
|
if (!options) { return; }
|
|
|
|
// The existing url is updated.
|
|
_.has(options, 'url') && this.url.update(options.url);
|
|
|
|
// The existing list of headers must be cleared before adding the given headers to it.
|
|
options.header && this.headers.repopulate(options.header);
|
|
|
|
// Only update the method if one is provided.
|
|
_.has(options, 'method') && (this.method = _.isNil(options.method) ?
|
|
DEFAULT_REQ_METHOD : String(options.method).toUpperCase());
|
|
|
|
// The rest of the properties are not assumed to exist so we merge in the defined ones.
|
|
_.mergeDefined(this, /** @lends Request.prototype */ {
|
|
/**
|
|
* @type {RequestBody|undefined}
|
|
*/
|
|
body: _.createDefined(options, 'body', RequestBody),
|
|
|
|
// auth is a special case, empty RequestAuth should not be created for falsy values
|
|
// to allow inheritance from parent
|
|
/**
|
|
* @type {RequestAuth}
|
|
*/
|
|
auth: options.auth ? new RequestAuth(options.auth) : undefined,
|
|
|
|
/**
|
|
* @type {ProxyConfig}
|
|
*/
|
|
proxy: options.proxy && new ProxyConfig(options.proxy),
|
|
|
|
/**
|
|
* @type {Certificate|undefined}
|
|
*/
|
|
certificate: options.certificate && new Certificate(options.certificate)
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Sets authentication method for the request
|
|
*
|
|
* @param {?String|RequestAuth.definition} type -
|
|
* @param {VariableList=} [options] -
|
|
*
|
|
* @note This function was previously (in v2 of SDK) used to clone request and populate headers. Now it is used to
|
|
* only set auth information to request
|
|
*
|
|
* @note that ItemGroup#authorizeUsing depends on this function
|
|
*/
|
|
authorizeUsing: function (type, options) {
|
|
if (_.isObject(type) && _.isNil(options)) {
|
|
options = _.omit(type, 'type');
|
|
type = type.type;
|
|
}
|
|
|
|
// null = delete request
|
|
if (type === null) {
|
|
_.has(this, 'auth') && (delete this.auth);
|
|
|
|
return;
|
|
}
|
|
|
|
if (!RequestAuth.isValidType(type)) {
|
|
return;
|
|
}
|
|
|
|
// create a new authentication data
|
|
if (!this.auth) {
|
|
this.auth = new RequestAuth(null, this);
|
|
}
|
|
else {
|
|
this.auth.clear(type);
|
|
}
|
|
|
|
this.auth.use(type, options);
|
|
},
|
|
|
|
/**
|
|
* Returns an object where the key is a header name and value is the header value.
|
|
*
|
|
* @param {Object=} options -
|
|
* @param {Boolean} options.ignoreCase When set to "true", will ensure that all the header keys are lower case.
|
|
* @param {Boolean} options.enabled Only get the enabled headers
|
|
* @param {Boolean} options.multiValue When set to "true", duplicate header values will be stored in an array
|
|
* @param {Boolean} options.sanitizeKeys When set to "true", headers with falsy keys are removed
|
|
* @returns {Object}
|
|
* @note If multiple headers are present in the same collection with same name, but different case
|
|
* (E.g "x-forward-port" and "X-Forward-Port", and `options.ignoreCase` is set to true,
|
|
* the values will be stored in an array.
|
|
*/
|
|
getHeaders: function getHeaders (options) {
|
|
!options && (options = {});
|
|
|
|
// @note: options.multiValue will not be respected since, Header._postman_propertyAllowsMultipleValues
|
|
// gets higher precedence in PropertyLists.toObject.
|
|
// @todo: sanitizeKeys for headers by default.
|
|
return this.headers.toObject(options.enabled, !options.ignoreCase, options.multiValue, options.sanitizeKeys);
|
|
},
|
|
|
|
/**
|
|
* Calls the given callback on each Header object contained within the request.
|
|
*
|
|
* @param {Function} callback -
|
|
*/
|
|
forEachHeader: function forEachHeader (callback) {
|
|
this.headers.all().forEach(function (header) {
|
|
return callback(header, this);
|
|
}, this);
|
|
},
|
|
|
|
/**
|
|
* Adds a header to the PropertyList of headers.
|
|
*
|
|
* @param {Header| {key: String, value: String}} header Can be a {Header} object, or a raw header object.
|
|
*/
|
|
addHeader: function (header) {
|
|
this.headers.add(header);
|
|
},
|
|
|
|
/**
|
|
* Removes a header from the request.
|
|
*
|
|
* @param {String|Header} toRemove A header object to remove, or a string containing the header key.
|
|
* @param {Object} options -
|
|
* @param {Boolean} options.ignoreCase If set to true, ignores case while removing the header.
|
|
*/
|
|
removeHeader: function (toRemove, options) {
|
|
toRemove = _.isString(toRemove) ? toRemove : toRemove.key;
|
|
|
|
options = options || {};
|
|
|
|
if (!toRemove) { // Nothing to remove :(
|
|
return;
|
|
}
|
|
|
|
options.ignoreCase && (toRemove = toRemove.toLowerCase());
|
|
|
|
this.headers.remove(function (header) {
|
|
var key = options.ignoreCase ? header.key.toLowerCase() : header.key;
|
|
|
|
return key === toRemove;
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Updates or inserts the given header.
|
|
*
|
|
* @param {Object} header -
|
|
*/
|
|
upsertHeader: function (header) {
|
|
if (!(header && header.key)) { return; } // if no valid header is provided, do nothing
|
|
|
|
var existing = this.headers.find({ key: header.key });
|
|
|
|
if (!existing) {
|
|
return this.headers.add(header);
|
|
}
|
|
|
|
existing.value = header.value;
|
|
},
|
|
|
|
/**
|
|
* Add query parameters to the request.
|
|
*
|
|
* @todo: Rename this?
|
|
* @param {Array<QueryParam>|String} params -
|
|
*/
|
|
addQueryParams: function (params) {
|
|
this.url.addQueryParams(params);
|
|
},
|
|
|
|
/**
|
|
* Removes parameters passed in params.
|
|
*
|
|
* @param {String|Array} params -
|
|
*/
|
|
removeQueryParams: function (params) {
|
|
this.url.removeQueryParams(params);
|
|
},
|
|
|
|
/**
|
|
* Get the request size by computing the headers and body or using the
|
|
* actual content length header once the request is sent.
|
|
*
|
|
* @returns {Object}
|
|
*/
|
|
size: function () {
|
|
var contentLength = this.headers.get(CONTENT_LENGTH),
|
|
requestTarget = this.url.getPathWithQuery(),
|
|
bodyString,
|
|
sizeInfo = {
|
|
body: 0,
|
|
header: 0,
|
|
total: 0,
|
|
source: SIZE_SOURCE.computed
|
|
};
|
|
|
|
// if 'Content-Length' header is present, we take body as declared by
|
|
// the client(postman-request or user-defined). else we need to compute the same.
|
|
if (contentLength && util.isNumeric(contentLength)) {
|
|
sizeInfo.body = parseInt(contentLength, 10);
|
|
sizeInfo.source = SIZE_SOURCE.contentLength;
|
|
}
|
|
// otherwise, if body is defined, we calculate the length of the body
|
|
else if (this.body) {
|
|
// @note body.toString() returns E for formdata or file mode
|
|
bodyString = this.body.toString();
|
|
sizeInfo.body = supportsBuffer ? Buffer.byteLength(bodyString) :
|
|
/* istanbul ignore next */
|
|
bodyString.length;
|
|
}
|
|
|
|
// https://tools.ietf.org/html/rfc7230#section-3
|
|
// HTTP-message = start-line (request-line / status-line)
|
|
// *( header-field CRLF )
|
|
// CRLF
|
|
// [ message-body ]
|
|
// request-line = method SP request-target SP HTTP-version CRLF
|
|
sizeInfo.header = (this.method + SP + requestTarget + SP + HTTP_X_X + CRLF + CRLF).length +
|
|
this.headers.contentSize();
|
|
|
|
// compute the approximate total body size by adding size of header and body
|
|
sizeInfo.total = (sizeInfo.body || 0) + (sizeInfo.header);
|
|
|
|
return sizeInfo;
|
|
},
|
|
|
|
/**
|
|
* Converts the Request to a plain JavaScript object, which is also how the request is
|
|
* represented in a collection file.
|
|
*
|
|
* @returns {{url: (*|String), method: *, header: (undefined|*), body: *, auth: *, certificate: *}}
|
|
*/
|
|
toJSON: function () {
|
|
var obj = PropertyBase.toJSON(this);
|
|
|
|
// remove header array if blank
|
|
if (_.isArray(obj.header) && !obj.header.length) {
|
|
delete obj.header;
|
|
}
|
|
|
|
return obj;
|
|
},
|
|
|
|
/**
|
|
* Creates a clone of this request
|
|
*
|
|
* @returns {Request}
|
|
*/
|
|
clone: function () {
|
|
return new Request(this.toJSON());
|
|
}
|
|
});
|
|
|
|
_.assign(Request, /** @lends Request */ {
|
|
/**
|
|
* Defines the name of this property for internal use.
|
|
*
|
|
* @private
|
|
* @readOnly
|
|
* @type {String}
|
|
*/
|
|
_postman_propertyName: 'Request',
|
|
|
|
/**
|
|
* Check whether an object is an instance of {@link ItemGroup}.
|
|
*
|
|
* @param {*} obj -
|
|
* @returns {Boolean}
|
|
*/
|
|
isRequest: function (obj) {
|
|
return Boolean(obj) && ((obj instanceof Request) ||
|
|
_.inSuperChain(obj.constructor, '_postman_propertyName', Request._postman_propertyName));
|
|
}
|
|
});
|
|
|
|
module.exports = {
|
|
Request
|
|
};
|