app.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. // Metrics must be initialized before importing anything else
  2. require('@overleaf/metrics/initialize')
  3. const Events = require('node:events')
  4. const Metrics = require('@overleaf/metrics')
  5. const logger = require('@overleaf/logger')
  6. logger.initialize(process.env.METRICS_APP_NAME || 'filestore')
  7. const settings = require('@overleaf/settings')
  8. const express = require('express')
  9. const fileController = require('./app/js/FileController')
  10. const keyBuilder = require('./app/js/KeyBuilder')
  11. const RequestLogger = require('./app/js/RequestLogger')
  12. Events.setMaxListeners(20)
  13. const app = express()
  14. app.use(RequestLogger.middleware)
  15. Metrics.open_sockets.monitor(true)
  16. Metrics.memory.monitor(logger)
  17. if (Metrics.event_loop) {
  18. Metrics.event_loop.monitor(logger)
  19. }
  20. Metrics.leaked_sockets.monitor(logger)
  21. app.use(function (req, res, next) {
  22. Metrics.inc('http-request')
  23. next()
  24. })
  25. // Handle requests that come in after we've started shutting down
  26. app.use((req, res, next) => {
  27. if (settings.shuttingDown) {
  28. logger.warn(
  29. { req, timeSinceShutdown: Date.now() - settings.shutDownTime },
  30. 'request received after shutting down'
  31. )
  32. // We don't want keep-alive connections to be kept open when the server is shutting down.
  33. res.set('Connection', 'close')
  34. }
  35. next()
  36. })
  37. Metrics.injectMetricsRoute(app)
  38. if (settings.filestore.stores.template_files) {
  39. app.head(
  40. '/template/:template_id/v/:version/:format',
  41. keyBuilder.templateFileKeyMiddleware,
  42. fileController.getFileHead
  43. )
  44. app.get(
  45. '/template/:template_id/v/:version/:format',
  46. keyBuilder.templateFileKeyMiddleware,
  47. fileController.getFile
  48. )
  49. app.get(
  50. '/template/:template_id/v/:version/:format/:sub_type',
  51. keyBuilder.templateFileKeyMiddleware,
  52. fileController.getFile
  53. )
  54. app.post(
  55. '/template/:template_id/v/:version/:format',
  56. keyBuilder.templateFileKeyMiddleware,
  57. fileController.insertFile
  58. )
  59. }
  60. app.get(
  61. '/bucket/:bucket/key/*',
  62. keyBuilder.bucketFileKeyMiddleware,
  63. fileController.getFile
  64. )
  65. app.get(
  66. '/history/global/hash/:hash',
  67. keyBuilder.globalBlobFileKeyMiddleware,
  68. fileController.getFile
  69. )
  70. app.get(
  71. '/history/project/:historyId/hash/:hash',
  72. keyBuilder.projectBlobFileKeyMiddleware,
  73. fileController.getFile
  74. )
  75. app.get('/status', function (req, res) {
  76. if (settings.shuttingDown) {
  77. res.sendStatus(503) // Service unavailable
  78. } else {
  79. res.send('filestore is up')
  80. }
  81. })
  82. app.get('/health_check', (req, res) => {
  83. res.sendStatus(200)
  84. })
  85. app.use(RequestLogger.errorHandler)
  86. const port = settings.internal.filestore.port || 3009
  87. const host = settings.internal.filestore.host || '0.0.0.0'
  88. let server = null
  89. if (!module.parent) {
  90. // Called directly
  91. server = app.listen(port, host, error => {
  92. if (error) {
  93. logger.error({ err: error }, 'Error starting Filestore')
  94. throw error
  95. }
  96. logger.debug(`Filestore starting up, listening on ${host}:${port}`)
  97. })
  98. }
  99. process
  100. .on('unhandledRejection', (reason, p) => {
  101. logger.err(reason, 'Unhandled Rejection at Promise', p)
  102. })
  103. .on('uncaughtException', err => {
  104. logger.err(err, 'Uncaught Exception thrown')
  105. process.exit(1)
  106. })
  107. function handleShutdownSignal(signal) {
  108. logger.info({ signal }, 'received interrupt, cleaning up')
  109. if (settings.shuttingDown) {
  110. logger.warn({ signal }, 'already shutting down, ignoring interrupt')
  111. return
  112. }
  113. settings.shuttingDown = true
  114. settings.shutDownTime = Date.now()
  115. // stop accepting new connections, the callback is called when existing connections have finished
  116. server.close(() => {
  117. logger.info({ signal }, 'server closed')
  118. // exit after a short delay so logs can be flushed
  119. setTimeout(() => {
  120. process.exit()
  121. }, 100)
  122. })
  123. // close idle http keep-alive connections
  124. server.closeIdleConnections()
  125. setTimeout(() => {
  126. logger.info({ signal }, 'shutdown timed out, exiting')
  127. // close all connections immediately
  128. server.closeAllConnections()
  129. // exit after a short delay to allow for cleanup
  130. setTimeout(() => {
  131. process.exit()
  132. }, 100)
  133. }, settings.gracefulShutdownDelayInMs)
  134. }
  135. process.on('SIGTERM', handleShutdownSignal)
  136. module.exports = app