app.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. 'use strict'
  2. /* eslint-disable no-console */
  3. // Metrics must be initialized before importing anything else
  4. require('@overleaf/metrics/initialize')
  5. const config = require('config')
  6. const Events = require('node:events')
  7. const express = require('express')
  8. const helmet = require('helmet')
  9. const HTTPStatus = require('http-status')
  10. const logger = require('@overleaf/logger')
  11. const Metrics = require('@overleaf/metrics')
  12. const bodyParser = require('body-parser')
  13. const security = require('./api/middleware/security')
  14. const healthChecks = require('./api/controllers/health_checks')
  15. const { mongodb, loadGlobalBlobs } = require('./storage')
  16. const projectsRoutes = require('./api/routes/projects')
  17. const projectImportRoutes = require('./api/routes/project_import')
  18. const { createHandleValidationError } = require('@overleaf/validation-tools')
  19. Events.setMaxListeners(20)
  20. const app = express()
  21. module.exports = app
  22. const handleValidationError = createHandleValidationError(
  23. HTTPStatus.UNPROCESSABLE_ENTITY
  24. )
  25. logger.initialize('history-v1')
  26. Metrics.open_sockets.monitor()
  27. Metrics.injectMetricsRoute(app)
  28. app.use(Metrics.http.monitor(logger))
  29. Metrics.leaked_sockets.monitor(logger)
  30. // We may have fairly large JSON bodies when receiving large Changes. Clients
  31. // may have to handle 413 status codes and try creating files instead of sending
  32. // text content in changes.
  33. app.use(bodyParser.json({ limit: '12MB' }))
  34. app.use(
  35. bodyParser.urlencoded({
  36. extended: false,
  37. })
  38. )
  39. security.setupSSL(app)
  40. security.setupBasicHttpAuthForSwaggerDocs(app)
  41. const HTTP_REQUEST_TIMEOUT = parseInt(config.get('httpRequestTimeout'), 10)
  42. app.use(function (req, res, next) {
  43. res.setTimeout(HTTP_REQUEST_TIMEOUT)
  44. next()
  45. })
  46. app.get('/', function (req, res) {
  47. res.send('')
  48. })
  49. app.get('/status', healthChecks.status)
  50. app.get('/health_check', healthChecks.healthCheck)
  51. app.get('/docs', function (req, res) {
  52. res.send('OK')
  53. })
  54. function setupErrorHandling() {
  55. app.use(function (req, res, next) {
  56. const err = new Error('Not Found')
  57. err.status = HTTPStatus.NOT_FOUND
  58. return next(err)
  59. })
  60. app.use(handleValidationError)
  61. app.use(function (err, req, res, next) {
  62. const projectId = req.params?.project_id || req.body?.projectId
  63. logger.error({ err, projectId }, err.message)
  64. if (res.headersSent) {
  65. return next(err)
  66. }
  67. // Handle errors that specify a statusCode. Some come from our code. Some
  68. // bubble up from AWS SDK, but they sometimes have the statusCode set to
  69. // 200, notably some InternalErrors and TimeoutErrors, so we have to guard
  70. // against that. We also check `status`, but `statusCode` is preferred.
  71. const statusCode = err.statusCode || err.status
  72. if (err.headers) {
  73. res.set(err.headers)
  74. }
  75. if (statusCode && statusCode >= 400 && statusCode < 600) {
  76. res.status(statusCode)
  77. } else {
  78. res.status(HTTPStatus.INTERNAL_SERVER_ERROR)
  79. }
  80. const sendErrorToClient = app.get('env') === 'development'
  81. res.json({
  82. message: err.message,
  83. error: sendErrorToClient ? err : {},
  84. })
  85. })
  86. }
  87. app.setup = async function appSetup() {
  88. await mongodb.client.connect()
  89. logger.info('Connected to MongoDB')
  90. await loadGlobalBlobs()
  91. logger.info('Global blobs loaded')
  92. app.use(helmet())
  93. app.use('/api', projectsRoutes)
  94. app.use('/api', projectImportRoutes)
  95. setupErrorHandling()
  96. }
  97. async function startApp() {
  98. await app.setup()
  99. const port = parseInt(process.env.PORT, 10) || 3100
  100. app.listen(port, err => {
  101. if (err) {
  102. console.error(err)
  103. process.exit(1)
  104. }
  105. Metrics.event_loop.monitor(logger)
  106. Metrics.memory.monitor(logger)
  107. })
  108. }
  109. // Run this if we're called directly
  110. if (!module.parent) {
  111. startApp().catch(err => {
  112. console.error(err)
  113. process.exit(1)
  114. })
  115. }