client.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920
  1. /**
  2. * Root reference for iframes.
  3. */
  4. var root;
  5. if (typeof window !== 'undefined') { // Browser window
  6. root = window;
  7. } else if (typeof self !== 'undefined') { // Web Worker
  8. root = self;
  9. } else { // Other environments
  10. console.warn("Using browser-only version of superagent in non-browser environment");
  11. root = this;
  12. }
  13. var Emitter = require('component-emitter');
  14. var RequestBase = require('./request-base');
  15. var isObject = require('./is-object');
  16. var ResponseBase = require('./response-base');
  17. var Agent = require('./agent-base');
  18. /**
  19. * Noop.
  20. */
  21. function noop(){};
  22. /**
  23. * Expose `request`.
  24. */
  25. var request = exports = module.exports = function(method, url) {
  26. // callback
  27. if ('function' == typeof url) {
  28. return new exports.Request('GET', method).end(url);
  29. }
  30. // url first
  31. if (1 == arguments.length) {
  32. return new exports.Request('GET', method);
  33. }
  34. return new exports.Request(method, url);
  35. }
  36. exports.Request = Request;
  37. /**
  38. * Determine XHR.
  39. */
  40. request.getXHR = function () {
  41. if (root.XMLHttpRequest
  42. && (!root.location || 'file:' != root.location.protocol
  43. || !root.ActiveXObject)) {
  44. return new XMLHttpRequest;
  45. } else {
  46. try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch(e) {}
  47. try { return new ActiveXObject('Msxml2.XMLHTTP.6.0'); } catch(e) {}
  48. try { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); } catch(e) {}
  49. try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch(e) {}
  50. }
  51. throw Error("Browser-only version of superagent could not find XHR");
  52. };
  53. /**
  54. * Removes leading and trailing whitespace, added to support IE.
  55. *
  56. * @param {String} s
  57. * @return {String}
  58. * @api private
  59. */
  60. var trim = ''.trim
  61. ? function(s) { return s.trim(); }
  62. : function(s) { return s.replace(/(^\s*|\s*$)/g, ''); };
  63. /**
  64. * Serialize the given `obj`.
  65. *
  66. * @param {Object} obj
  67. * @return {String}
  68. * @api private
  69. */
  70. function serialize(obj) {
  71. if (!isObject(obj)) return obj;
  72. var pairs = [];
  73. for (var key in obj) {
  74. pushEncodedKeyValuePair(pairs, key, obj[key]);
  75. }
  76. return pairs.join('&');
  77. }
  78. /**
  79. * Helps 'serialize' with serializing arrays.
  80. * Mutates the pairs array.
  81. *
  82. * @param {Array} pairs
  83. * @param {String} key
  84. * @param {Mixed} val
  85. */
  86. function pushEncodedKeyValuePair(pairs, key, val) {
  87. if (val != null) {
  88. if (Array.isArray(val)) {
  89. val.forEach(function(v) {
  90. pushEncodedKeyValuePair(pairs, key, v);
  91. });
  92. } else if (isObject(val)) {
  93. for(var subkey in val) {
  94. pushEncodedKeyValuePair(pairs, key + '[' + subkey + ']', val[subkey]);
  95. }
  96. } else {
  97. pairs.push(encodeURIComponent(key)
  98. + '=' + encodeURIComponent(val));
  99. }
  100. } else if (val === null) {
  101. pairs.push(encodeURIComponent(key));
  102. }
  103. }
  104. /**
  105. * Expose serialization method.
  106. */
  107. request.serializeObject = serialize;
  108. /**
  109. * Parse the given x-www-form-urlencoded `str`.
  110. *
  111. * @param {String} str
  112. * @return {Object}
  113. * @api private
  114. */
  115. function parseString(str) {
  116. var obj = {};
  117. var pairs = str.split('&');
  118. var pair;
  119. var pos;
  120. for (var i = 0, len = pairs.length; i < len; ++i) {
  121. pair = pairs[i];
  122. pos = pair.indexOf('=');
  123. if (pos == -1) {
  124. obj[decodeURIComponent(pair)] = '';
  125. } else {
  126. obj[decodeURIComponent(pair.slice(0, pos))] =
  127. decodeURIComponent(pair.slice(pos + 1));
  128. }
  129. }
  130. return obj;
  131. }
  132. /**
  133. * Expose parser.
  134. */
  135. request.parseString = parseString;
  136. /**
  137. * Default MIME type map.
  138. *
  139. * superagent.types.xml = 'application/xml';
  140. *
  141. */
  142. request.types = {
  143. html: 'text/html',
  144. json: 'application/json',
  145. xml: 'text/xml',
  146. urlencoded: 'application/x-www-form-urlencoded',
  147. 'form': 'application/x-www-form-urlencoded',
  148. 'form-data': 'application/x-www-form-urlencoded'
  149. };
  150. /**
  151. * Default serialization map.
  152. *
  153. * superagent.serialize['application/xml'] = function(obj){
  154. * return 'generated xml here';
  155. * };
  156. *
  157. */
  158. request.serialize = {
  159. 'application/x-www-form-urlencoded': serialize,
  160. 'application/json': JSON.stringify
  161. };
  162. /**
  163. * Default parsers.
  164. *
  165. * superagent.parse['application/xml'] = function(str){
  166. * return { object parsed from str };
  167. * };
  168. *
  169. */
  170. request.parse = {
  171. 'application/x-www-form-urlencoded': parseString,
  172. 'application/json': JSON.parse
  173. };
  174. /**
  175. * Parse the given header `str` into
  176. * an object containing the mapped fields.
  177. *
  178. * @param {String} str
  179. * @return {Object}
  180. * @api private
  181. */
  182. function parseHeader(str) {
  183. var lines = str.split(/\r?\n/);
  184. var fields = {};
  185. var index;
  186. var line;
  187. var field;
  188. var val;
  189. for (var i = 0, len = lines.length; i < len; ++i) {
  190. line = lines[i];
  191. index = line.indexOf(':');
  192. if (index === -1) { // could be empty line, just skip it
  193. continue;
  194. }
  195. field = line.slice(0, index).toLowerCase();
  196. val = trim(line.slice(index + 1));
  197. fields[field] = val;
  198. }
  199. return fields;
  200. }
  201. /**
  202. * Check if `mime` is json or has +json structured syntax suffix.
  203. *
  204. * @param {String} mime
  205. * @return {Boolean}
  206. * @api private
  207. */
  208. function isJSON(mime) {
  209. // should match /json or +json
  210. // but not /json-seq
  211. return /[\/+]json($|[^-\w])/.test(mime);
  212. }
  213. /**
  214. * Initialize a new `Response` with the given `xhr`.
  215. *
  216. * - set flags (.ok, .error, etc)
  217. * - parse header
  218. *
  219. * Examples:
  220. *
  221. * Aliasing `superagent` as `request` is nice:
  222. *
  223. * request = superagent;
  224. *
  225. * We can use the promise-like API, or pass callbacks:
  226. *
  227. * request.get('/').end(function(res){});
  228. * request.get('/', function(res){});
  229. *
  230. * Sending data can be chained:
  231. *
  232. * request
  233. * .post('/user')
  234. * .send({ name: 'tj' })
  235. * .end(function(res){});
  236. *
  237. * Or passed to `.send()`:
  238. *
  239. * request
  240. * .post('/user')
  241. * .send({ name: 'tj' }, function(res){});
  242. *
  243. * Or passed to `.post()`:
  244. *
  245. * request
  246. * .post('/user', { name: 'tj' })
  247. * .end(function(res){});
  248. *
  249. * Or further reduced to a single call for simple cases:
  250. *
  251. * request
  252. * .post('/user', { name: 'tj' }, function(res){});
  253. *
  254. * @param {XMLHTTPRequest} xhr
  255. * @param {Object} options
  256. * @api private
  257. */
  258. function Response(req) {
  259. this.req = req;
  260. this.xhr = this.req.xhr;
  261. // responseText is accessible only if responseType is '' or 'text' and on older browsers
  262. this.text = ((this.req.method !='HEAD' && (this.xhr.responseType === '' || this.xhr.responseType === 'text')) || typeof this.xhr.responseType === 'undefined')
  263. ? this.xhr.responseText
  264. : null;
  265. this.statusText = this.req.xhr.statusText;
  266. var status = this.xhr.status;
  267. // handle IE9 bug: http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request
  268. if (status === 1223) {
  269. status = 204;
  270. }
  271. this._setStatusProperties(status);
  272. this.header = this.headers = parseHeader(this.xhr.getAllResponseHeaders());
  273. // getAllResponseHeaders sometimes falsely returns "" for CORS requests, but
  274. // getResponseHeader still works. so we get content-type even if getting
  275. // other headers fails.
  276. this.header['content-type'] = this.xhr.getResponseHeader('content-type');
  277. this._setHeaderProperties(this.header);
  278. if (null === this.text && req._responseType) {
  279. this.body = this.xhr.response;
  280. } else {
  281. this.body = this.req.method != 'HEAD'
  282. ? this._parseBody(this.text ? this.text : this.xhr.response)
  283. : null;
  284. }
  285. }
  286. ResponseBase(Response.prototype);
  287. /**
  288. * Parse the given body `str`.
  289. *
  290. * Used for auto-parsing of bodies. Parsers
  291. * are defined on the `superagent.parse` object.
  292. *
  293. * @param {String} str
  294. * @return {Mixed}
  295. * @api private
  296. */
  297. Response.prototype._parseBody = function(str) {
  298. var parse = request.parse[this.type];
  299. if (this.req._parser) {
  300. return this.req._parser(this, str);
  301. }
  302. if (!parse && isJSON(this.type)) {
  303. parse = request.parse['application/json'];
  304. }
  305. return parse && str && (str.length || str instanceof Object)
  306. ? parse(str)
  307. : null;
  308. };
  309. /**
  310. * Return an `Error` representative of this response.
  311. *
  312. * @return {Error}
  313. * @api public
  314. */
  315. Response.prototype.toError = function(){
  316. var req = this.req;
  317. var method = req.method;
  318. var url = req.url;
  319. var msg = 'cannot ' + method + ' ' + url + ' (' + this.status + ')';
  320. var err = new Error(msg);
  321. err.status = this.status;
  322. err.method = method;
  323. err.url = url;
  324. return err;
  325. };
  326. /**
  327. * Expose `Response`.
  328. */
  329. request.Response = Response;
  330. /**
  331. * Initialize a new `Request` with the given `method` and `url`.
  332. *
  333. * @param {String} method
  334. * @param {String} url
  335. * @api public
  336. */
  337. function Request(method, url) {
  338. var self = this;
  339. this._query = this._query || [];
  340. this.method = method;
  341. this.url = url;
  342. this.header = {}; // preserves header name case
  343. this._header = {}; // coerces header names to lowercase
  344. this.on('end', function(){
  345. var err = null;
  346. var res = null;
  347. try {
  348. res = new Response(self);
  349. } catch(e) {
  350. err = new Error('Parser is unable to parse the response');
  351. err.parse = true;
  352. err.original = e;
  353. // issue #675: return the raw response if the response parsing fails
  354. if (self.xhr) {
  355. // ie9 doesn't have 'response' property
  356. err.rawResponse = typeof self.xhr.responseType == 'undefined' ? self.xhr.responseText : self.xhr.response;
  357. // issue #876: return the http status code if the response parsing fails
  358. err.status = self.xhr.status ? self.xhr.status : null;
  359. err.statusCode = err.status; // backwards-compat only
  360. } else {
  361. err.rawResponse = null;
  362. err.status = null;
  363. }
  364. return self.callback(err);
  365. }
  366. self.emit('response', res);
  367. var new_err;
  368. try {
  369. if (!self._isResponseOK(res)) {
  370. new_err = new Error(res.statusText || 'Unsuccessful HTTP response');
  371. }
  372. } catch(custom_err) {
  373. new_err = custom_err; // ok() callback can throw
  374. }
  375. // #1000 don't catch errors from the callback to avoid double calling it
  376. if (new_err) {
  377. new_err.original = err;
  378. new_err.response = res;
  379. new_err.status = res.status;
  380. self.callback(new_err, res);
  381. } else {
  382. self.callback(null, res);
  383. }
  384. });
  385. }
  386. /**
  387. * Mixin `Emitter` and `RequestBase`.
  388. */
  389. Emitter(Request.prototype);
  390. RequestBase(Request.prototype);
  391. /**
  392. * Set Content-Type to `type`, mapping values from `request.types`.
  393. *
  394. * Examples:
  395. *
  396. * superagent.types.xml = 'application/xml';
  397. *
  398. * request.post('/')
  399. * .type('xml')
  400. * .send(xmlstring)
  401. * .end(callback);
  402. *
  403. * request.post('/')
  404. * .type('application/xml')
  405. * .send(xmlstring)
  406. * .end(callback);
  407. *
  408. * @param {String} type
  409. * @return {Request} for chaining
  410. * @api public
  411. */
  412. Request.prototype.type = function(type){
  413. this.set('Content-Type', request.types[type] || type);
  414. return this;
  415. };
  416. /**
  417. * Set Accept to `type`, mapping values from `request.types`.
  418. *
  419. * Examples:
  420. *
  421. * superagent.types.json = 'application/json';
  422. *
  423. * request.get('/agent')
  424. * .accept('json')
  425. * .end(callback);
  426. *
  427. * request.get('/agent')
  428. * .accept('application/json')
  429. * .end(callback);
  430. *
  431. * @param {String} accept
  432. * @return {Request} for chaining
  433. * @api public
  434. */
  435. Request.prototype.accept = function(type){
  436. this.set('Accept', request.types[type] || type);
  437. return this;
  438. };
  439. /**
  440. * Set Authorization field value with `user` and `pass`.
  441. *
  442. * @param {String} user
  443. * @param {String} [pass] optional in case of using 'bearer' as type
  444. * @param {Object} options with 'type' property 'auto', 'basic' or 'bearer' (default 'basic')
  445. * @return {Request} for chaining
  446. * @api public
  447. */
  448. Request.prototype.auth = function(user, pass, options){
  449. if (1 === arguments.length) pass = '';
  450. if (typeof pass === 'object' && pass !== null) { // pass is optional and can be replaced with options
  451. options = pass;
  452. pass = '';
  453. }
  454. if (!options) {
  455. options = {
  456. type: 'function' === typeof btoa ? 'basic' : 'auto',
  457. };
  458. }
  459. var encoder = function(string) {
  460. if ('function' === typeof btoa) {
  461. return btoa(string);
  462. }
  463. throw new Error('Cannot use basic auth, btoa is not a function');
  464. };
  465. return this._auth(user, pass, options, encoder);
  466. };
  467. /**
  468. * Add query-string `val`.
  469. *
  470. * Examples:
  471. *
  472. * request.get('/shoes')
  473. * .query('size=10')
  474. * .query({ color: 'blue' })
  475. *
  476. * @param {Object|String} val
  477. * @return {Request} for chaining
  478. * @api public
  479. */
  480. Request.prototype.query = function(val){
  481. if ('string' != typeof val) val = serialize(val);
  482. if (val) this._query.push(val);
  483. return this;
  484. };
  485. /**
  486. * Queue the given `file` as an attachment to the specified `field`,
  487. * with optional `options` (or filename).
  488. *
  489. * ``` js
  490. * request.post('/upload')
  491. * .attach('content', new Blob(['<a id="a"><b id="b">hey!</b></a>'], { type: "text/html"}))
  492. * .end(callback);
  493. * ```
  494. *
  495. * @param {String} field
  496. * @param {Blob|File} file
  497. * @param {String|Object} options
  498. * @return {Request} for chaining
  499. * @api public
  500. */
  501. Request.prototype.attach = function(field, file, options){
  502. if (file) {
  503. if (this._data) {
  504. throw Error("superagent can't mix .send() and .attach()");
  505. }
  506. this._getFormData().append(field, file, options || file.name);
  507. }
  508. return this;
  509. };
  510. Request.prototype._getFormData = function(){
  511. if (!this._formData) {
  512. this._formData = new root.FormData();
  513. }
  514. return this._formData;
  515. };
  516. /**
  517. * Invoke the callback with `err` and `res`
  518. * and handle arity check.
  519. *
  520. * @param {Error} err
  521. * @param {Response} res
  522. * @api private
  523. */
  524. Request.prototype.callback = function(err, res){
  525. if (this._shouldRetry(err, res)) {
  526. return this._retry();
  527. }
  528. var fn = this._callback;
  529. this.clearTimeout();
  530. if (err) {
  531. if (this._maxRetries) err.retries = this._retries - 1;
  532. this.emit('error', err);
  533. }
  534. fn(err, res);
  535. };
  536. /**
  537. * Invoke callback with x-domain error.
  538. *
  539. * @api private
  540. */
  541. Request.prototype.crossDomainError = function(){
  542. var err = new Error('Request has been terminated\nPossible causes: the network is offline, Origin is not allowed by Access-Control-Allow-Origin, the page is being unloaded, etc.');
  543. err.crossDomain = true;
  544. err.status = this.status;
  545. err.method = this.method;
  546. err.url = this.url;
  547. this.callback(err);
  548. };
  549. // This only warns, because the request is still likely to work
  550. Request.prototype.buffer = Request.prototype.ca = Request.prototype.agent = function(){
  551. console.warn("This is not supported in browser version of superagent");
  552. return this;
  553. };
  554. // This throws, because it can't send/receive data as expected
  555. Request.prototype.pipe = Request.prototype.write = function(){
  556. throw Error("Streaming is not supported in browser version of superagent");
  557. };
  558. /**
  559. * Check if `obj` is a host object,
  560. * we don't want to serialize these :)
  561. *
  562. * @param {Object} obj
  563. * @return {Boolean}
  564. * @api private
  565. */
  566. Request.prototype._isHost = function _isHost(obj) {
  567. // Native objects stringify to [object File], [object Blob], [object FormData], etc.
  568. return obj && 'object' === typeof obj && !Array.isArray(obj) && Object.prototype.toString.call(obj) !== '[object Object]';
  569. }
  570. /**
  571. * Initiate request, invoking callback `fn(res)`
  572. * with an instanceof `Response`.
  573. *
  574. * @param {Function} fn
  575. * @return {Request} for chaining
  576. * @api public
  577. */
  578. Request.prototype.end = function(fn){
  579. if (this._endCalled) {
  580. console.warn("Warning: .end() was called twice. This is not supported in superagent");
  581. }
  582. this._endCalled = true;
  583. // store callback
  584. this._callback = fn || noop;
  585. // querystring
  586. this._finalizeQueryString();
  587. return this._end();
  588. };
  589. Request.prototype._end = function() {
  590. var self = this;
  591. var xhr = (this.xhr = request.getXHR());
  592. var data = this._formData || this._data;
  593. this._setTimeouts();
  594. // state change
  595. xhr.onreadystatechange = function(){
  596. var readyState = xhr.readyState;
  597. if (readyState >= 2 && self._responseTimeoutTimer) {
  598. clearTimeout(self._responseTimeoutTimer);
  599. }
  600. if (4 != readyState) {
  601. return;
  602. }
  603. // In IE9, reads to any property (e.g. status) off of an aborted XHR will
  604. // result in the error "Could not complete the operation due to error c00c023f"
  605. var status;
  606. try { status = xhr.status } catch(e) { status = 0; }
  607. if (!status) {
  608. if (self.timedout || self._aborted) return;
  609. return self.crossDomainError();
  610. }
  611. self.emit('end');
  612. };
  613. // progress
  614. var handleProgress = function(direction, e) {
  615. if (e.total > 0) {
  616. e.percent = e.loaded / e.total * 100;
  617. }
  618. e.direction = direction;
  619. self.emit('progress', e);
  620. };
  621. if (this.hasListeners('progress')) {
  622. try {
  623. xhr.onprogress = handleProgress.bind(null, 'download');
  624. if (xhr.upload) {
  625. xhr.upload.onprogress = handleProgress.bind(null, 'upload');
  626. }
  627. } catch(e) {
  628. // Accessing xhr.upload fails in IE from a web worker, so just pretend it doesn't exist.
  629. // Reported here:
  630. // https://connect.microsoft.com/IE/feedback/details/837245/xmlhttprequest-upload-throws-invalid-argument-when-used-from-web-worker-context
  631. }
  632. }
  633. // initiate request
  634. try {
  635. if (this.username && this.password) {
  636. xhr.open(this.method, this.url, true, this.username, this.password);
  637. } else {
  638. xhr.open(this.method, this.url, true);
  639. }
  640. } catch (err) {
  641. // see #1149
  642. return this.callback(err);
  643. }
  644. // CORS
  645. if (this._withCredentials) xhr.withCredentials = true;
  646. // body
  647. if (!this._formData && 'GET' != this.method && 'HEAD' != this.method && 'string' != typeof data && !this._isHost(data)) {
  648. // serialize stuff
  649. var contentType = this._header['content-type'];
  650. var serialize = this._serializer || request.serialize[contentType ? contentType.split(';')[0] : ''];
  651. if (!serialize && isJSON(contentType)) {
  652. serialize = request.serialize['application/json'];
  653. }
  654. if (serialize) data = serialize(data);
  655. }
  656. // set header fields
  657. for (var field in this.header) {
  658. if (null == this.header[field]) continue;
  659. if (this.header.hasOwnProperty(field))
  660. xhr.setRequestHeader(field, this.header[field]);
  661. }
  662. if (this._responseType) {
  663. xhr.responseType = this._responseType;
  664. }
  665. // send stuff
  666. this.emit('request', this);
  667. // IE11 xhr.send(undefined) sends 'undefined' string as POST payload (instead of nothing)
  668. // We need null here if data is undefined
  669. xhr.send(typeof data !== 'undefined' ? data : null);
  670. return this;
  671. };
  672. request.agent = function() {
  673. return new Agent();
  674. };
  675. ["GET", "POST", "OPTIONS", "PATCH", "PUT", "DELETE"].forEach(function(method) {
  676. Agent.prototype[method.toLowerCase()] = function(url, fn) {
  677. var req = new request.Request(method, url);
  678. this._setDefaults(req);
  679. if (fn) {
  680. req.end(fn);
  681. }
  682. return req;
  683. };
  684. });
  685. Agent.prototype.del = Agent.prototype['delete'];
  686. /**
  687. * GET `url` with optional callback `fn(res)`.
  688. *
  689. * @param {String} url
  690. * @param {Mixed|Function} [data] or fn
  691. * @param {Function} [fn]
  692. * @return {Request}
  693. * @api public
  694. */
  695. request.get = function(url, data, fn) {
  696. var req = request('GET', url);
  697. if ('function' == typeof data) (fn = data), (data = null);
  698. if (data) req.query(data);
  699. if (fn) req.end(fn);
  700. return req;
  701. };
  702. /**
  703. * HEAD `url` with optional callback `fn(res)`.
  704. *
  705. * @param {String} url
  706. * @param {Mixed|Function} [data] or fn
  707. * @param {Function} [fn]
  708. * @return {Request}
  709. * @api public
  710. */
  711. request.head = function(url, data, fn) {
  712. var req = request('HEAD', url);
  713. if ('function' == typeof data) (fn = data), (data = null);
  714. if (data) req.query(data);
  715. if (fn) req.end(fn);
  716. return req;
  717. };
  718. /**
  719. * OPTIONS query to `url` with optional callback `fn(res)`.
  720. *
  721. * @param {String} url
  722. * @param {Mixed|Function} [data] or fn
  723. * @param {Function} [fn]
  724. * @return {Request}
  725. * @api public
  726. */
  727. request.options = function(url, data, fn) {
  728. var req = request('OPTIONS', url);
  729. if ('function' == typeof data) (fn = data), (data = null);
  730. if (data) req.send(data);
  731. if (fn) req.end(fn);
  732. return req;
  733. };
  734. /**
  735. * DELETE `url` with optional `data` and callback `fn(res)`.
  736. *
  737. * @param {String} url
  738. * @param {Mixed} [data]
  739. * @param {Function} [fn]
  740. * @return {Request}
  741. * @api public
  742. */
  743. function del(url, data, fn) {
  744. var req = request('DELETE', url);
  745. if ('function' == typeof data) (fn = data), (data = null);
  746. if (data) req.send(data);
  747. if (fn) req.end(fn);
  748. return req;
  749. }
  750. request['del'] = del;
  751. request['delete'] = del;
  752. /**
  753. * PATCH `url` with optional `data` and callback `fn(res)`.
  754. *
  755. * @param {String} url
  756. * @param {Mixed} [data]
  757. * @param {Function} [fn]
  758. * @return {Request}
  759. * @api public
  760. */
  761. request.patch = function(url, data, fn) {
  762. var req = request('PATCH', url);
  763. if ('function' == typeof data) (fn = data), (data = null);
  764. if (data) req.send(data);
  765. if (fn) req.end(fn);
  766. return req;
  767. };
  768. /**
  769. * POST `url` with optional `data` and callback `fn(res)`.
  770. *
  771. * @param {String} url
  772. * @param {Mixed} [data]
  773. * @param {Function} [fn]
  774. * @return {Request}
  775. * @api public
  776. */
  777. request.post = function(url, data, fn) {
  778. var req = request('POST', url);
  779. if ('function' == typeof data) (fn = data), (data = null);
  780. if (data) req.send(data);
  781. if (fn) req.end(fn);
  782. return req;
  783. };
  784. /**
  785. * PUT `url` with optional `data` and callback `fn(res)`.
  786. *
  787. * @param {String} url
  788. * @param {Mixed|Function} [data] or fn
  789. * @param {Function} [fn]
  790. * @return {Request}
  791. * @api public
  792. */
  793. request.put = function(url, data, fn) {
  794. var req = request('PUT', url);
  795. if ('function' == typeof data) (fn = data), (data = null);
  796. if (data) req.send(data);
  797. if (fn) req.end(fn);
  798. return req;
  799. };