├── .travis.yml ├── Makefile ├── .gitignore ├── .editorconfig ├── CHANGELOG.md ├── package.json ├── LICENSE ├── tests ├── promises.js └── basic.js ├── README.md └── index.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "11" 4 | - "10" 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | ./node_modules/.bin/mocha ./tests/ --timeout 15000 --reporter list -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.{json,xml,yml}] 13 | indent_size = 2 14 | 15 | [*.{md,yml}] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### Next (Master - Unstable) 4 | 5 | - fix `Content-Type` contains `json` check 6 | - add support for request timing `Unirest.get(...).time().end()` 7 | - add support for sending `json` arrays 8 | - add `Unirest.Response.statusCodes` dictionary for response code sugar properties 9 | - add check for GZIP empty body error 10 | - add check for [MSIE `1223` status code](http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request) 11 | - update `request` to `2.65.0` 12 | - update documentation and typos 13 | - remove `Unirest.params` 14 | - lint code to adhere to `standard` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unirest", 3 | "version": "0.6.0", 4 | "description": "Simplified, lightweight HTTP client library", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "tests" 8 | }, 9 | "dependencies": { 10 | "form-data": "^0.2.0", 11 | "mime": "^2.4.0", 12 | "request": "^2.88.0" 13 | }, 14 | "devDependencies": { 15 | "body-parser": "^1.15.1", 16 | "express": "^4.13.4", 17 | "mocha": "^6.0.0", 18 | "should": "~8.3.1" 19 | }, 20 | "scripts": { 21 | "test": "make test" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git@github.com:Mashape/unirest-nodejs.git" 26 | }, 27 | "keywords": [ 28 | "request", 29 | "http", 30 | "library", 31 | "superagent", 32 | "simple", 33 | "util", 34 | "utility", 35 | "method" 36 | ], 37 | "author": "Mashape (https://www.mashape.com)", 38 | "license": "MIT", 39 | "bugs": { 40 | "url": "https://github.com/Mashape/unirest-nodejs/issues" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013-2015 Mashape (https://www.mashape.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /tests/promises.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var unirest = require('../index') 3 | var express = require('express') 4 | var bodyParser = require('body-parser') 5 | 6 | // Mock Server 7 | var app = express() 8 | var server 9 | 10 | describe('Unirest Promises', function () { 11 | describe('GET', function () { 12 | var host, port, url 13 | var fixture = { 14 | message: 'some message under a json object' 15 | } 16 | 17 | before(function(done) { 18 | app.use(bodyParser.json({ 19 | type: 'application/vnd.api+json' 20 | })) 21 | 22 | app.get('/', function handleRoot(req, res) { 23 | res.set('content-type', 'application/vnd.api+json') 24 | res.send(fixture) 25 | }) 26 | 27 | server = app.listen(3000, function liftServer () { 28 | host = server.address().address 29 | port = server.address().port 30 | url = 'http://localhost:3000' 31 | done() 32 | }) 33 | }) 34 | 35 | after(function afterAll (done) { 36 | server.close(function closeCallback () { 37 | done() 38 | }) 39 | }) 40 | 41 | it('supports async, and await', async function () { 42 | let response = await unirest.get(url).type('json') 43 | return response.body.should.eql(fixture) 44 | }) 45 | 46 | it('support chaining', function jsonTest (done) { 47 | unirest 48 | .get(url) 49 | .type('json') 50 | .then(function (response) { 51 | return response 52 | }) 53 | .then((response) => { 54 | response.body.should.eql(fixture) 55 | done() 56 | }) 57 | }) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /tests/basic.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var should = require('should') 3 | var unirest = require('../index') 4 | var express = require('express') 5 | var bodyParser = require('body-parser') 6 | 7 | // Mock Server 8 | var app = express() 9 | var server 10 | 11 | describe('Unirest', function () { 12 | describe('Cookie Jar', function () { 13 | it('should contain both add and getCookieString methods', function (done) { 14 | var jar = unirest.jar() 15 | 16 | jar.should.have.property('add') 17 | jar.should.have.property('getCookieString') 18 | jar.add.should.be.a.Function 19 | jar.getCookieString.should.be.a.Function 20 | 21 | done() 22 | }) 23 | }) 24 | 25 | describe('GET request', function () { 26 | it('should correctly parse JSON.', function (done) { 27 | unirest.get('http://mockbin.com/request').set('Accept', 'application/json').end(function (response) { 28 | should(response.status).equal(200) 29 | should(response.body).have.type('object') 30 | done() 31 | }) 32 | }) 33 | 34 | it('should correctly parse GZIPPED data.', function (done) { 35 | unirest.get('http://mockbin.com/gzip/request').set('Accept-Encoding', 'gzip').end(function (response) { 36 | should(response.status).equal(200) 37 | should(response.body).have.type('object') 38 | done() 39 | }) 40 | }) 41 | 42 | it('should correctly handle redirects.', function (done) { 43 | unirest.get('http://mockbin.com/redirect/302').timeout(2500).end(function (response) { 44 | should(response.status).equal(200) 45 | should(response.body).equal('redirect finished') 46 | done() 47 | }) 48 | }) 49 | 50 | it('should correctly handle timeouts.', function (done) { 51 | unirest.get('http://mockbin.com/redirect/3').timeout(20).end(function (response) { 52 | response.error.should.exist 53 | response.error.code.should.equal('ETIMEDOUT') 54 | done() 55 | }) 56 | }) 57 | 58 | it('should correctly handle timeouts with 3 retries.', function (done) { 59 | var retryCount = 0; 60 | unirest.get('http://mockbin.com/redirect/3') 61 | .timeout(20) 62 | .retry(function (response) { 63 | retryCount++; 64 | }) 65 | .end(function (response) { 66 | response.error.should.exist 67 | response.error.code.should.equal('ETIMEDOUT') 68 | should(retryCount).equal(3) 69 | done() 70 | }) 71 | }) 72 | 73 | it('should correctly handle refused connections.', function (done) { 74 | unirest.get('http://localhost:9999').timeout(200).end(function (response) { 75 | response.error.should.exist 76 | response.error.code.should.equal('ECONNREFUSED') 77 | done() 78 | }) 79 | }) 80 | 81 | it('should be able to work like other unirest libraries', function (done) { 82 | unirest.get('http://mockbin.com/gzip/request', { 'Accept-Encoding': 'gzip' }, 'Hello World', function (response) { 83 | should(response.status).equal(200) 84 | should(response.body).have.type('object') 85 | done() 86 | }) 87 | }) 88 | 89 | it('should be able to return the request time', function (done) { 90 | unirest.get('http://mockbin.com').time(true).end(function (response) { 91 | should(typeof response.elapsedTime).equal('number') 92 | should((response.elapsedTime > 0), true) 93 | done() 94 | }) 95 | }) 96 | }) 97 | 98 | describe('GET request', function () { 99 | var host, port, url 100 | var fixture = { 101 | message: 'some message under a json object' 102 | } 103 | 104 | before(function(done) { 105 | app.use(bodyParser.json({ 106 | type: 'application/vnd.api+json' 107 | })) 108 | 109 | app.get('/', function handleRoot(req, res) { 110 | res.set('content-type', 'application/vnd.api+json') 111 | res.send(fixture) 112 | }) 113 | 114 | server = app.listen(3000, function liftServer () { 115 | host = server.address().address 116 | port = server.address().port 117 | url = 'http://localhost:3000' 118 | done() 119 | }) 120 | }) 121 | 122 | after(function afterAll (done) { 123 | server.close(function closeCallback () { 124 | done() 125 | }) 126 | }) 127 | 128 | it('should get a json from the main route', function jsonTest (done) { 129 | unirest.get(url).type('json').end(function endJsonTest (response) { 130 | response.body.should.eql(fixture) 131 | done() 132 | }) 133 | }) 134 | }) 135 | 136 | describe('POST request', function () { 137 | it('should correctly post FORM data.', function (done) { 138 | var data = { 139 | is: 'unirest', 140 | my: 'name', 141 | hello: 'world' 142 | } 143 | 144 | unirest.post('http://mockbin.com/request').send(data).end(function (response) { 145 | should(response.status).equal(200) 146 | should(response.body.postData.params).have.type('object') 147 | should(response.body.postData.params).have.property('is', data.is) 148 | should(response.body.postData.params).have.property('my', data.my) 149 | should(response.body.postData.params).have.property('hello', data.hello) 150 | should(response.body.headers['content-length']).equal('30') 151 | should(response.body.headers['content-type']).equal('application/x-www-form-urlencoded') 152 | done() 153 | }) 154 | }) 155 | 156 | it('should not fail when content-type header is specified.', function (done) { 157 | var data = { 158 | is: 'unirest', 159 | my: 'name', 160 | hello: 'world', 161 | failing_to_encode_data: 'this is a test' 162 | } 163 | 164 | unirest.post('http://mockbin.com/request').header('Content-Type', 'application/x-www-form-urlencoded').send(data).end(function (response) { 165 | should(response.status).equal(200) 166 | should(response.body.postData.params).have.type('object') 167 | should(response.body.postData.params).have.property('is', data.is) 168 | should(response.body.postData.params).have.property('my', data.my) 169 | should(response.body.postData.params).have.property('hello', data.hello) 170 | should(response.body.postData.params).have.property('failing_to_encode_data', data.failing_to_encode_data) 171 | should(response.body.headers['content-length']).equal('74') 172 | should(response.body.headers['content-type']).equal('application/x-www-form-urlencoded') 173 | done() 174 | }) 175 | }) 176 | 177 | it('should correctly transform FORM data.', function (done) { 178 | var data = { 179 | lebron: false, 180 | jordan: 23, 181 | testing: { key: 'value' }, 182 | scores: [ 183 | 1, 184 | 3, 185 | 2, 186 | 1, 187 | 3 188 | ] 189 | } 190 | 191 | try { 192 | var request = unirest.post('http://mockbin.com/request').set('Accept', 'application/json') 193 | 194 | for (var key in data) { 195 | request.field(key, data[key]) 196 | } 197 | 198 | request.end(function (response) { 199 | should(response.status).equal(200) 200 | should(response.body.postData.params).have.property('jordan', data.jordan.toString()) 201 | should(response.body.postData.params).have.property('lebron', data.lebron.toString()) 202 | should(response.body.postData.params).have.property('testing', JSON.stringify(data.testing)) 203 | should(response.body.postData.params).have.property('scores', ['1', '3', '2', '1', '3']) 204 | }) 205 | } catch (e) { 206 | done(e) 207 | } finally { 208 | done() 209 | } 210 | }) 211 | 212 | it('should correctly post MULTIFORM data.', function (done) { 213 | var request = unirest.post('http://mockbin.com/request') 214 | var file = __dirname + '/../README.md' 215 | var data = { 216 | a: 'foo', 217 | b: 'bar', 218 | c: undefined 219 | } 220 | 221 | request.attach('u', file) 222 | 223 | for (var key in data) { 224 | request.field(key, data[key]) 225 | } 226 | 227 | request.end(function (response) { 228 | should(response.status).equal(200) 229 | should(response.body.headers['content-type']).startWith('multipart/form-data') 230 | done() 231 | }) 232 | }) 233 | 234 | it('should correctly post MULTIFORM data using fs.createReadStream.', function (done) { 235 | var request = unirest.post('http://mockbin.com/request') 236 | var file = __dirname + '/../README.md' 237 | var data = { 238 | a: 'foo', 239 | b: 'bar', 240 | c: undefined 241 | } 242 | 243 | request.attach({'u': fs.createReadStream(file)}) 244 | 245 | for (var key in data) { 246 | request.field(key, data[key]) 247 | } 248 | 249 | request.end(function (response) { 250 | should(response.status).equal(200) 251 | should(response.body.headers['content-type']).startWith('multipart/form-data') 252 | done() 253 | }) 254 | }) 255 | 256 | it('should correctly post MULTIFORM data with port number', function (done) { 257 | var request = unirest.post('http://mockbin.com:80/request') 258 | var file = __dirname + '/../README.md' 259 | var data = { 260 | a: 'foo', 261 | b: 'bar', 262 | c: undefined 263 | } 264 | 265 | request.attach('u', file) 266 | 267 | for (var key in data) { 268 | request.field(key, data[key]) 269 | } 270 | 271 | request.end(function (response) { 272 | should(response.status).equal(200) 273 | should(response.body.headers['content-type']).startWith('multipart/form-data') 274 | done() 275 | }) 276 | }) 277 | 278 | it('should correctly post JSON data.', function (done) { 279 | var data = { 280 | is: 'unirest', 281 | my: 'name', 282 | hello: 'world' 283 | } 284 | 285 | unirest.post('http://mockbin.com/echo').header('Content-Type', 'application/json').send(data).end(function (response) { 286 | should(response.status).equal(200) 287 | should(response.body).have.type('object') 288 | should(JSON.stringify(response.body)).equal(JSON.stringify(data)) 289 | should(response.headers['content-type']).startWith('application/json') 290 | done() 291 | }) 292 | }) 293 | 294 | it('should correctly post JSON array.', function (done) { 295 | var data = [{ 296 | is: 'unirest', 297 | my: 'name', 298 | hello: 'world' 299 | }] 300 | 301 | unirest.post('http://mockbin.com/echo').header('Content-Type', 'application/json').send(data).end(function (response) { 302 | should(response.status).equal(200) 303 | should(response.body).have.type('object') 304 | should(JSON.stringify(response.body)).equal(JSON.stringify(data)) 305 | should(response.headers['content-type']).startWith('application/json') 306 | done() 307 | }) 308 | }) 309 | 310 | it('should check for buffers', function (done) { 311 | unirest.post('http://mockbin.com/request') 312 | .headers({ 'Accept': 'application/json' }) 313 | .send(Buffer.from([1, 2, 3])) 314 | .end(function (response) { 315 | should(response.body.bodySize).exists 316 | should(response.body.bodySize).equal(3) 317 | done() 318 | }) 319 | }) 320 | 321 | it('should correctly post a buffer with mime-type', function (done) { 322 | unirest.post('http://mockbin.com/request') 323 | .headers({ 'Content-Type': 'img/svg+xml' }) 324 | .send(Buffer.from('')) 325 | .end(function (response) { 326 | should(response.body.headers['content-type']).equal('img/svg+xml') 327 | done() 328 | }) 329 | }) 330 | }) 331 | 332 | describe('bodyparser', function unirestDescribe () { 333 | var host, port, url 334 | var postCall, exampleRequest 335 | 336 | before(function beforeAll (done) { 337 | exampleRequest = { 338 | data: { 339 | id: 1, 340 | type: 'Person', 341 | name: 'Nico' 342 | } 343 | } 344 | 345 | app.use(bodyParser.json({ type: 'application/vnd.api+json' })) 346 | 347 | app.post('/', function handleRoot (req, res) { 348 | res.send(req.body) 349 | }) 350 | 351 | server = app.listen(3000, function liftServer () { 352 | host = server.address().address 353 | port = server.address().port 354 | url = 'http://localhost:3000' 355 | done() 356 | }) 357 | }) 358 | 359 | after(function afterAll (done) { 360 | server.close(function closeCallback () { 361 | done() 362 | }) 363 | }) 364 | 365 | it('should send a custom content type as a header using the type function', function jsonTest (done) { 366 | unirest 367 | .post(url + '/') 368 | .type('application/vnd.api+json') 369 | .send(exampleRequest) 370 | .end(function endJsonTest (response) { 371 | response.body.should.eql(exampleRequest) 372 | done() 373 | }) 374 | }) 375 | 376 | it('should send a custom header with the header function', function jsonTest (done) { 377 | unirest 378 | .post(url + '/') 379 | .header('Content-Type', 'application/vnd.api+json') 380 | .send(exampleRequest) 381 | .end(function endJsonTest (response) { 382 | response.body.should.eql(exampleRequest) 383 | done() 384 | }) 385 | }) 386 | }) 387 | }) 388 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unirest for Node.js [![Build Status][travis-image]][travis-url] 2 | 3 | [![License][npm-license]][license-url] 4 | [![Downloads][npm-downloads]][npm-url] 5 | [![Gitter][gitter-image]][gitter-url] 6 | [![Not Maintained](https://img.shields.io/badge/Maintenance%20Level-Not%20Maintained-yellow.svg)](https://gist.github.com/cheerfulstoic/d107229326a01ff0f333a1d3476e068d) 7 | 8 | 9 | ![][unirest-logo] 10 | 11 | 12 | [Unirest](http://unirest.io) is a set of lightweight HTTP libraries available in multiple languages. 13 | 14 | ## Installing 15 | 16 | To utilize unirest for node.js install the the `npm` module: 17 | 18 | ```bash 19 | $ npm install unirest 20 | ``` 21 | 22 | After installing the `npm` package you can now start simplifying requests like so: 23 | 24 | ```js 25 | var unirest = require('unirest'); 26 | ``` 27 | 28 | ## Creating Requests 29 | 30 | You're probably wondering how by using **Unirest** makes creating requests easier. Besides automatically supporting gzip, and parsing responses, lets start with a basic working example: 31 | 32 | ```js 33 | unirest 34 | .post('http://mockbin.com/request') 35 | .headers({'Accept': 'application/json', 'Content-Type': 'application/json'}) 36 | .send({ "parameter": 23, "foo": "bar" }) 37 | .then((response) => { 38 | console.log(response.body) 39 | }) 40 | ``` 41 | 42 | ## Uploading Files 43 | 44 | Transferring file data has been simplified: 45 | 46 | ```js 47 | unirest 48 | .post('http://mockbin.com/request') 49 | .headers({'Content-Type': 'multipart/form-data'}) 50 | .field('parameter', 'value') // Form field 51 | .attach('file', '/tmp/file') // Attachment 52 | .then(function (response) { 53 | console.log(response.body) 54 | }) 55 | ``` 56 | 57 | ## Custom Entity Body 58 | 59 | ```js 60 | unirest 61 | .post('http://mockbin.com/request') 62 | .headers({'Accept': 'application/json'}) 63 | .send(Buffer.from([1,2,3])) 64 | .then(function (response) { 65 | console.log(response.body) 66 | }) 67 | ``` 68 | 69 | # Unirest 70 | 71 | A request can be initiated by invoking the appropriate method on the unirest object, then calling `.end()` to send the request. Alternatively you can send the request directly by providing a callback along with the url. 72 | 73 | ## unirest(method [, uri, headers, body, callback]) 74 | 75 | - `method` - Request type (GET, PUT, POST, etc...) 76 | - `uri` - _Optional_; When passed will return a [Request](#request) object. Otherwise returns generated function with `method` pre-defined (e.g. `unirest.get`) 77 | - `headers` (`Object`) - _Optional_; HTTP Request headers 78 | - `body` (`Mixed`) - _Optional_; HTTP Request body 79 | - `callback` (`Function`) - _Optional_; Invoked when Request has finalized with the argument [Response](#response) 80 | 81 | ## unirest\[method](url [, headers, body, callback]) 82 | 83 | - `method` - Request type, pre-defined methods, see below. 84 | - `url` - Request location. 85 | - `headers` (`Object` | `Function`) - _Optional_; When `Object` headers are passed along to the [`Request.header`](#requestheaderobject-or-field-value) method, 86 | when `Function` this argument is used as the `callback`. 87 | - `body` (`Mixed` | `Function`) - _Optional_; When `body` is not a `Function` it will be passed along to `Request.send()` method, 88 | otherwise when a `Function` it will be used as the `callback`. 89 | - `callback` (`Function`) - _Optional_; Calls end with given argument, otherwise `Request` is returned. 90 | 91 | All arguments above, with the exclusion of `url`, will accept a `Function` as the `callback`. 92 | When no `callback` is present, the [Request](#request) object will be returned. 93 | 94 | ### get 95 | 96 | Returns a [Request](#request) object with the `method` option set to `GET` 97 | 98 | ```js 99 | var Request = unirest.get('http://mockbin.com/request') 100 | ``` 101 | 102 | ### head 103 | Returns a [Request](#request) object with the `method` option set to `HEAD` 104 | 105 | ```js 106 | let Request = unirest.head('http://mockbin.com/request') 107 | ``` 108 | 109 | ### put 110 | Returns a [Request](#request) object with the `method` option set to `PUT` 111 | 112 | ```js 113 | let Request = unirest.put('http://mockbin.com/request') 114 | ``` 115 | 116 | ### post 117 | Returns a [Request](#request) object with the `method` option set to `POST` 118 | 119 | ```js 120 | let Request = unirest.post('http://mockbin.com/request') 121 | ``` 122 | 123 | ### patch 124 | 125 | Returns a [Request](#request) object with the `method` option set to `PATCH` 126 | 127 | ```js 128 | let Request = unirest.patch('http://mockbin.com/request') 129 | ``` 130 | 131 | ### delete 132 | Returns a [Request](#request) object with the `method` option set to `DELETE` 133 | 134 | ```js 135 | let Request = unirest.delete('http://mockbin.com/request') 136 | ``` 137 | 138 | ## unirest.jar() 139 | 140 | Creates a container to store multiple cookies, i.e. a cookie jar. 141 | 142 | ```js 143 | let CookieJar = unirest.jar() 144 | CookieJar.add('key=value', '/') 145 | 146 | unirest 147 | .get('http://mockbin.com/request') 148 | .jar(CookieJar) 149 | ``` 150 | 151 | ## unirest.cookie(String) 152 | 153 | Creates a cookie, see above for example. 154 | 155 | ## unirest.request 156 | 157 | `mikeal/request` library (the underlying layer of unirest) for direct use. 158 | 159 | # Request 160 | 161 | Provides simple and easy to use methods for manipulating the request prior to being sent. This object is created when a 162 | Unirest Method is invoked. This object contains methods that are chainable like other libraries such as jQuery and popular 163 | request module Superagent (which this library is modeled after slightly). 164 | 165 | **Example** 166 | 167 | ```js 168 | var Request = unirest.post('http://mockbin.com/request'); 169 | 170 | Request 171 | .header('Accept', 'application/json') 172 | .end(function (response) { 173 | ... 174 | }) 175 | ``` 176 | 177 | ## Request Methods 178 | 179 | Request Methods differ from Option Methods (See Below) in that these methods transform, or handle the data in a sugared way, where as Option Methods require a more _hands on_ approach. 180 | 181 | #### Request.auth(Object) or (user, pass, sendImmediately) 182 | 183 | Accepts either an `Object` containing `user`, `pass`, and optionally `sendImmediately`. 184 | 185 | - `user` (`String`) - Authentication Username 186 | - `pass` (`String`) - Authentication Password 187 | - `sendImmediately` (`String`) - _Optional_; Defaults to `true`; Flag to determine whether Request should send the basic authentication header along with the request. Upon being _false_, Request will retry with a _proper_ authentication header after receiving a `401` response from the server (which must contain a `WWW-Authenticate` header indicating the required authentication method) 188 | 189 | **Object** 190 | 191 | ```js 192 | Request.auth({ 193 | user: 'Nijiko', 194 | pass: 'insecure', 195 | sendImmediately: true 196 | }) 197 | ``` 198 | 199 | **Arguments** 200 | 201 | ```js 202 | Request.auth('Nijiko', 'insecure', true) 203 | ``` 204 | 205 | #### Request.header(header[, value]) 206 | 207 | **Suggested Method for setting Headers** 208 | 209 | Accepts either an `Object` containing `header-name: value` entries, 210 | or `field` and `value` arguments. Each entry is then stored in a two locations, one in the case-sensitive `Request.options.headers` and the other on a private `_headers` object that is case-insensitive for internal header lookup. 211 | 212 | - `field` (`String`) - Header name, such as `Accepts` 213 | - `value` (`String`) - Header value, such as `application/json` 214 | 215 | **Object** 216 | 217 | ```js 218 | Request.headers({ 219 | 'Accept': 'application/json', 220 | 'User-Agent': 'Unirest Node.js' 221 | }) 222 | ``` 223 | 224 | Note the usage of [`Request.headers`](#requestheaders) which is simply an alias to the `Request.header` method, you can also use [`Request.set`](#requestset) to set headers. 225 | 226 | **Arguments** 227 | 228 | ```js 229 | Request.header('Accept', 'application/json'); 230 | ``` 231 | 232 | #### Request.part(Object) 233 | 234 | **Experimental** 235 | 236 | Similiar to `Request.multipart()` except it only allows one object to be passed at a time and does the pre-processing on necessary `body` values for you. 237 | 238 | Each object is then appended to the `Request.options.multipart` array. 239 | 240 | ```js 241 | Request 242 | .part({ 243 | 'content-type': 'application/json', 244 | body: { foo: 'bar' } 245 | }) 246 | .part({ 247 | 'content-type': 'text/html', 248 | body: 'Hello World!' 249 | }) 250 | ``` 251 | 252 | #### Request.query(Object) or (String) 253 | 254 | Serializes argument passed to a querystring representation. 255 | 256 | Should `url` already contain a querystring, the representation will be appended to the `url`. 257 | 258 | ```js 259 | unirest 260 | .post('http://mockbin.com/request') 261 | .query('name=nijiko') 262 | .query({ 263 | pet: 'spot' 264 | }) 265 | .then((response) => { 266 | console.log(response.body) 267 | }) 268 | ``` 269 | 270 | #### Request.send(Object | String) 271 | 272 | Data marshalling for HTTP request body data 273 | 274 | Determines whether data mime-type is `form` or `json`. 275 | For irregular mime-types the `.type()` method is used to infer the `content-type` header. 276 | 277 | When mime-type is `application/x-www-form-urlencoded` data is appended rather than overwritten. 278 | 279 | **JSON** 280 | 281 | ```js 282 | unirest 283 | .post('http://mockbin.com/request') 284 | .type('json') 285 | .send({ 286 | foo: 'bar', 287 | hello: 3 288 | }) 289 | .then((response) => { 290 | console.log(response.body) 291 | }) 292 | ``` 293 | 294 | **FORM Encoded** 295 | 296 | ```js 297 | // Body would be: 298 | // name=nijiko&pet=turtle 299 | unirest 300 | .post('http://mockbin.com/request') 301 | .send('name=nijiko') 302 | .send('pet=spot') 303 | .then((response) => { 304 | console.log(response.body) 305 | }) 306 | ``` 307 | 308 | **HTML / Other** 309 | 310 | ```js 311 | unirest 312 | .post('http://mockbin.com/request') 313 | .set('Content-Type', 'text/html') 314 | .send('Hello World!') 315 | .then((response) => { 316 | console.log(response.body) 317 | }) 318 | ``` 319 | 320 | #### Request.type(String) 321 | 322 | Sets the header `Content-Type` through either lookup for extensions (`xml`, `png`, `json`, etc...) using `mime` or using the full value such as `application/json`. 323 | 324 | Uses [`Request.header`](#requestheaderobject-or-field-value) to set header value. 325 | 326 | ```js 327 | Request.type('application/json') // Content-Type: application/json 328 | Request.type('json') // Content-Type: application/json 329 | Request.type('html') // Content-Type: text/html 330 | … 331 | ``` 332 | 333 | ## Request Form Methods 334 | 335 | The following methods are sugar methods for attaching files, and form fields. Instead of handling files and processing them yourself Unirest can do that for you. 336 | 337 | #### Request.attach(Object) or (name, path) 338 | 339 | `Object` should consist of `name: 'path'` otherwise use `name` and `path`. 340 | 341 | - `name` (`String`) - File field name 342 | - `path` (`String` | `Object`) - File value, A `String` will be parsed based on its value. If `path` contains `http` or `https` Request will handle it as a `remote file`. If `path` does not contain `http` or `https` then unirest will assume that it is the path to a local file and attempt to find it using `path.resolve`. An `Object` is directly set, so you can do pre-processing if you want without worrying about the string value. 343 | 344 | **Object** 345 | 346 | ```js 347 | unirest 348 | .post('http://mockbin.com/request') 349 | .header('Accept', 'application/json') 350 | .field({ 351 | 'parameter': 'value' 352 | }) 353 | .attach({ 354 | 'file': 'dog.png', 355 | 'relative file': fs.createReadStream(path.join(__dirname, 'dog.png')), 356 | 'remote file': unirest.request('http://google.com/doodle.png') 357 | }) 358 | .then((response) => { 359 | console.log(response.body) 360 | }) 361 | ``` 362 | 363 | **Arguments** 364 | 365 | ```js 366 | unirest 367 | .post('http://mockbin.com/request') 368 | .header('Accept', 'application/json') 369 | .field('parameter', 'value') // Form field 370 | .attach('file', 'dog.png') // Attachment 371 | .attach('remote file', fs.createReadStream(path.join(__dirname, 'dog.png'))) // Same as above. 372 | .attach('remote file', unirest.request('http://google.com/doodle.png')) 373 | .then((response) => { 374 | console.log(response.body) 375 | }) 376 | ``` 377 | 378 | #### Request.field(Object) or (name, value) 379 | 380 | `Object` should consist of `name: 'value'` otherwise use `name` and `value` 381 | 382 | See `Request.attach` for usage. 383 | 384 | #### Request.stream() 385 | 386 | Sets `_stream` flag to use `request` streaming instead of direct `form-data` usage. 387 | This seemingly appears to only work for node servers, use streaming only if you are a hundred percent sure it will work. 388 | Tread carefully. 389 | 390 | ## Request.options 391 | 392 | The _options_ `object` is where almost all of the request settings live. Each option method sugars to a field on this object to allow for chaining and ease of use. If 393 | you have trouble with an option method and wish to directly access the _options_ object 394 | you are free to do so. 395 | 396 | This object is modeled after the `request` libraries options that are passed along through its constructor. 397 | 398 | * `url` (`String` | `Object`) - Url, or object parsed from `url.parse()` 399 | * `qs` (`Object`) - Object consisting of `querystring` values to append to `url` upon request. 400 | * `method` (`String`) - Default `GET`; HTTP Method. 401 | * `headers` (`Object`) - Default `{}`; HTTP Headers. 402 | * `body` (`String` | `Object`) - Entity body for certain requests. 403 | * `form` (`Object`) - Form data. 404 | * `auth` (`Object`) - See `Request.auth()` below. 405 | * `multipart` (`Object`) - _Experimental_; See documentation below. 406 | * `followRedirect` (`Boolean`) - Default `true`; Follow HTTP `3xx` responses as redirects. 407 | * `followAllRedirects` (`Boolean`) - Default `false`; Follow **Non**-GET HTTP `3xx` responses as redirects. 408 | * `maxRedirects` (`Number`) - Default `10`; Maximum number of redirects before aborting. 409 | * `encoding` (`String`) - Encoding to be used on `setEncoding` of response data. 410 | * `timeout` (`Number`) - Number of milliseconds to wait before aborting. 411 | * `proxy` (`String`) - See `Request.proxy()` below. 412 | * `oauth` (`Object`) - See `Request.oauth()` below. 413 | * `hawk` (`Object`) - See `Request.hawk()` below 414 | * `strictSSL` (`Boolean`) - Default `true`; See `Request.strictSSL()` below. 415 | * `secureProtocol` (`String`) - See `Request.secureProtocol()` below. 416 | * `jar` (`Boolean` | `Jar`) - See `Request.jar()` below. 417 | * `aws` (`Object`) - See `Request.aws()` below. 418 | * `httpSignature` (`Object`) - See `Request.httpSignature()` Below. 419 | * `localAddress` (`String`) - See `Request.localAddress()` Below. 420 | * `pool` (`Object`) - See `Request.pool()` Below. 421 | * `forever` (`Boolean`) - Default `undefined`; See `Request.forever()` Below 422 | 423 | ## Request Option Methods 424 | 425 | #### Request.url(String) 426 | 427 | Sets `url` location of the current request on `Request.options` to the given `String` 428 | 429 | ```js 430 | Request.url('http://mockbin.com/request'); 431 | ``` 432 | 433 | #### Request.method(String) 434 | 435 | Sets `method` value on `Request.options` to the given value. 436 | 437 | ```js 438 | Request.method('HEAD'); 439 | ``` 440 | 441 | #### Request.form(Object) 442 | 443 | Sets `form` object on `Request.options` to the given object. 444 | 445 | When used `body` is set to the object passed as a `querystring` representation and the `Content-Type` header to `application/x-www-form-urlencoded; charset=utf-8` 446 | 447 | ```js 448 | Request.form({ 449 | key: 'value' 450 | }) 451 | ``` 452 | 453 | #### Request.multipart(Array) 454 | 455 | **Experimental** 456 | 457 | Sets `multipart` array containing multipart-form objects on `Request.options` to be sent along with the Request. 458 | 459 | Each objects property with the exclusion of `body` is treated as a header value. Each `body` value must be pre-processed if necessary when using this method. 460 | 461 | ```js 462 | Request.multipart([{ 463 | 'content-type': 'application/json', 464 | body: JSON.stringify({ 465 | foo: 'bar' 466 | }) 467 | }, { 468 | 'content-type': 'text/html', 469 | body: 'Hello World!' 470 | }]) 471 | ``` 472 | 473 | #### Request.maxRedirects(Number) 474 | 475 | Sets `maxRedirects`, the number of redirects the current Request will follow, on `Request.options` based on the given value. 476 | 477 | ```js 478 | Request.maxRedirects(6) 479 | ``` 480 | 481 | #### Request.followRedirect(Boolean) 482 | 483 | Sets `followRedirect` flag on `Request.options` for whether the current Request should follow HTTP redirects based on the given value. 484 | 485 | ```js 486 | Request.followRedirect(true); 487 | ``` 488 | 489 | #### Request.timeout(Number) 490 | 491 | Sets `timeout`, number of milliseconds Request should wait for a response before aborting, on `Request.options` based on the given value. 492 | 493 | ```js 494 | Request.timeout(2000) 495 | ``` 496 | 497 | #### Request.encoding(String) 498 | 499 | Sets `encoding`, encoding to be used on setEncoding of response data if set to null, the body is returned as a Buffer, on `Request.options` based on given value. 500 | 501 | ```js 502 | Request.encoding('utf-8') 503 | ``` 504 | 505 | #### Request.strictSSL(Boolean) 506 | 507 | Sets `strictSSL` flag to require that SSL certificates be valid on `Request.options` based on given value. 508 | 509 | ```js 510 | Request.strictSSL(true) 511 | ``` 512 | 513 | #### Request.httpSignature(Object) 514 | 515 | Sets `httpSignature` 516 | 517 | #### Request.proxy(String) 518 | 519 | Sets `proxy`, HTTP Proxy to be set on `Request.options` based on value. 520 | 521 | ```js 522 | Request.proxy('http://localproxy.com') 523 | ``` 524 | 525 | #### Request.secureProtocol(String) 526 | 527 | Sets the secure protocol to use: 528 | 529 | ```js 530 | Request.secureProtocol('SSLv2_method') 531 | // or 532 | Request.secureProtocol('SSLv3_client_method') 533 | ``` 534 | 535 | See [openssl.org](https://www.openssl.org/docs/ssl/SSL_CTX_new.html) for all possible values. 536 | 537 | #### Request.aws(Object) 538 | 539 | Sets `aws`, AWS Signing Credentials, on `Request.options` 540 | 541 | ```js 542 | Request.aws({ 543 | key: 'AWS_S3_KEY', 544 | secret: 'AWS_S3_SECRET', 545 | bucket: 'BUCKET NAME' 546 | }) 547 | ``` 548 | 549 | #### Request.oauth(Object) 550 | 551 | Sets `oauth`, list of oauth credentials, on `Request.options` based on given object. 552 | 553 | ```js 554 | unirest 555 | .get('https://api.twitter.com/oauth/request_token') 556 | .oauth({ 557 | callback: 'http://mysite.com/callback/', 558 | consumer_key: 'CONSUMER_KEY', 559 | consumer_secret: 'CONSUMER_SECRET' 560 | }) 561 | .then(response => { 562 | let access_token = response.body 563 | 564 | return unirest 565 | .post('https://api.twitter.com/oauth/access_token') 566 | .oauth({ 567 | consumer_key: 'CONSUMER_KEY', 568 | consumer_secret: 'CONSUMER_SECRET', 569 | token: access_token.oauth_token, 570 | verifier: token: access_token.oauth_verifier 571 | }) 572 | }) 573 | .then((response) => { 574 | var token = response.body 575 | 576 | return unirest 577 | .get('https://api.twitter.com/1/users/show.json') 578 | .oauth({ 579 | consumer_key: 'CONSUMER_KEY', 580 | consumer_secret: 'CONSUMER_SECRET', 581 | token: token.oauth_token, 582 | token_secret: token.oauth_token_secret 583 | }) 584 | .query({ 585 | screen_name: token.screen_name, 586 | user_id: token.user_id 587 | }) 588 | }) 589 | .then((response) => { 590 | console.log(response.body) 591 | }) 592 | ``` 593 | 594 | #### Request.hawk(Object) 595 | 596 | Sets `hawk` object on `Request.options` to the given object. 597 | 598 | Hawk requires a field `credentials` as seen in their [documentation](https://github.com/hueniverse/hawk#usage-example), and below. 599 | 600 | ```js 601 | Request.hawk({ 602 | credentials: { 603 | key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn', 604 | algorithm: 'sha256', 605 | user: 'Steve' 606 | } 607 | }) 608 | ``` 609 | 610 | #### Request.localAddress(String) 611 | 612 | Sets `localAddress`, local interface to bind for network connections, on `Request.options` 613 | 614 | ```js 615 | Request.localAddress('127.0.0.1') 616 | Request.localAddress('1.2.3.4') 617 | ``` 618 | 619 | #### Request.jar(Boolean) or Request.jar(Jar) 620 | 621 | Sets `jar`, cookie container, on `Request.options`. When set to `true` it stores cookies for future usage. 622 | 623 | See `unirest.jar` for more information on how to use `Jar` argument. 624 | 625 | #### Request.pool(Object) 626 | 627 | Sets `pool` object on `Request.options` to the given object. 628 | 629 | A maxSockets property can also be provided on the pool object to set the max number of sockets for all agents created. 630 | 631 | Note that if you are sending multiple requests in a loop and creating multiple new pool objects, maxSockets will not work as intended. To work around this, create the pool object with the maxSockets property outside of the loop. 632 | 633 | ```js 634 | poolOption = { maxSockets: 100 } 635 | 636 | Request.pool poolOption 637 | ``` 638 | 639 | #### Request.forever(Boolean) 640 | 641 | Sets `forever` flag to use `forever-agent` module. When set to `true`, default http agent will be replaced by `forever-agent`, which keeps socket connections alive between keep-alive requests. 642 | 643 | ```js 644 | Request.forever(true); 645 | ``` 646 | 647 | #### Request.then(Function callback) 648 | 649 | Promise polyfill method. Wraps `Request.end` in a Promise and will resolve or 650 | reject based on the result of the request. 651 | 652 | ```js 653 | unirest 654 | .get('http://mockbin.com/request') 655 | .then((response) => { 656 | console.log(response) 657 | }) 658 | .catch(err => { 659 | console.log(err) 660 | }) 661 | ``` 662 | 663 | #### Request.end(Function callback) 664 | 665 | Sends HTTP Request and awaits Response finalization. Request compression and Response decompression occurs here. 666 | Upon HTTP Response post-processing occurs and invokes `callback` with a single argument, the `[Response](#response)` object. 667 | 668 | ```js 669 | unirest 670 | .get('http://mockbin.com/request') 671 | .end((response) => { 672 | console.log(response) 673 | }) 674 | ``` 675 | 676 | ## Request Aliases 677 | 678 | #### Request.set 679 | 680 | **Alias** for [`Request.header()`](#requestheaderobject-or-field-value) 681 | 682 | #### Request.headers 683 | 684 | **Alias** for [`Request.header()`](#requestheaderobject-or-field-value) 685 | 686 | #### Request.redirects 687 | 688 | **Alias** for [`Request.maxRedirects()`](#requestmaxredirectsnumber) 689 | 690 | #### Request.redirect 691 | 692 | **Alias** for [`Request.followRedirect()`](#requestfollowredirectboolean) 693 | 694 | #### Request.ssl 695 | 696 | **Alias** for [`Request.strictSSL()`](#requeststrictsslboolean) 697 | 698 | #### Request.ip 699 | 700 | **Alias** for [`Request.localAddress()`](#requestlocaladdressstring) 701 | 702 | #### Request.complete 703 | 704 | **Alias** for [`Request.end()`](#requestlocaladdressstring) 705 | 706 | #### Request.as.json 707 | 708 | **Alias** for [`Request.end()`](#requestendfunction-callback) 709 | 710 | #### Request.as.binary 711 | 712 | **Alias** for [`Request.end()`](#requestendfunction-callback) 713 | 714 | #### Request.as.string 715 | 716 | **Alias** for [`Request.end()`](#requestendfunction-callback) 717 | 718 | # Response 719 | 720 | Upon ending a request, and receiving a Response the object that is returned contains a number of helpful properties to ease coding pains. 721 | 722 | ## General 723 | 724 | 725 | - `body` (`Mixed`) - Processed body data 726 | - `raw_body` (`Mixed`) - Unprocessed body data 727 | - `headers` (`Object`) - Header details 728 | - `cookies` (`Object`) - Cookies from `set-cookies`, and `cookie` headers. 729 | - `httpVersion` (`String`) - Server http version. (e.g. 1.1) 730 | - `httpVersionMajor` (`Number`) - Major number (e.g. 1) 731 | - `httpVersionMinor` (`Number`) - Minor number (e.g. 1) 732 | - `url` (`String`) - Dependant on input, can be empty. 733 | - `domain` (`String` | `null`) - Dependant on input, can be empty. 734 | - `method` (`String` | `null`) - Method used, dependant on input. 735 | - `client` (`Object`) - Client Object. Detailed information regarding the Connection and Byte throughput. 736 | - `connection` (`Object`) - Client Object. Specific connection object, useful for events such as errors. **Advanced** 737 | - `socket` (`Object`) Client Object. Socket specific object and information. Most throughput is same across all three client objects. 738 | - `request` (`Object`) - Initial request object. 739 | - `setEncoding` (`Function`) - Set encoding type. 740 | 741 | ## Status Information 742 | 743 | - `code` (`Number`) - Status Code, i.e. `200` 744 | - `status` (`Number`) - Status Code, same as above. 745 | - `statusType` (`Number`) - Status Code Range Type 746 | - `1` - Info 747 | - `2` - Ok 748 | - `3` - Miscellaneous 749 | - `4` - Client Error 750 | - `5` - Server Error 751 | - `info` (`Boolean`) - Status Range Info? 752 | - `ok` (`Boolean`) - Status Range Ok? 753 | - `clientError` (`Boolean`) - Status Range Client Error? 754 | - `serverError` (`Boolean`) - Status Range Server Error? 755 | - `accepted` (`Boolean`) - Status Code `202`? 756 | - `noContent` (`Boolean`) - Status Code `204` or `1223`? 757 | - `badRequest` (`Boolean`) - Status Code `400`? 758 | - `unauthorized` (`Boolean`) - Status Code `401`? 759 | - `notAcceptable` (`Boolean`) - Status Code `406`? 760 | - `notFound` (`Boolean`) - Status Code `404`? 761 | - `forbidden` (`Boolean`) - Status Code `403`? 762 | - `error` (`Boolean` | `Object`) - Dependant on status code range. 763 | 764 | ## response.cookie(name) 765 | 766 | Sugar method for retrieving a cookie from the `response.cookies` object. 767 | 768 | 769 | ```js 770 | var CookieJar = unirest.jar(); 771 | CookieJar.add(unirest.cookie('another cookie=23')); 772 | 773 | unirest.get('http://google.com').jar(CookieJar).end(function (response) { 774 | // Except google trims the value passed :/ 775 | console.log(response.cookie('another cookie')); 776 | }); 777 | ``` 778 | 779 | ## Contributing 780 | 781 | 1. Fork it 782 | 2. Create your feature branch (`git checkout -b my-new-feature`) 783 | 3. Commit your changes (`git commit -am 'Add some feature'`) 784 | 4. Push to the branch (`git push origin my-new-feature`) 785 | 5. Create new Pull Request 786 | 787 | 788 | ---- 789 | 790 | Made with ♥ from the [Kong](https://www.konghq.com/) team 791 | 792 | [unirest-logo]: http://cl.ly/image/2P373Y090s2O/Image%202015-10-12%20at%209.48.06%20PM.png 793 | 794 | 795 | [license-url]: https://github.com/Kong/unirest-nodejs/blob/master/LICENSE 796 | 797 | [gitter-url]: https://gitter.im/Kong/unirest-nodejs 798 | [gitter-image]: https://img.shields.io/badge/Gitter-Join%20Chat-blue.svg?style=flat 799 | 800 | [travis-url]: https://travis-ci.org/Kong/unirest-nodejs 801 | [travis-image]: https://img.shields.io/travis/Kong/unirest-nodejs.svg?style=flat 802 | 803 | [npm-url]: https://www.npmjs.com/package/unirest 804 | [npm-license]: https://img.shields.io/npm/l/unirest.svg?style=flat 805 | [npm-version]: https://badge.fury.io/js/unirest.svg 806 | [npm-downloads]: https://img.shields.io/npm/dm/unirest.svg?style=flat 807 | 808 | [codeclimate-url]: https://codeclimate.com/github/Kong/unirest-nodejs 809 | [codeclimate-quality]: https://img.shields.io/codeclimate/github/Kong/unirest-nodejs.svg?style=flat 810 | [codeclimate-coverage]: https://img.shields.io/codeclimate/coverage/github/Kong/unirest-nodejs.svg?style=flat 811 | 812 | [david-url]: https://david-dm.org/Kong/unirest-nodejs 813 | [david-image]: https://img.shields.io/david/Kong/unirest-nodejs.svg?style=flat 814 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Unirest for Node.js 3 | * 4 | * @author Nijko Yonskai 5 | * @copyright 2013-2015 6 | * @license MIT 7 | */ 8 | 9 | /** 10 | * Module Dependencies 11 | */ 12 | 13 | var StringDecoder = require('string_decoder').StringDecoder 14 | var QueryString = require('querystring') 15 | var FormData = require('form-data') 16 | var Stream = require('stream') 17 | var mime = require('mime') 18 | var zlib = require('zlib') 19 | var path = require('path') 20 | var URL = require('url') 21 | var fs = require('fs') 22 | 23 | /** 24 | * Define form mime type 25 | */ 26 | mime.define({ 27 | 'application/x-www-form-urlencoded': ['form', 'urlencoded', 'form-data'] 28 | }) 29 | 30 | /** 31 | * Initialize our Rest Container 32 | * 33 | * @type {Object} 34 | */ 35 | var Unirest = function (method, uri, headers, body, callback) { 36 | var unirest = function (uri, headers, body, callback) { 37 | var $this = { 38 | /** 39 | * Stream Multipart form-data request 40 | * 41 | * @type {Boolean} 42 | */ 43 | _stream: false, 44 | 45 | /** 46 | * Container to hold multipart form data for processing upon request. 47 | * 48 | * @type {Array} 49 | * @private 50 | */ 51 | _multipart: [], 52 | 53 | /** 54 | * Container to hold form data for processing upon request. 55 | * 56 | * @type {Array} 57 | * @private 58 | */ 59 | _form: [], 60 | 61 | /** 62 | * Request option container for details about the request. 63 | * 64 | * @type {Object} 65 | */ 66 | options: { 67 | /** 68 | * Url obtained from request method arguments. 69 | * 70 | * @type {String} 71 | */ 72 | url: uri, 73 | 74 | /** 75 | * Method obtained from request method arguments. 76 | * 77 | * @type {String} 78 | */ 79 | method: method, 80 | 81 | /** 82 | * List of headers with case-sensitive fields. 83 | * 84 | * @type {Object} 85 | */ 86 | headers: {} 87 | }, 88 | 89 | hasHeader: function (name) { 90 | var headers 91 | var lowercaseHeaders 92 | 93 | name = name.toLowerCase() 94 | headers = Object.keys($this.options.headers) 95 | lowercaseHeaders = headers.map(function (header) { 96 | return header.toLowerCase() 97 | }) 98 | 99 | for (var i = 0; i < lowercaseHeaders.length; i++) { 100 | if (lowercaseHeaders[i] === name) { 101 | return headers[i] 102 | } 103 | } 104 | 105 | return false 106 | }, 107 | 108 | /** 109 | * Turn on multipart-form streaming 110 | * 111 | * @return {Object} 112 | */ 113 | stream: function () { 114 | $this._stream = true 115 | return this 116 | }, 117 | 118 | /** 119 | * Attaches a field to the multipart-form request, with pre-processing. 120 | * 121 | * @param {String} name 122 | * @param {String} value 123 | * @return {Object} 124 | */ 125 | field: function (name, value, options) { 126 | return handleField(name, value, options) 127 | }, 128 | 129 | /** 130 | * Attaches a file to the multipart-form request. 131 | * 132 | * @param {String} name 133 | * @param {String|Object} path 134 | * @return {Object} 135 | */ 136 | attach: function (name, path, options) { 137 | options = options || {} 138 | options.attachment = true 139 | return handleField(name, path, options) 140 | }, 141 | 142 | /** 143 | * Attaches field to the multipart-form request, with no pre-processing. 144 | * 145 | * @param {String} name 146 | * @param {String|Object} path 147 | * @param {Object} options 148 | * @return {Object} 149 | */ 150 | rawField: function (name, value, options) { 151 | $this._multipart.push({ 152 | name: name, 153 | value: value, 154 | options: options, 155 | attachment: options.attachment || false 156 | }) 157 | }, 158 | 159 | /** 160 | * Basic Header Authentication Method 161 | * 162 | * Supports user being an Object to reflect Request 163 | * Supports user, password to reflect SuperAgent 164 | * 165 | * @param {String|Object} user 166 | * @param {String} password 167 | * @param {Boolean} sendImmediately 168 | * @return {Object} 169 | */ 170 | auth: function (user, password, sendImmediately) { 171 | $this.options.auth = (is(user).a(Object)) ? user : { 172 | user: user, 173 | password: password, 174 | sendImmediately: sendImmediately 175 | } 176 | 177 | return $this 178 | }, 179 | 180 | /** 181 | * Sets header field to value 182 | * 183 | * @param {String} field Header field 184 | * @param {String} value Header field value 185 | * @return {Object} 186 | */ 187 | header: function (field, value) { 188 | if (is(field).a(Object)) { 189 | for (var key in field) { 190 | if (Object.prototype.hasOwnProperty.call(field, key)) { 191 | $this.header(key, field[key]) 192 | } 193 | } 194 | 195 | return $this 196 | } 197 | 198 | var existingHeaderName = $this.hasHeader(field) 199 | $this.options.headers[existingHeaderName || field] = value 200 | 201 | return $this 202 | }, 203 | 204 | /** 205 | * Serialize value as querystring representation, and append or set on `Request.options.url` 206 | * 207 | * @param {String|Object} value 208 | * @return {Object} 209 | */ 210 | query: function (value) { 211 | if (is(value).a(Object)) value = Unirest.serializers.form(value) 212 | if (!value.length) return $this 213 | $this.options.url += (does($this.options.url).contain('?') ? '&' : '?') + value 214 | return $this 215 | }, 216 | 217 | /** 218 | * Set _content-type_ header with type passed through `mime.getType()` when necessary. 219 | * 220 | * @param {String} type 221 | * @return {Object} 222 | */ 223 | type: function (type) { 224 | $this.header('Content-Type', does(type).contain('/') 225 | ? type 226 | : mime.getType(type)) 227 | return $this 228 | }, 229 | 230 | /** 231 | * Data marshalling for HTTP request body data 232 | * 233 | * Determines whether type is `form` or `json`. 234 | * For irregular mime-types the `.type()` method is used to infer the `content-type` header. 235 | * 236 | * When mime-type is `application/x-www-form-urlencoded` data is appended rather than overwritten. 237 | * 238 | * @param {Mixed} data 239 | * @return {Object} 240 | */ 241 | send: function (data) { 242 | var type = $this.options.headers[$this.hasHeader('content-type')] 243 | 244 | if ((is(data).a(Object) || is(data).a(Array)) && !Buffer.isBuffer(data)) { 245 | if (!type) { 246 | $this.type('form') 247 | type = $this.options.headers[$this.hasHeader('content-type')] 248 | $this.options.body = Unirest.serializers.form(data) 249 | } else if (~type.indexOf('json')) { 250 | $this.options.json = true 251 | 252 | if ($this.options.body && is($this.options.body).a(Object)) { 253 | for (var key in data) { 254 | if (Object.prototype.hasOwnProperty.call(data, key)) { 255 | $this.options.body[key] = data[key] 256 | } 257 | } 258 | } else { 259 | $this.options.body = data 260 | } 261 | } else { 262 | $this.options.body = Unirest.Request.serialize(data, type) 263 | } 264 | } else if (is(data).a(String)) { 265 | if (!type) { 266 | $this.type('form') 267 | type = $this.options.headers[$this.hasHeader('content-type')] 268 | } 269 | 270 | if (type === 'application/x-www-form-urlencoded') { 271 | $this.options.body = $this.options.body 272 | ? $this.options.body + '&' + data 273 | : data 274 | } else { 275 | $this.options.body = ($this.options.body || '') + data 276 | } 277 | } else { 278 | $this.options.body = data 279 | } 280 | 281 | return $this 282 | }, 283 | 284 | /** 285 | * Takes multipart options and places them on `options.multipart` array. 286 | * Transforms body when an `Object` or _content-type_ is present. 287 | * 288 | * Example: 289 | * 290 | * Unirest.get('http://google.com').part({ 291 | * 'content-type': 'application/json', 292 | * body: { 293 | * phrase: 'Hello' 294 | * } 295 | * }).part({ 296 | * 'content-type': 'application/json', 297 | * body: { 298 | * phrase: 'World' 299 | * } 300 | * }).end(function (response) {}) 301 | * 302 | * @param {Object|String} options When an Object, headers should be placed directly on the object, 303 | * not under a child property. 304 | * @return {Object} 305 | */ 306 | part: function (options) { 307 | if (!$this._multipart) { 308 | $this.options.multipart = [] 309 | } 310 | 311 | if (is(options).a(Object)) { 312 | if (options['content-type']) { 313 | var type = Unirest.type(options['content-type'], true) 314 | if (type) options.body = Unirest.Response.parse(options.body) 315 | } else { 316 | if (is(options.body).a(Object)) { 317 | options.body = Unirest.serializers.json(options.body) 318 | } 319 | } 320 | 321 | $this.options.multipart.push(options) 322 | } else { 323 | $this.options.multipart.push({ 324 | body: options 325 | }) 326 | } 327 | 328 | return $this 329 | }, 330 | 331 | /** 332 | * Instructs the Request to be retried if specified error status codes (4xx, 5xx, ETIMEDOUT) are returned. 333 | * Retries are delayed with an exponential backoff. 334 | * 335 | * @param {(err: Error) => boolean} [callback] - Invoked on response error. Return false to stop next request. 336 | * @param {Object} [options] - Optional retry configuration to override defaults. 337 | * @param {number} [options.attempts=3] - The number of retry attempts. 338 | * @param {number} [options.delayInMs=250] - The delay in milliseconds (delayInMs *= delayMulti) 339 | * @param {number} [options.delayMulti=2] - The multiplier of delayInMs after each attempt. 340 | * @param {Array} [options.statusCodes=["ETIMEDOUT", "5xx"]] - The status codes to retry on. 341 | * @return {Object} 342 | */ 343 | retry: function (callback, options) { 344 | 345 | $this.options.retry = { 346 | callback: typeof callback === "function" ? callback : null, 347 | attempts: options && +options.attempts || 3, 348 | delayInMs: options && +options.delayInMs || 250, 349 | delayMulti: options && +options.delayMulti || 2, 350 | statusCodes: (options && options.statusCodes || ["ETIMEDOUT", "5xx"]).slice(0) 351 | }; 352 | 353 | return $this 354 | }, 355 | 356 | /** 357 | * Proxies the call to end. This adds support for using promises as well as async/await. 358 | * 359 | * @param {Function} callback 360 | * @return {Promise} 361 | **/ 362 | then: function (callback) { 363 | return new Promise((resolve, reject) => { 364 | this.end(result => { 365 | try { 366 | resolve(callback(result)) 367 | } catch (err) { 368 | reject(err) 369 | } 370 | }) 371 | }) 372 | }, 373 | 374 | /** 375 | * Sends HTTP Request and awaits Response finalization. Request compression and Response decompression occurs here. 376 | * Upon HTTP Response post-processing occurs and invokes `callback` with a single argument, the `[Response](#response)` object. 377 | * 378 | * @param {Function} callback 379 | * @return {Object} 380 | */ 381 | end: function (callback) { 382 | var self = this 383 | var Request 384 | var header 385 | var parts 386 | var form 387 | 388 | function handleRetriableRequestResponse (result) { 389 | 390 | // If retries is not defined or all attempts tried, return true to invoke end's callback. 391 | if ($this.options.retry === undefined || $this.options.retry.attempts === 0) { 392 | return true 393 | } 394 | 395 | // If status code is not listed, abort with return true to invoke end's callback. 396 | var isStatusCodeDefined = (function (code, codes) { 397 | 398 | if (codes.indexOf(code) !== -1) { 399 | return true 400 | } 401 | 402 | return codes.reduce(function (p, c) { 403 | return p || String(code).split("").every(function (ch, i) { 404 | return ch === "x" || ch === c[i] 405 | }) 406 | }, false) 407 | 408 | }(result.code || result.error && result.error.code, $this.options.retry.statusCodes)) 409 | 410 | if (!isStatusCodeDefined) { 411 | return true 412 | } 413 | 414 | if ($this.options.retry.callback) { 415 | var isContinue = $this.options.retry.callback(result) 416 | // If retry callback returns false, stop retries and invoke end's callback. 417 | if (isContinue === false) { 418 | return true; 419 | } 420 | } 421 | 422 | setTimeout(function () { 423 | self.end(callback) 424 | }, $this.options.retry.delayInMs) 425 | 426 | $this.options.retry.attempts-- 427 | $this.options.retry.delayInMs *= $this.options.retry.delayMulti 428 | 429 | // Return false to not invoke end's callback. 430 | return false 431 | } 432 | 433 | function handleRequestResponse (error, response, body) { 434 | var result = {} 435 | var status 436 | var data 437 | var type 438 | 439 | // Handle pure error 440 | if (error && !response) { 441 | result.error = error 442 | 443 | if (handleRetriableRequestResponse(result) && callback) { 444 | callback(result) 445 | } 446 | 447 | return 448 | } 449 | 450 | // Handle No Response... 451 | // This is weird. 452 | if (!response) { 453 | console.log('This is odd, report this action / request to: http://github.com/mashape/unirest-nodejs') 454 | 455 | result.error = { 456 | message: 'No response found.' 457 | } 458 | 459 | if (handleRetriableRequestResponse(result) && callback) { 460 | callback(result) 461 | } 462 | 463 | return 464 | } 465 | 466 | // Create response reference 467 | result = response 468 | 469 | // Create response status reference 470 | status = response.statusCode 471 | 472 | // Normalize MSIE response to HTTP 204 473 | status = (status === 1223 ? 204 : status) 474 | 475 | // Obtain status range typecode (1, 2, 3, 4, 5, etc.) 476 | type = status / 100 | 0 477 | 478 | // Generate sugar helper properties for status information 479 | result.code = status 480 | result.status = status 481 | result.statusType = type 482 | result.info = type === 1 483 | result.ok = type === 2 484 | result.clientError = type === 4 485 | result.serverError = type === 5 486 | result.error = (type === 4 || type === 5) ? (function generateErrorMessage () { 487 | var msg = 'got ' + result.status + ' response' 488 | var err = new Error(msg) 489 | err.status = result.status 490 | return err 491 | })() : false 492 | 493 | // Iterate over Response Status Codes and generate more sugar 494 | for (var name in Unirest.Response.statusCodes) { 495 | result[name] = Unirest.Response.statusCodes[name] === status 496 | } 497 | 498 | // Cookie Holder 499 | result.cookies = {} 500 | 501 | // Cookie Sugar Method 502 | result.cookie = function (name) { 503 | return result.cookies[name] 504 | } 505 | 506 | function setCookie (cookie) { 507 | var crumbs = Unirest.trim(cookie).split('=') 508 | var key = Unirest.trim(crumbs[0]) 509 | var value = Unirest.trim(crumbs.slice(1).join('=')) 510 | 511 | if (crumbs[0] && crumbs[0] !== '') { 512 | result.cookies[key] = value === '' ? true : value 513 | } 514 | } 515 | 516 | if (response.cookies && is(response.cookies).a(Object) && Object.keys(response.cookies).length > 0) { 517 | result.cookies = response.cookies 518 | } else { 519 | // Handle cookies to be set 520 | var cookies = response.headers['set-cookie'] 521 | if (cookies && is(cookies).a(Array)) { 522 | for (var index = 0; index < cookies.length; index++) { 523 | var entry = cookies[index] 524 | 525 | if (is(entry).a(String) && does(entry).contain(';')) { 526 | entry.split(';').forEach(setCookie) 527 | } 528 | } 529 | } 530 | 531 | // Handle cookies that have been set 532 | cookies = response.headers.cookie 533 | if (cookies && is(cookies).a(String)) { 534 | cookies.split(';').forEach(setCookie) 535 | } 536 | } 537 | 538 | // Obtain response body 539 | body = body || response.body 540 | result.raw_body = body 541 | result.headers = response.headers 542 | 543 | // Handle Response Body 544 | if (body) { 545 | type = Unirest.type(result.headers['content-type'], true) 546 | if (type) data = Unirest.Response.parse(body, type) 547 | else data = body 548 | } 549 | 550 | result.body = data 551 | 552 | ;(handleRetriableRequestResponse(result)) && (callback) && callback(result) 553 | } 554 | 555 | function handleGZIPResponse (response) { 556 | if (/^(deflate|gzip)$/.test(response.headers['content-encoding'])) { 557 | var unzip = zlib.createUnzip() 558 | var stream = new Stream() 559 | var _on = response.on 560 | var decoder 561 | 562 | // Keeping node happy 563 | stream.req = response.req 564 | 565 | // Make sure we emit prior to processing 566 | unzip.on('error', function (error) { 567 | // Catch the parser error when there is no content 568 | if (error.errno === zlib.Z_BUF_ERROR || error.errno === zlib.Z_DATA_ERROR) { 569 | stream.emit('end') 570 | return 571 | } 572 | 573 | stream.emit('error', error) 574 | }) 575 | 576 | // Start the processing 577 | response.pipe(unzip) 578 | 579 | // Ensure encoding is captured 580 | response.setEncoding = function (type) { 581 | decoder = new StringDecoder(type) 582 | } 583 | 584 | // Capture decompression and decode with captured encoding 585 | unzip.on('data', function (buffer) { 586 | if (!decoder) return stream.emit('data', buffer) 587 | var string = decoder.write(buffer) 588 | if (string.length) stream.emit('data', string) 589 | }) 590 | 591 | // Emit yoself 592 | unzip.on('end', function () { 593 | stream.emit('end') 594 | }) 595 | 596 | response.on = function (type, next) { 597 | if (type === 'data' || type === 'end') { 598 | stream.on(type, next) 599 | } else if (type === 'error') { 600 | _on.call(response, type, next) 601 | } else { 602 | _on.call(response, type, next) 603 | } 604 | } 605 | } 606 | } 607 | 608 | function handleFormData (form) { 609 | for (var i = 0; i < $this._multipart.length; i++) { 610 | var item = $this._multipart[i] 611 | 612 | if (item.attachment && is(item.value).a(String)) { 613 | if (does(item.value).contain('http://') || does(item.value).contain('https://')) { 614 | item.value = Unirest.request(item.value) 615 | } else { 616 | item.value = fs.createReadStream(path.resolve(item.value)) 617 | } 618 | } 619 | 620 | form.append(item.name, item.value, item.options) 621 | } 622 | 623 | return form 624 | } 625 | 626 | if ($this._multipart.length && !$this._stream) { 627 | header = $this.options.headers[$this.hasHeader('content-type')] 628 | parts = URL.parse($this.options.url) 629 | form = new FormData() 630 | 631 | if (header) { 632 | $this.options.headers['content-type'] = header.split(';')[0] + '; boundary=' + form.getBoundary() 633 | } else { 634 | $this.options.headers['content-type'] = 'multipart/form-data; boundary=' + form.getBoundary() 635 | } 636 | 637 | function authn(auth) { 638 | if (!auth) return null; 639 | if (typeof auth === 'string') return auth; 640 | if (auth.user && auth.pass) return auth.user + ':' + auth.pass; 641 | return auth; 642 | } 643 | 644 | return handleFormData(form).submit({ 645 | protocol: parts.protocol, 646 | port: parts.port, 647 | // Formdata doesn't expect port to be included with host 648 | // so we use hostname rather than host 649 | host: parts.hostname, 650 | path: parts.path, 651 | method: $this.options.method, 652 | headers: $this.options.headers, 653 | auth: authn($this.options.auth || parts.auth) 654 | }, function (error, response) { 655 | var decoder = new StringDecoder('utf8') 656 | 657 | if (error) { 658 | return handleRequestResponse(error, response) 659 | } 660 | 661 | if (!response.body) { 662 | response.body = '' 663 | } 664 | 665 | // Node 10+ 666 | response.resume() 667 | 668 | // GZIP, Feel me? 669 | handleGZIPResponse(response) 670 | 671 | // Fallback 672 | response.on('data', function (chunk) { 673 | if (typeof chunk === 'string') response.body += chunk 674 | else response.body += decoder.write(chunk) 675 | }) 676 | 677 | // After all, we end up here 678 | response.on('end', function () { 679 | return handleRequestResponse(error, response) 680 | }) 681 | }) 682 | } 683 | 684 | Request = Unirest.request($this.options, handleRequestResponse) 685 | Request.on('response', handleGZIPResponse) 686 | 687 | if ($this._multipart.length && $this._stream) { 688 | handleFormData(Request.form()) 689 | } 690 | 691 | return Request 692 | } 693 | } 694 | 695 | /** 696 | * Alias for _.header_ 697 | * @type {Function} 698 | */ 699 | $this.headers = $this.header 700 | 701 | /** 702 | * Alias for _.header_ 703 | * 704 | * @type {Function} 705 | */ 706 | $this.set = $this.header 707 | 708 | /** 709 | * Alias for _.end_ 710 | * 711 | * @type {Function} 712 | */ 713 | $this.complete = $this.end 714 | 715 | /** 716 | * Aliases for _.end_ 717 | * 718 | * @type {Object} 719 | */ 720 | 721 | $this.as = { 722 | json: $this.end, 723 | binary: $this.end, 724 | string: $this.end 725 | } 726 | 727 | /** 728 | * Handles Multipart Field Processing 729 | * 730 | * @param {String} name 731 | * @param {Mixed} value 732 | * @param {Object} options 733 | */ 734 | function handleField (name, value, options) { 735 | var serialized 736 | var length 737 | var key 738 | var i 739 | 740 | options = options || { attachment: false } 741 | 742 | if (is(name).a(Object)) { 743 | for (key in name) { 744 | if (Object.prototype.hasOwnProperty.call(name, key)) { 745 | handleField(key, name[key], options) 746 | } 747 | } 748 | } else { 749 | if (is(value).a(Array)) { 750 | for (i = 0, length = value.length; i < length; i++) { 751 | serialized = handleFieldValue(value[i]) 752 | if (serialized) { 753 | $this.rawField(name, serialized, options) 754 | } 755 | } 756 | } else if (value != null) { 757 | $this.rawField(name, handleFieldValue(value), options) 758 | } 759 | } 760 | 761 | return $this 762 | } 763 | 764 | /** 765 | * Handles Multipart Value Processing 766 | * 767 | * @param {Mixed} value 768 | */ 769 | function handleFieldValue (value) { 770 | if (!(value instanceof Buffer || typeof value === 'string')) { 771 | if (is(value).a(Object)) { 772 | if (value instanceof fs.FileReadStream) { 773 | return value 774 | } else { 775 | return Unirest.serializers.json(value) 776 | } 777 | } else { 778 | return value.toString() 779 | } 780 | } else return value 781 | } 782 | 783 | function setupOption (name, ref) { 784 | $this[name] = function (arg) { 785 | $this.options[ref || name] = arg 786 | return $this 787 | } 788 | } 789 | 790 | // Iterates over a list of option methods to generate the chaining 791 | // style of use you see in Superagent and jQuery. 792 | for (var x in Unirest.enum.options) { 793 | if (Object.prototype.hasOwnProperty.call(Unirest.enum.options, x)) { 794 | var option = Unirest.enum.options[x] 795 | var reference = null 796 | 797 | if (option.indexOf(':') > -1) { 798 | option = option.split(':') 799 | reference = option[1] 800 | option = option[0] 801 | } 802 | 803 | setupOption(option, reference) 804 | } 805 | } 806 | 807 | if (headers && typeof headers === 'function') { 808 | callback = headers 809 | headers = null 810 | } else if (body && typeof body === 'function') { 811 | callback = body 812 | body = null 813 | } 814 | 815 | if (headers) $this.set(headers) 816 | if (body) $this.send(body) 817 | 818 | return callback ? $this.end(callback) : $this 819 | } 820 | 821 | return uri ? unirest(uri, headers, body, callback) : unirest 822 | } 823 | 824 | /** 825 | * Expose the underlying layer. 826 | */ 827 | Unirest.request = require('request') 828 | Unirest.cookie = Unirest.request.cookie 829 | Unirest.pipe = Unirest.request.pipe 830 | 831 | /** 832 | * Mime-type lookup / parser. 833 | * 834 | * @param {String} type 835 | * @param {Boolean} parse Should we parse? 836 | * @return {String} 837 | */ 838 | Unirest.type = function (type, parse) { 839 | if (typeof type !== 'string') return false 840 | return parse ? type.split(/ *; */).shift() : (Unirest.types[type] || type) 841 | } 842 | 843 | /** 844 | * Utility method to trim strings. 845 | * 846 | * @type {String} 847 | */ 848 | Unirest.trim = ''.trim 849 | ? function (s) { return s.trim() } 850 | : function (s) { return s.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '') } 851 | 852 | /** 853 | * Parser methods for different data types. 854 | * 855 | * @type {Object} 856 | */ 857 | Unirest.parsers = { 858 | string: function (data) { 859 | var obj = {} 860 | var pairs = data.split('&') 861 | var parts 862 | var pair 863 | 864 | for (var i = 0, len = pairs.length; i < len; ++i) { 865 | pair = pairs[i] 866 | parts = pair.split('=') 867 | obj[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]) 868 | } 869 | 870 | return obj 871 | }, 872 | 873 | json: function (data) { 874 | try { 875 | data = JSON.parse(data) 876 | } catch (e) {} 877 | 878 | return data 879 | } 880 | } 881 | 882 | /** 883 | * Serialization methods for different data types. 884 | * 885 | * @type {Object} 886 | */ 887 | Unirest.serializers = { 888 | form: function (obj) { 889 | return QueryString.stringify(obj) 890 | }, 891 | 892 | json: function (obj) { 893 | return JSON.stringify(obj) 894 | } 895 | } 896 | 897 | /** 898 | * Unirest Request Utility Methods 899 | * 900 | * @type {Object} 901 | */ 902 | Unirest.Request = { 903 | serialize: function (string, type) { 904 | var serializer = Unirest.firstMatch(type, Unirest.enum.serialize) 905 | return serializer ? serializer(string) : string 906 | }, 907 | 908 | uid: function (len) { 909 | var output = '' 910 | var chars = 'abcdefghijklmnopqrstuvwxyz123456789' 911 | var nchars = chars.length 912 | while (len--) output += chars[Math.random() * nchars | 0] 913 | return output 914 | } 915 | } 916 | 917 | /** 918 | * Unirest Response Utility Methods 919 | * 920 | * @type {Object} 921 | */ 922 | Unirest.Response = { 923 | parse: function (string, type) { 924 | var parser = Unirest.firstMatch(type, Unirest.enum.parse) 925 | return parser ? parser(string) : string 926 | }, 927 | 928 | parseHeader: function (str) { 929 | var lines = str.split(/\r?\n/) 930 | var fields = {} 931 | var index 932 | var line 933 | var field 934 | var val 935 | 936 | // Trailing CRLF 937 | lines.pop() 938 | 939 | for (var i = 0, len = lines.length; i < len; ++i) { 940 | line = lines[i] 941 | index = line.indexOf(':') 942 | field = line.slice(0, index).toLowerCase() 943 | val = Unirest.trim(line.slice(index + 1)) 944 | fields[field] = val 945 | } 946 | 947 | return fields 948 | }, 949 | 950 | statusCodes: { 951 | 'created': 201, 952 | 'accepted': 202, 953 | 'nonAuthoritativeInformation': 203, 954 | 'noContent': 204, 955 | 'resetContent': 205, 956 | 'partialContent': 206, 957 | 'multiStatus': 207, 958 | 'alreadyReported': 208, 959 | 'imUsed': 226, 960 | 'multipleChoices': 300, 961 | 'movedPermanently': 301, 962 | 'found': 302, 963 | 'seeOther': 303, 964 | 'notModified': 304, 965 | 'useProxy': 305, 966 | 'temporaryRedirect': 307, 967 | 'permanentRedirect': 308, 968 | 'badRequest': 400, 969 | 'unauthorized': 401, 970 | 'paymentRequired': 402, 971 | 'forbidden': 403, 972 | 'notFound': 404, 973 | 'methodNotAllowed': 405, 974 | 'notAcceptable': 406, 975 | 'proxyAuthenticationRequired': 407, 976 | 'requestTimeout': 408, 977 | 'conflict': 409, 978 | 'gone': 410, 979 | 'lengthRequired': 411, 980 | 'preconditionFailed': 412, 981 | 'requestEntityTooLarge': 413, 982 | 'uriTooLong': 414, 983 | 'unsupportedMediaType': 415, 984 | 'rangeNotSatisfiable': 416, 985 | 'expectationFailed': 417, 986 | 'misdirectedRequest': 421, 987 | 'unprocessableEntity': 422, 988 | 'locked': 423, 989 | 'failedDependency': 424, 990 | 'upgradeRequired': 426, 991 | 'preconditionRequired': 428, 992 | 'tooManyRequests': 429, 993 | 'requestHeaderFieldsTooLarge': 431, 994 | 'internalServerError': 500, 995 | 'notImplemented': 501, 996 | 'badGateway': 502, 997 | 'serviceUnavailable': 503, 998 | 'gatewayTimeout': 504, 999 | 'httpVersionNotSupported': 505, 1000 | 'variantAlsoNegotiates': 506, 1001 | 'insufficientStorage': 507, 1002 | 'loopDetected': 508, 1003 | 'notExtended': 510 1004 | } 1005 | } 1006 | 1007 | /** 1008 | * Expose cookie store (tough-cookie) 1009 | * 1010 | * @return {Function} Cookie Store 1011 | */ 1012 | Unirest.jar = function (options) { 1013 | var jar = Unirest.request.jar() 1014 | options = options || {} 1015 | 1016 | // Because Requests aliases toughcookie rather than returning. 1017 | if (options.store) { 1018 | jar._jar.store = options.store 1019 | } 1020 | 1021 | if (options.rejectPublicSuffixes) { 1022 | jar._jar.rejectPublicSuffixes = options.rejectPublicSuffixes 1023 | } 1024 | 1025 | // Alias helper methods 1026 | jar.add = jar.setCookie 1027 | jar.toString = jar.getCookieString 1028 | 1029 | // Export 1030 | return jar 1031 | } 1032 | 1033 | /** 1034 | * Enum Structures 1035 | * 1036 | * @type {Object} 1037 | */ 1038 | Unirest.enum = { 1039 | serialize: { 1040 | 'application/x-www-form-urlencoded': Unirest.serializers.form, 1041 | 'application/json': Unirest.serializers.json, 1042 | '+json': Unirest.serializers.json 1043 | }, 1044 | 1045 | parse: { 1046 | 'application/x-www-form-urlencoded': Unirest.parsers.string, 1047 | 'application/json': Unirest.parsers.json, 1048 | '+json': Unirest.parsers.json 1049 | }, 1050 | 1051 | methods: [ 1052 | 'GET', 1053 | 'HEAD', 1054 | 'PUT', 1055 | 'POST', 1056 | 'PATCH', 1057 | 'DELETE', 1058 | 'OPTIONS' 1059 | ], 1060 | 1061 | options: [ 1062 | 'uri:url', 'redirects:maxRedirects', 'redirect:followRedirect', 'url', 'method', 'qs', 'form', 'json', 'multipart', 1063 | 'followRedirect', 'followAllRedirects', 'maxRedirects', 'encoding', 'pool', 'timeout', 'proxy', 'oauth', 'hawk', 'time', 1064 | 'ssl:strictSSL', 'strictSSL', 'jar', 'cookies:jar', 'aws', 'httpSignature', 'localAddress', 'ip:localAddress', 'secureProtocol', 'forever' 1065 | ] 1066 | } 1067 | 1068 | /** 1069 | * Returns a list of values obtained by checking the specified string 1070 | * whether it contains array value or object key, when true the value 1071 | * is appended to the list to be returned. 1072 | * 1073 | * @param {String} string String to be tested 1074 | * @param {Object|Array} map Values / Keys to test against string. 1075 | * @return {Array} List of values truthfully matched against string. 1076 | */ 1077 | Unirest.matches = function matches (string, map) { 1078 | var results = [] 1079 | 1080 | for (var key in map) { 1081 | if (typeof map.length !== 'undefined') { 1082 | key = map[key] 1083 | } 1084 | 1085 | if (string.indexOf(key) !== -1) { 1086 | results.push(map[key]) 1087 | } 1088 | } 1089 | 1090 | return results 1091 | } 1092 | 1093 | /** 1094 | * Returns the first value obtained through #matches 1095 | * 1096 | * @see #matches 1097 | * @param {String} string String to be tested 1098 | * @param {Object|Array} map Values / Keys to test against string. 1099 | * @return {Mixed} First match value 1100 | */ 1101 | Unirest.firstMatch = function firstMatch (string, map) { 1102 | return Unirest.matches(string, map)[0] 1103 | } 1104 | 1105 | /** 1106 | * Generate sugar for request library. 1107 | * 1108 | * This allows us to mock super-agent chaining style while using request library under the hood. 1109 | */ 1110 | function setupMethod (method) { 1111 | Unirest[method] = Unirest(method) 1112 | } 1113 | 1114 | for (var i = 0; i < Unirest.enum.methods.length; i++) { 1115 | var method = Unirest.enum.methods[i].toLowerCase() 1116 | setupMethod(method) 1117 | } 1118 | 1119 | /** 1120 | * Simple Utility Methods for checking information about a value. 1121 | * 1122 | * @param {Mixed} value Could be anything. 1123 | * @return {Object} 1124 | */ 1125 | function is (value) { 1126 | return { 1127 | a: function (check) { 1128 | if (check.prototype) check = check.prototype.constructor.name 1129 | var type = Object.prototype.toString.call(value).slice(8, -1).toLowerCase() 1130 | return value != null && type === check.toLowerCase() 1131 | } 1132 | } 1133 | } 1134 | 1135 | /** 1136 | * Simple Utility Methods for checking information about a value. 1137 | * 1138 | * @param {Mixed} value Could be anything. 1139 | * @return {Object} 1140 | */ 1141 | function does (value) { 1142 | var arrayIndexOf = (Array.indexOf ? function (arr, obj, from) { 1143 | return arr.indexOf(obj, from) 1144 | } : function (arr, obj, from) { 1145 | var l = arr.length 1146 | var i = from ? parseInt((1 * from) + (from < 0 ? l : 0), 10) : 0 1147 | i = i < 0 ? 0 : i 1148 | for (; i < l; i++) if (i in arr && arr[i] === obj) return i 1149 | return -1 1150 | }) 1151 | 1152 | return { 1153 | startWith: function (string) { 1154 | if (is(value).a(String)) return value.slice(0, string.length) === string 1155 | if (is(value).a(Array)) return value[0] === string 1156 | return false 1157 | }, 1158 | 1159 | endWith: function (string) { 1160 | if (is(value).a(String)) return value.slice(-string.length) === string 1161 | if (is(value).a(Array)) return value[value.length - 1] === string 1162 | return false 1163 | }, 1164 | 1165 | contain: function (field) { 1166 | if (is(value).a(String)) return value.indexOf(field) > -1 1167 | if (is(value).a(Object)) return Object.prototype.hasOwnProperty.call(value, field) 1168 | if (is(value).a(Array)) return !!~arrayIndexOf(value, field) 1169 | return false 1170 | } 1171 | } 1172 | } 1173 | 1174 | /** 1175 | * Expose the Unirest Container 1176 | */ 1177 | 1178 | module.exports = exports = Unirest 1179 | --------------------------------------------------------------------------------