FileDownloader.js 4.0 KB

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