app.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. // Metrics must be initialized before importing anything else
  2. require('@overleaf/metrics/initialize')
  3. const Events = require('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. if (settings.sentry && settings.sentry.dsn) {
  18. logger.initializeErrorReporting(settings.sentry.dsn)
  19. }
  20. Metrics.open_sockets.monitor(true)
  21. Metrics.memory.monitor(logger)
  22. if (Metrics.event_loop) {
  23. Metrics.event_loop.monitor(logger)
  24. }
  25. Metrics.leaked_sockets.monitor(logger)
  26. app.use(function (req, res, next) {
  27. Metrics.inc('http-request')
  28. next()
  29. })
  30. // Handle requests that come in after we've started shutting down
  31. app.use((req, res, next) => {
  32. if (settings.shuttingDown) {
  33. logger.warn(
  34. { req, timeSinceShutdown: Date.now() - settings.shutDownTime },
  35. 'request received after shutting down'
  36. )
  37. // We don't want keep-alive connections to be kept open when the server is shutting down.
  38. res.set('Connection', 'close')
  39. }
  40. next()
  41. })
  42. Metrics.injectMetricsRoute(app)
  43. app.head(
  44. '/project/:project_id/file/:file_id',
  45. keyBuilder.userFileKeyMiddleware,
  46. fileController.getFileHead
  47. )
  48. app.get(
  49. '/project/:project_id/file/:file_id',
  50. keyBuilder.userFileKeyMiddleware,
  51. fileController.getFile
  52. )
  53. app.post(
  54. '/project/:project_id/file/:file_id',
  55. keyBuilder.userFileKeyMiddleware,
  56. fileController.insertFile
  57. )
  58. app.put(
  59. '/project/:project_id/file/:file_id',
  60. keyBuilder.userFileKeyMiddleware,
  61. bodyParser.json(),
  62. fileController.copyFile
  63. )
  64. app.delete(
  65. '/project/:project_id/file/:file_id',
  66. keyBuilder.userFileKeyMiddleware,
  67. fileController.deleteFile
  68. )
  69. app.delete(
  70. '/project/:project_id',
  71. keyBuilder.userProjectKeyMiddleware,
  72. fileController.deleteProject
  73. )
  74. app.head(
  75. '/template/:template_id/v/:version/:format',
  76. keyBuilder.templateFileKeyMiddleware,
  77. fileController.getFileHead
  78. )
  79. app.get(
  80. '/template/:template_id/v/:version/:format',
  81. keyBuilder.templateFileKeyMiddleware,
  82. fileController.getFile
  83. )
  84. app.get(
  85. '/template/:template_id/v/:version/:format/:sub_type',
  86. keyBuilder.templateFileKeyMiddleware,
  87. fileController.getFile
  88. )
  89. app.post(
  90. '/template/:template_id/v/:version/:format',
  91. keyBuilder.templateFileKeyMiddleware,
  92. fileController.insertFile
  93. )
  94. app.head(
  95. '/project/:project_id/public/:public_file_id',
  96. keyBuilder.publicFileKeyMiddleware,
  97. fileController.getFileHead
  98. )
  99. app.get(
  100. '/project/:project_id/public/:public_file_id',
  101. keyBuilder.publicFileKeyMiddleware,
  102. fileController.getFile
  103. )
  104. app.post(
  105. '/project/:project_id/public/:public_file_id',
  106. keyBuilder.publicFileKeyMiddleware,
  107. fileController.insertFile
  108. )
  109. app.put(
  110. '/project/:project_id/public/:public_file_id',
  111. keyBuilder.publicFileKeyMiddleware,
  112. bodyParser.json(),
  113. fileController.copyFile
  114. )
  115. app.delete(
  116. '/project/:project_id/public/:public_file_id',
  117. keyBuilder.publicFileKeyMiddleware,
  118. fileController.deleteFile
  119. )
  120. app.get(
  121. '/project/:project_id/size',
  122. keyBuilder.publicProjectKeyMiddleware,
  123. fileController.directorySize
  124. )
  125. app.get(
  126. '/bucket/:bucket/key/*',
  127. keyBuilder.bucketFileKeyMiddleware,
  128. fileController.getFile
  129. )
  130. app.get('/status', function (req, res) {
  131. if (settings.shuttingDown) {
  132. res.sendStatus(503) // Service unavailable
  133. } else {
  134. res.send('filestore is up')
  135. }
  136. })
  137. app.get('/health_check', healthCheckController.check)
  138. app.use(RequestLogger.errorHandler)
  139. const port = settings.internal.filestore.port || 3009
  140. const host = settings.internal.filestore.host || '0.0.0.0'
  141. if (!module.parent) {
  142. // Called directly
  143. app.listen(port, host, error => {
  144. if (error) {
  145. logger.error('Error starting Filestore', error)
  146. throw error
  147. }
  148. logger.debug(`Filestore starting up, listening on ${host}:${port}`)
  149. })
  150. }
  151. process
  152. .on('unhandledRejection', (reason, p) => {
  153. logger.err(reason, 'Unhandled Rejection at Promise', p)
  154. })
  155. .on('uncaughtException', err => {
  156. logger.err(err, 'Uncaught Exception thrown')
  157. process.exit(1)
  158. })
  159. function handleShutdownSignal(signal) {
  160. logger.info({ signal }, 'received interrupt, cleaning up')
  161. if (settings.shuttingDown) {
  162. logger.warn({ signal }, 'already shutting down, ignoring interrupt')
  163. return
  164. }
  165. settings.shuttingDown = true
  166. settings.shutDownTime = Date.now()
  167. setTimeout(() => {
  168. logger.info({ signal }, 'shutting down')
  169. process.exit()
  170. }, settings.gracefulShutdownDelayInMs)
  171. }
  172. process.on('SIGTERM', handleShutdownSignal)
  173. module.exports = app