download.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. var progress = require('request-progress');
  2. var request = require('request');
  3. var Q = require('q');
  4. var mout = require('mout');
  5. var retry = require('retry');
  6. var fs = require('graceful-fs');
  7. var createError = require('./createError');
  8. var errorCodes = [
  9. 'EADDRINFO',
  10. 'ETIMEDOUT',
  11. 'ECONNRESET',
  12. 'ESOCKETTIMEDOUT'
  13. ];
  14. function download(url, file, options) {
  15. var operation;
  16. var response;
  17. var deferred = Q.defer();
  18. var progressDelay = 8000;
  19. options = mout.object.mixIn({
  20. retries: 5,
  21. factor: 2,
  22. minTimeout: 1000,
  23. maxTimeout: 35000,
  24. randomize: true
  25. }, options || {});
  26. // Retry on network errors
  27. operation = retry.operation(options);
  28. operation.attempt(function () {
  29. var req;
  30. var writeStream;
  31. var contentLength;
  32. var bytesDownloaded = 0;
  33. req = progress(request(url, options), {
  34. delay: progressDelay
  35. })
  36. .on('response', function (res) {
  37. var status = res.statusCode;
  38. if (status < 200 || status >= 300) {
  39. return deferred.reject(createError('Status code of ' + status, 'EHTTP'));
  40. }
  41. response = res;
  42. contentLength = Number(res.headers['content-length']);
  43. })
  44. .on('data', function (data) {
  45. bytesDownloaded += data.length;
  46. })
  47. .on('progress', function (state) {
  48. deferred.notify(state);
  49. })
  50. .on('end', function () {
  51. // Check if the whole file was downloaded
  52. // In some unstable connections the ACK/FIN packet might be sent in the
  53. // middle of the download
  54. // See: https://github.com/joyent/node/issues/6143
  55. if (contentLength && bytesDownloaded < contentLength) {
  56. req.emit('error', createError('Transfer closed with ' + (contentLength - bytesDownloaded) + ' bytes remaining to read', 'EINCOMPLETE'));
  57. }
  58. })
  59. .on('error', function (error) {
  60. var timeout = operation._timeouts[0];
  61. // Reject if error is not a network error
  62. if (errorCodes.indexOf(error.code) === -1) {
  63. return deferred.reject(error);
  64. }
  65. // Next attempt will start reporting download progress immediately
  66. progressDelay = 0;
  67. // Check if there are more retries
  68. if (operation.retry(error)) {
  69. // Ensure that there are no more events from this request
  70. req.removeAllListeners();
  71. req.on('error', function () {});
  72. // Ensure that there are no more events from the write stream
  73. writeStream.removeAllListeners();
  74. writeStream.on('error', function () {});
  75. return deferred.notify({
  76. retry: true,
  77. delay: timeout,
  78. error: error
  79. });
  80. }
  81. // No more retries, reject!
  82. deferred.reject(error);
  83. });
  84. // Pipe read stream to write stream
  85. writeStream = req
  86. .pipe(fs.createWriteStream(file))
  87. .on('error', deferred.reject)
  88. .on('close', function () {
  89. deferred.resolve(response);
  90. });
  91. });
  92. return deferred.promise;
  93. }
  94. module.exports = download;