| 1 | | /** |
| 2 | | * Module dependencies. |
| 3 | | */ |
| 4 | | |
| 5 | 1 | require('buffer-concat'); |
| 6 | 1 | var http = require('http'); |
| 7 | 1 | var https = require('https'); |
| 8 | 1 | var urlutil = require('url'); |
| 9 | 1 | var qs = require('querystring'); |
| 10 | | |
| 11 | 1 | var USER_AGENT = exports.USER_AGENT = 'node-urllib/1.0'; |
| 12 | 1 | var TIME_OUT = exports.TIME_OUT = 60000; // 60 seconds |
| 13 | | |
| 14 | | // change Agent.maxSockets to 1000 |
| 15 | 1 | exports.agent = new http.Agent(); |
| 16 | 1 | exports.agent.maxSockets = 1000; |
| 17 | | |
| 18 | 1 | exports.httpsAgent = new https.Agent(); |
| 19 | 1 | exports.httpsAgent.maxSockets = 1000; |
| 20 | | |
| 21 | | /** |
| 22 | | * The default request timeout(in milliseconds). |
| 23 | | * @type {Number} |
| 24 | | * @const |
| 25 | | */ |
| 26 | 1 | exports.TIMEOUT = 5000; |
| 27 | | |
| 28 | | |
| 29 | | /** |
| 30 | | * Handle all http request, both http and https support well. |
| 31 | | * |
| 32 | | * @example |
| 33 | | * |
| 34 | | * var urllib = require('urllib'); |
| 35 | | * // GET http://httptest.cnodejs.net |
| 36 | | * urllib.request('http://httptest.cnodejs.net/test/get', function(err, data, res) {}); |
| 37 | | * // POST http://httptest.cnodejs.net |
| 38 | | * var args = { type: 'post', data: { foo: 'bar' } }; |
| 39 | | * urllib.request('http://httptest.cnodejs.net/test/post', args, function(err, data, res) {}); |
| 40 | | * |
| 41 | | * @param {String|Object} url |
| 42 | | * @param {Object} [args], optional |
| 43 | | * - {Object} [data]: request data, will auto be query stringify. |
| 44 | | * - {String|Buffer} [content]: optional, if set content, `data` will ignore. |
| 45 | | * - {ReadStream} [stream]: read stream to sent. |
| 46 | | * - {WriteStream} [writeStream]: writable stream to save response data. |
| 47 | | * If you use this, callback's data should be null. |
| 48 | | * - {String} [type]: optional, could be GET | POST | DELETE | PUT, default is GET |
| 49 | | * - {String} [dataType]: optional, `text` or `json`, default is text |
| 50 | | * - {Object} [headers]: optional, request headers |
| 51 | | * - {Number} [timeout]: request timeout(in milliseconds), default is `exports.TIMEOUT` |
| 52 | | * - {Agent} [agent]: optional, http agent |
| 53 | | * - {Agent} [httpsAgent]: optional, https agent |
| 54 | | * - {String} auth: Basic authentication i.e. 'user:password' to compute an Authorization header. |
| 55 | | * @param {Function} callback, callback(error, data, res) |
| 56 | | * @param {Object} optional context of callback, callback.call(context, error, data, res) |
| 57 | | * @return {HttpRequest} req object. |
| 58 | | * @api public |
| 59 | | */ |
| 60 | 1 | exports.request = function (url, args, callback, context) { |
| 61 | 26 | if (typeof args === 'function') { |
| 62 | 5 | context = callback; |
| 63 | 5 | callback = args; |
| 64 | 5 | args = null; |
| 65 | | } |
| 66 | 26 | args = args || {}; |
| 67 | 26 | args.timeout = args.timeout || exports.TIMEOUT; |
| 68 | 26 | var info = typeof url === 'string' ? urlutil.parse(url) : url; |
| 69 | 26 | args.type = (args.type || args.method || info.method || 'GET').toUpperCase(); |
| 70 | 26 | var method = args.type; |
| 71 | 26 | var port = info.port || 80; |
| 72 | 26 | var httplib = http; |
| 73 | 26 | var agent = args.agent || exports.agent; |
| 74 | 26 | if (info.protocol === 'https:') { |
| 75 | 2 | httplib = https; |
| 76 | 2 | agent = args.httpsAgent || exports.httpsAgent; |
| 77 | 2 | if (!info.port) { |
| 78 | 2 | port = 443; |
| 79 | | } |
| 80 | | } |
| 81 | 26 | var options = { |
| 82 | | host: info.hostname || info.host || 'localhost', |
| 83 | | path: info.path || '/', |
| 84 | | method: method, |
| 85 | | port: port, |
| 86 | | agent: agent, |
| 87 | | headers: args.headers || {} |
| 88 | | }; |
| 89 | | |
| 90 | 26 | var auth = args.auth || info.auth; |
| 91 | 26 | if (auth) { |
| 92 | 2 | options.auth = auth; |
| 93 | | } |
| 94 | | |
| 95 | 26 | var body = args.content || args.data; |
| 96 | 26 | if (!args.content) { |
| 97 | 25 | if (body && !(typeof body === 'string' || Buffer.isBuffer(body))) { |
| 98 | 5 | body = qs.stringify(body); |
| 99 | 5 | if (method === 'POST' || method === 'PUT') { |
| 100 | | // auto add application/x-www-form-urlencoded when using urlencode form request |
| 101 | 3 | if (!options.headers['Content-Type'] && !options.headers['content-type']) { |
| 102 | 2 | options.headers['Content-Type'] = 'application/x-www-form-urlencoded'; |
| 103 | | } |
| 104 | | } |
| 105 | | } |
| 106 | | } |
| 107 | 26 | if ((method === 'GET' || method === 'HEAD') && body) { |
| 108 | 2 | options.path += (info.query ? '' : '?') + body; |
| 109 | 2 | body = null; |
| 110 | | } |
| 111 | 26 | if (body) { |
| 112 | 6 | var length = body.length; |
| 113 | 6 | if (!Buffer.isBuffer(body)) { |
| 114 | 3 | length = Buffer.byteLength(body); |
| 115 | | } |
| 116 | 6 | options.headers['Content-Length'] = length; |
| 117 | | } |
| 118 | 26 | if (args.dataType === 'json') { |
| 119 | 3 | options.headers.Accept = 'application/json'; |
| 120 | | } |
| 121 | | |
| 122 | 26 | var timer = null; |
| 123 | 26 | var done = function (err, data, res) { |
| 124 | 25 | if (timer) { |
| 125 | 24 | clearTimeout(timer); |
| 126 | 24 | timer = null; |
| 127 | | } |
| 128 | 25 | if (!callback) { |
| 129 | 0 | return; |
| 130 | | } |
| 131 | 25 | var cb = callback; |
| 132 | 25 | callback = null; |
| 133 | 25 | cb.call(context, err, data, res); |
| 134 | | }; |
| 135 | | |
| 136 | 26 | var writeStream = args.writeStream; |
| 137 | | |
| 138 | 26 | var req = httplib.request(options, function (res) { |
| 139 | 21 | if (writeStream) { |
| 140 | 1 | res.on('end', callback.bind(null, null, null, res)); |
| 141 | 1 | return res.pipe(writeStream); |
| 142 | | } |
| 143 | 20 | var chunks = [], size = 0; |
| 144 | 20 | res.on('data', function (chunk) { |
| 145 | 249 | size += chunk.length; |
| 146 | 249 | chunks.push(chunk); |
| 147 | | }); |
| 148 | 20 | res.on('end', function () { |
| 149 | 20 | var data = Buffer.concat(chunks, size); |
| 150 | 20 | var err = null; |
| 151 | 20 | if (args.dataType === 'json') { |
| 152 | 3 | try { |
| 153 | 3 | data = JSON.parse(data); |
| 154 | | } catch (e) { |
| 155 | 1 | err = e; |
| 156 | | } |
| 157 | | } |
| 158 | 20 | done(err, data, res); |
| 159 | | }); |
| 160 | | }); |
| 161 | | |
| 162 | 26 | var timeout = args.timeout; |
| 163 | 26 | var __err = null; |
| 164 | 26 | timer = setTimeout(function () { |
| 165 | 1 | timer = null; |
| 166 | 1 | __err = new Error('Request timeout for ' + timeout + 'ms.'); |
| 167 | 1 | __err.name = 'RequestTimeoutError'; |
| 168 | 1 | req.abort(); |
| 169 | | }, timeout); |
| 170 | 26 | req.once('error', function (err) { |
| 171 | 5 | if (!__err && err.name === 'Error') { |
| 172 | 2 | err.name = 'RequestError'; |
| 173 | | } |
| 174 | 5 | done(__err || err); |
| 175 | | }); |
| 176 | | |
| 177 | 26 | if (writeStream) { |
| 178 | 3 | writeStream.once('error', function (err) { |
| 179 | 1 | __err = err; |
| 180 | 1 | req.abort(); |
| 181 | | }); |
| 182 | | } |
| 183 | | |
| 184 | 26 | if (args.stream) { |
| 185 | 3 | args.stream.pipe(req); |
| 186 | 3 | args.stream.once('error', function (err) { |
| 187 | 1 | __err = err; |
| 188 | 1 | req.abort(); |
| 189 | | }); |
| 190 | | } else { |
| 191 | 23 | req.end(body); |
| 192 | | } |
| 193 | 26 | return req; |
| 194 | | }; |
| 195 | | |