index.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. 'use strict';
  2. const EventEmitter = require('events');
  3. const http = require('http');
  4. const https = require('https');
  5. const PassThrough = require('stream').PassThrough;
  6. const urlLib = require('url');
  7. const querystring = require('querystring');
  8. const duplexer3 = require('duplexer3');
  9. const isStream = require('is-stream');
  10. const getStream = require('get-stream');
  11. const timedOut = require('timed-out');
  12. const urlParseLax = require('url-parse-lax');
  13. const lowercaseKeys = require('lowercase-keys');
  14. const isRedirect = require('is-redirect');
  15. const unzipResponse = require('unzip-response');
  16. const createErrorClass = require('create-error-class');
  17. const isRetryAllowed = require('is-retry-allowed');
  18. const Buffer = require('safe-buffer').Buffer;
  19. const pkg = require('./package');
  20. function requestAsEventEmitter(opts) {
  21. opts = opts || {};
  22. const ee = new EventEmitter();
  23. const requestUrl = opts.href || urlLib.resolve(urlLib.format(opts), opts.path);
  24. let redirectCount = 0;
  25. let retryCount = 0;
  26. let redirectUrl;
  27. const get = opts => {
  28. const fn = opts.protocol === 'https:' ? https : http;
  29. const req = fn.request(opts, res => {
  30. const statusCode = res.statusCode;
  31. if (isRedirect(statusCode) && opts.followRedirect && 'location' in res.headers && (opts.method === 'GET' || opts.method === 'HEAD')) {
  32. res.resume();
  33. if (++redirectCount > 10) {
  34. ee.emit('error', new got.MaxRedirectsError(statusCode, opts), null, res);
  35. return;
  36. }
  37. const bufferString = Buffer.from(res.headers.location, 'binary').toString();
  38. redirectUrl = urlLib.resolve(urlLib.format(opts), bufferString);
  39. const redirectOpts = Object.assign({}, opts, urlLib.parse(redirectUrl));
  40. ee.emit('redirect', res, redirectOpts);
  41. get(redirectOpts);
  42. return;
  43. }
  44. setImmediate(() => {
  45. const response = typeof unzipResponse === 'function' && req.method !== 'HEAD' ? unzipResponse(res) : res;
  46. response.url = redirectUrl || requestUrl;
  47. response.requestUrl = requestUrl;
  48. ee.emit('response', response);
  49. });
  50. });
  51. req.once('error', err => {
  52. const backoff = opts.retries(++retryCount, err);
  53. if (backoff) {
  54. setTimeout(get, backoff, opts);
  55. return;
  56. }
  57. ee.emit('error', new got.RequestError(err, opts));
  58. });
  59. if (opts.gotTimeout) {
  60. timedOut(req, opts.gotTimeout);
  61. }
  62. setImmediate(() => {
  63. ee.emit('request', req);
  64. });
  65. };
  66. get(opts);
  67. return ee;
  68. }
  69. function asPromise(opts) {
  70. return new Promise((resolve, reject) => {
  71. const ee = requestAsEventEmitter(opts);
  72. ee.on('request', req => {
  73. if (isStream(opts.body)) {
  74. opts.body.pipe(req);
  75. opts.body = undefined;
  76. return;
  77. }
  78. req.end(opts.body);
  79. });
  80. ee.on('response', res => {
  81. const stream = opts.encoding === null ? getStream.buffer(res) : getStream(res, opts);
  82. stream
  83. .catch(err => reject(new got.ReadError(err, opts)))
  84. .then(data => {
  85. const statusCode = res.statusCode;
  86. const limitStatusCode = opts.followRedirect ? 299 : 399;
  87. res.body = data;
  88. if (opts.json && res.body) {
  89. try {
  90. res.body = JSON.parse(res.body);
  91. } catch (e) {
  92. throw new got.ParseError(e, statusCode, opts, data);
  93. }
  94. }
  95. if (statusCode < 200 || statusCode > limitStatusCode) {
  96. throw new got.HTTPError(statusCode, opts);
  97. }
  98. resolve(res);
  99. })
  100. .catch(err => {
  101. Object.defineProperty(err, 'response', {value: res});
  102. reject(err);
  103. });
  104. });
  105. ee.on('error', reject);
  106. });
  107. }
  108. function asStream(opts) {
  109. const input = new PassThrough();
  110. const output = new PassThrough();
  111. const proxy = duplexer3(input, output);
  112. if (opts.json) {
  113. throw new Error('got can not be used as stream when options.json is used');
  114. }
  115. if (opts.body) {
  116. proxy.write = () => {
  117. throw new Error('got\'s stream is not writable when options.body is used');
  118. };
  119. }
  120. const ee = requestAsEventEmitter(opts);
  121. ee.on('request', req => {
  122. proxy.emit('request', req);
  123. if (isStream(opts.body)) {
  124. opts.body.pipe(req);
  125. return;
  126. }
  127. if (opts.body) {
  128. req.end(opts.body);
  129. return;
  130. }
  131. if (opts.method === 'POST' || opts.method === 'PUT' || opts.method === 'PATCH') {
  132. input.pipe(req);
  133. return;
  134. }
  135. req.end();
  136. });
  137. ee.on('response', res => {
  138. const statusCode = res.statusCode;
  139. res.pipe(output);
  140. if (statusCode < 200 || statusCode > 299) {
  141. proxy.emit('error', new got.HTTPError(statusCode, opts), null, res);
  142. return;
  143. }
  144. proxy.emit('response', res);
  145. });
  146. ee.on('redirect', proxy.emit.bind(proxy, 'redirect'));
  147. ee.on('error', proxy.emit.bind(proxy, 'error'));
  148. return proxy;
  149. }
  150. function normalizeArguments(url, opts) {
  151. if (typeof url !== 'string' && typeof url !== 'object') {
  152. throw new Error(`Parameter \`url\` must be a string or object, not ${typeof url}`);
  153. }
  154. if (typeof url === 'string') {
  155. url = url.replace(/^unix:/, 'http://$&');
  156. url = urlParseLax(url);
  157. if (url.auth) {
  158. throw new Error('Basic authentication must be done with auth option');
  159. }
  160. }
  161. opts = Object.assign(
  162. {
  163. protocol: 'http:',
  164. path: '',
  165. retries: 5
  166. },
  167. url,
  168. opts
  169. );
  170. opts.headers = Object.assign({
  171. 'user-agent': `${pkg.name}/${pkg.version} (https://github.com/sindresorhus/got)`,
  172. 'accept-encoding': 'gzip,deflate'
  173. }, lowercaseKeys(opts.headers));
  174. const query = opts.query;
  175. if (query) {
  176. if (typeof query !== 'string') {
  177. opts.query = querystring.stringify(query);
  178. }
  179. opts.path = `${opts.path.split('?')[0]}?${opts.query}`;
  180. delete opts.query;
  181. }
  182. if (opts.json && opts.headers.accept === undefined) {
  183. opts.headers.accept = 'application/json';
  184. }
  185. let body = opts.body;
  186. if (body) {
  187. if (typeof body !== 'string' && !(body !== null && typeof body === 'object')) {
  188. throw new Error('options.body must be a ReadableStream, string, Buffer or plain Object');
  189. }
  190. opts.method = opts.method || 'POST';
  191. if (isStream(body) && typeof body.getBoundary === 'function') {
  192. // Special case for https://github.com/form-data/form-data
  193. opts.headers['content-type'] = opts.headers['content-type'] || `multipart/form-data; boundary=${body.getBoundary()}`;
  194. } else if (body !== null && typeof body === 'object' && !Buffer.isBuffer(body) && !isStream(body)) {
  195. opts.headers['content-type'] = opts.headers['content-type'] || 'application/x-www-form-urlencoded';
  196. body = opts.body = querystring.stringify(body);
  197. }
  198. if (opts.headers['content-length'] === undefined && opts.headers['transfer-encoding'] === undefined && !isStream(body)) {
  199. const length = typeof body === 'string' ? Buffer.byteLength(body) : body.length;
  200. opts.headers['content-length'] = length;
  201. }
  202. }
  203. opts.method = (opts.method || 'GET').toUpperCase();
  204. if (opts.hostname === 'unix') {
  205. const matches = /(.+):(.+)/.exec(opts.path);
  206. if (matches) {
  207. opts.socketPath = matches[1];
  208. opts.path = matches[2];
  209. opts.host = null;
  210. }
  211. }
  212. if (typeof opts.retries !== 'function') {
  213. const retries = opts.retries;
  214. opts.retries = (iter, err) => {
  215. if (iter > retries || !isRetryAllowed(err)) {
  216. return 0;
  217. }
  218. const noise = Math.random() * 100;
  219. return ((1 << iter) * 1000) + noise;
  220. };
  221. }
  222. if (opts.followRedirect === undefined) {
  223. opts.followRedirect = true;
  224. }
  225. if (opts.timeout) {
  226. opts.gotTimeout = opts.timeout;
  227. delete opts.timeout;
  228. }
  229. return opts;
  230. }
  231. function got(url, opts) {
  232. try {
  233. return asPromise(normalizeArguments(url, opts));
  234. } catch (err) {
  235. return Promise.reject(err);
  236. }
  237. }
  238. const helpers = [
  239. 'get',
  240. 'post',
  241. 'put',
  242. 'patch',
  243. 'head',
  244. 'delete'
  245. ];
  246. helpers.forEach(el => {
  247. got[el] = (url, opts) => got(url, Object.assign({}, opts, {method: el}));
  248. });
  249. got.stream = (url, opts) => asStream(normalizeArguments(url, opts));
  250. for (const el of helpers) {
  251. got.stream[el] = (url, opts) => got.stream(url, Object.assign({}, opts, {method: el}));
  252. }
  253. function stdError(error, opts) {
  254. if (error.code !== undefined) {
  255. this.code = error.code;
  256. }
  257. Object.assign(this, {
  258. message: error.message,
  259. host: opts.host,
  260. hostname: opts.hostname,
  261. method: opts.method,
  262. path: opts.path
  263. });
  264. }
  265. got.RequestError = createErrorClass('RequestError', stdError);
  266. got.ReadError = createErrorClass('ReadError', stdError);
  267. got.ParseError = createErrorClass('ParseError', function (e, statusCode, opts, data) {
  268. stdError.call(this, e, opts);
  269. this.statusCode = statusCode;
  270. this.statusMessage = http.STATUS_CODES[this.statusCode];
  271. this.message = `${e.message} in "${urlLib.format(opts)}": \n${data.slice(0, 77)}...`;
  272. });
  273. got.HTTPError = createErrorClass('HTTPError', function (statusCode, opts) {
  274. stdError.call(this, {}, opts);
  275. this.statusCode = statusCode;
  276. this.statusMessage = http.STATUS_CODES[this.statusCode];
  277. this.message = `Response code ${this.statusCode} (${this.statusMessage})`;
  278. });
  279. got.MaxRedirectsError = createErrorClass('MaxRedirectsError', function (statusCode, opts) {
  280. stdError.call(this, {}, opts);
  281. this.statusCode = statusCode;
  282. this.statusMessage = http.STATUS_CODES[this.statusCode];
  283. this.message = 'Redirected 10 times. Aborting.';
  284. });
  285. module.exports = got;