FileController.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import FileHandler from './FileHandler.js'
  2. import metrics from '@overleaf/metrics'
  3. import parseRange from 'range-parser'
  4. import Errors from './Errors.js'
  5. import { pipeline } from 'node:stream'
  6. const maxSizeInBytes = 1024 * 1024 * 1024 // 1GB
  7. export default {
  8. getFile,
  9. getFileHead,
  10. insertFile,
  11. }
  12. function getFile(req, res, next) {
  13. const { key, bucket } = req
  14. const { format, style } = req.query
  15. const options = {
  16. key,
  17. bucket,
  18. format,
  19. style,
  20. }
  21. if (req.useSubdirectories) options.useSubdirectories = true
  22. metrics.inc('getFile')
  23. req.requestLogger.setMessage('getting file')
  24. req.requestLogger.addFields({
  25. key,
  26. bucket,
  27. format,
  28. style,
  29. cacheWarm: req.query.cacheWarm,
  30. })
  31. if (req.headers.range) {
  32. const range = _getRange(req.headers.range)
  33. if (range) {
  34. options.start = range.start
  35. options.end = range.end
  36. req.requestLogger.addFields({ range })
  37. }
  38. }
  39. FileHandler.getRedirectUrl(bucket, key, options, function (err, redirectUrl) {
  40. if (err) {
  41. metrics.inc('file_redirect_error')
  42. }
  43. if (redirectUrl) {
  44. metrics.inc('file_redirect')
  45. return res.redirect(redirectUrl)
  46. }
  47. FileHandler.getFile(bucket, key, options, function (err, fileStream) {
  48. if (err) {
  49. if (err instanceof Errors.NotFoundError) {
  50. res.sendStatus(404)
  51. } else {
  52. next(err)
  53. }
  54. return
  55. }
  56. if (res.destroyed) {
  57. fileStream.destroy()
  58. return
  59. }
  60. if (req.query.cacheWarm) {
  61. fileStream.destroy()
  62. return res.sendStatus(200).end()
  63. }
  64. pipeline(fileStream, res, err => {
  65. if (
  66. err &&
  67. (err.code === 'ERR_STREAM_PREMATURE_CLOSE' ||
  68. err.code === 'ERR_STREAM_UNABLE_TO_PIPE')
  69. ) {
  70. res.end()
  71. } else if (err) {
  72. next(
  73. new Errors.ReadError(
  74. 'error transferring stream',
  75. { bucket, key, format, style },
  76. err
  77. )
  78. )
  79. }
  80. })
  81. })
  82. })
  83. }
  84. function getFileHead(req, res, next) {
  85. const { key, bucket } = req
  86. metrics.inc('getFileSize')
  87. req.requestLogger.setMessage('getting file size')
  88. req.requestLogger.addFields({ key, bucket })
  89. FileHandler.getFileSize(bucket, key, function (err, fileSize) {
  90. if (err) {
  91. if (err instanceof Errors.NotFoundError) {
  92. res.sendStatus(404)
  93. } else {
  94. next(err)
  95. }
  96. return
  97. }
  98. res.set('Content-Length', fileSize)
  99. res.status(200).end()
  100. })
  101. }
  102. function insertFile(req, res, next) {
  103. metrics.inc('insertFile')
  104. const { key, bucket } = req
  105. req.requestLogger.setMessage('inserting file')
  106. req.requestLogger.addFields({ key, bucket })
  107. FileHandler.insertFile(bucket, key, req, function (err) {
  108. if (err) {
  109. next(err)
  110. } else {
  111. res.sendStatus(200)
  112. }
  113. })
  114. }
  115. function _getRange(header) {
  116. const parsed = parseRange(maxSizeInBytes, header)
  117. if (parsed === -1 || parsed === -2 || parsed.type !== 'bytes') {
  118. return null
  119. } else {
  120. const range = parsed[0]
  121. return { start: range.start, end: range.end }
  122. }
  123. }