UrlFetcher.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. /* eslint-disable
  2. handle-callback-err,
  3. no-return-assign,
  4. no-unused-vars,
  5. node/no-deprecated-api,
  6. */
  7. // TODO: This file was created by bulk-decaffeinate.
  8. // Fix any style issues and re-enable lint.
  9. /*
  10. * decaffeinate suggestions:
  11. * DS102: Remove unnecessary code created because of implicit returns
  12. * DS207: Consider shorter variations of null checks
  13. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  14. */
  15. let UrlFetcher
  16. const request = require('request').defaults({ jar: false })
  17. const fs = require('fs')
  18. const logger = require('logger-sharelatex')
  19. const settings = require('settings-sharelatex')
  20. const URL = require('url')
  21. const async = require('async')
  22. const oneMinute = 60 * 1000
  23. module.exports = UrlFetcher = {
  24. pipeUrlToFileWithRetry(url, filePath, callback) {
  25. const doDownload = function (cb) {
  26. UrlFetcher.pipeUrlToFile(url, filePath, cb)
  27. }
  28. async.retry(3, doDownload, callback)
  29. },
  30. pipeUrlToFile(url, filePath, _callback) {
  31. if (_callback == null) {
  32. _callback = function (error) {}
  33. }
  34. const callbackOnce = function (error) {
  35. if (timeoutHandler != null) {
  36. clearTimeout(timeoutHandler)
  37. }
  38. _callback(error)
  39. return (_callback = function () {})
  40. }
  41. if (settings.filestoreDomainOveride != null) {
  42. const p = URL.parse(url).path
  43. url = `${settings.filestoreDomainOveride}${p}`
  44. }
  45. var timeoutHandler = setTimeout(
  46. function () {
  47. timeoutHandler = null
  48. logger.error({ url, filePath }, 'Timed out downloading file to cache')
  49. return callbackOnce(
  50. new Error(`Timed out downloading file to cache ${url}`)
  51. )
  52. },
  53. // FIXME: maybe need to close fileStream here
  54. 3 * oneMinute
  55. )
  56. logger.log({ url, filePath }, 'started downloading url to cache')
  57. const urlStream = request.get({ url, timeout: oneMinute })
  58. urlStream.pause() // stop data flowing until we are ready
  59. // attach handlers before setting up pipes
  60. urlStream.on('error', function (error) {
  61. logger.error({ err: error, url, filePath }, 'error downloading url')
  62. return callbackOnce(
  63. error || new Error(`Something went wrong downloading the URL ${url}`)
  64. )
  65. })
  66. urlStream.on('end', () =>
  67. logger.log({ url, filePath }, 'finished downloading file into cache')
  68. )
  69. return urlStream.on('response', function (res) {
  70. if (res.statusCode >= 200 && res.statusCode < 300) {
  71. const fileStream = fs.createWriteStream(filePath)
  72. // attach handlers before setting up pipes
  73. fileStream.on('error', function (error) {
  74. logger.error(
  75. { err: error, url, filePath },
  76. 'error writing file into cache'
  77. )
  78. return fs.unlink(filePath, function (err) {
  79. if (err != null) {
  80. logger.err({ err, filePath }, 'error deleting file from cache')
  81. }
  82. return callbackOnce(error)
  83. })
  84. })
  85. fileStream.on('finish', function () {
  86. logger.log({ url, filePath }, 'finished writing file into cache')
  87. return callbackOnce()
  88. })
  89. fileStream.on('pipe', () =>
  90. logger.log({ url, filePath }, 'piping into filestream')
  91. )
  92. urlStream.pipe(fileStream)
  93. return urlStream.resume() // now we are ready to handle the data
  94. } else {
  95. logger.error(
  96. { statusCode: res.statusCode, url, filePath },
  97. 'unexpected status code downloading url to cache'
  98. )
  99. // https://nodejs.org/api/http.html#http_class_http_clientrequest
  100. // If you add a 'response' event handler, then you must consume
  101. // the data from the response object, either by calling
  102. // response.read() whenever there is a 'readable' event, or by
  103. // adding a 'data' handler, or by calling the .resume()
  104. // method. Until the data is consumed, the 'end' event will not
  105. // fire. Also, until the data is read it will consume memory
  106. // that can eventually lead to a 'process out of memory' error.
  107. urlStream.resume() // discard the data
  108. return callbackOnce(
  109. new Error(
  110. `URL returned non-success status code: ${res.statusCode} ${url}`
  111. )
  112. )
  113. }
  114. })
  115. }
  116. }