FileDownloader.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. const https = require('https');
  2. const axios = require('axios');
  3. const utils = require('./utils');
  4. const userAgent = 'Mozilla/5.0 (X11; HasCodingOs 1.0; Linux x64) AppleWebKit/637.36 (KHTML, like Gecko) Chrome/70.0.3112.101 Safari/637.36 HasBrowser/5.0';
  5. class FileDownloader {
  6. constructor(limitDownloadSize = 0) {
  7. this.limitDownloadSize = limitDownloadSize;
  8. }
  9. async load(url, opts, callback, abort) {
  10. let errMes = '';
  11. let options = {
  12. headers: {
  13. 'accept-encoding': 'gzip, compress, deflate',
  14. 'user-agent': userAgent,
  15. timeout: 300*1000,
  16. },
  17. httpsAgent: new https.Agent({
  18. rejectUnauthorized: false // решение проблемы 'unable to verify the first certificate' для некоторых сайтов с валидным сертификатом
  19. }),
  20. responseType: 'stream',
  21. };
  22. if (opts)
  23. options = Object.assign({}, opts, options);
  24. try {
  25. const res = await axios.get(url, options);
  26. let estSize = 0;
  27. if (res.headers['content-length']) {
  28. estSize = res.headers['content-length'];
  29. }
  30. if (this.limitDownloadSize && estSize > this.limitDownloadSize) {
  31. throw new Error('Файл слишком большой');
  32. }
  33. let prevProg = 0;
  34. let transferred = 0;
  35. const download = this.streamToBuffer(res.data, (chunk) => {
  36. transferred += chunk.length;
  37. if (this.limitDownloadSize) {
  38. if (transferred > this.limitDownloadSize) {
  39. errMes = 'Файл слишком большой';
  40. res.request.abort();
  41. }
  42. }
  43. let prog = 0;
  44. if (estSize)
  45. prog = Math.round(transferred/estSize*100);
  46. else
  47. prog = Math.round(transferred/(transferred + 200000)*100);
  48. if (prog != prevProg && callback)
  49. callback(prog);
  50. prevProg = prog;
  51. if (abort && abort()) {
  52. errMes = 'abort';
  53. res.request.abort();
  54. }
  55. });
  56. return await download;
  57. } catch (error) {
  58. errMes = (errMes ? errMes : error.message);
  59. throw new Error(errMes);
  60. }
  61. }
  62. async head(url) {
  63. const options = {
  64. headers: {
  65. 'user-agent': userAgent,
  66. timeout: 10*1000,
  67. },
  68. };
  69. const res = await axios.head(url, options);
  70. return res.headers;
  71. }
  72. streamToBuffer(stream, progress, timeout = 30*1000) {
  73. return new Promise((resolve, reject) => {
  74. if (!progress)
  75. progress = () => {};
  76. const _buf = [];
  77. let resolved = false;
  78. let timer = 0;
  79. stream.on('data', (chunk) => {
  80. timer = 0;
  81. _buf.push(chunk);
  82. progress(chunk);
  83. });
  84. stream.on('end', () => {
  85. resolved = true;
  86. timer = timeout;
  87. resolve(Buffer.concat(_buf));
  88. });
  89. stream.on('error', (err) => {
  90. reject(err);
  91. });
  92. stream.on('aborted', () => {
  93. reject(new Error('aborted'));
  94. });
  95. //бодяга с timer и timeout, чтобы гарантировать отсутствие зависания по каким-либо причинам
  96. (async() => {
  97. while (timer < timeout) {
  98. await utils.sleep(1000);
  99. timer += 1000;
  100. }
  101. if (!resolved)
  102. reject(new Error('FileDownloader: timed out'))
  103. })();
  104. });
  105. }
  106. }
  107. module.exports = FileDownloader;