app.js 4.8 KB

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