-
Notifications
You must be signed in to change notification settings - Fork 215
/
request.js
418 lines (360 loc) · 12.7 KB
/
request.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
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
};