app.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. 'use strict'
  2. /* eslint-disable no-console */
  3. // Initialize metrics as early as possible because this is where the Google
  4. // profiling agents are also started.
  5. const Metrics = require('@overleaf/metrics')
  6. Metrics.initialize('history-v1')
  7. const Events = require('events')
  8. const BPromise = require('bluebird')
  9. const express = require('express')
  10. const cors = require('cors')
  11. const helmet = require('helmet')
  12. const HTTPStatus = require('http-status')
  13. const logger = require('@overleaf/logger')
  14. const cookieParser = require('cookie-parser')
  15. const bodyParser = require('body-parser')
  16. const swaggerTools = require('swagger-tools')
  17. const swaggerDoc = require('./api/swagger')
  18. const security = require('./api/app/security')
  19. const healthChecks = require('./api/controllers/health_checks')
  20. const { mongodb, loadGlobalBlobs } = require('./storage')
  21. const path = require('path')
  22. Events.setMaxListeners(20)
  23. const app = express()
  24. module.exports = app
  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: '4MB' }))
  34. app.use(
  35. bodyParser.urlencoded({
  36. extended: false,
  37. })
  38. )
  39. app.use(cookieParser())
  40. app.use(cors())
  41. security.setupSSL(app)
  42. security.setupBasicHttpAuthForSwaggerDocs(app)
  43. app.use(function (req, res, next) {
  44. // use a 5 minute timeout on all responses
  45. res.setTimeout(5 * 60 * 1000)
  46. next()
  47. })
  48. app.get('/', function (req, res) {
  49. res.send('')
  50. })
  51. app.get('/status', healthChecks.status)
  52. app.get('/health_check', healthChecks.healthCheck)
  53. function setupSwagger() {
  54. return new BPromise(function (resolve) {
  55. swaggerTools.initializeMiddleware(swaggerDoc, function (middleware) {
  56. app.use(middleware.swaggerMetadata())
  57. app.use(middleware.swaggerSecurity(security.getSwaggerHandlers()))
  58. app.use(middleware.swaggerValidator())
  59. app.use(
  60. middleware.swaggerRouter({
  61. controllers: path.join(__dirname, 'api/controllers'),
  62. useStubs: app.get('env') === 'development',
  63. })
  64. )
  65. app.use(middleware.swaggerUi())
  66. resolve()
  67. })
  68. })
  69. }
  70. function setupErrorHandling() {
  71. app.use(function (req, res, next) {
  72. const err = new Error('Not Found')
  73. err.status = HTTPStatus.NOT_FOUND
  74. return next(err)
  75. })
  76. // Handle Swagger errors.
  77. app.use(function (err, req, res, next) {
  78. if (res.headersSent) {
  79. return next(err)
  80. }
  81. if (err.code === 'SCHEMA_VALIDATION_FAILED') {
  82. logger.error(err)
  83. return res.status(HTTPStatus.UNPROCESSABLE_ENTITY).json(err.results)
  84. }
  85. if (err.code === 'INVALID_TYPE' || err.code === 'PATTERN') {
  86. logger.error(err)
  87. return res.status(HTTPStatus.UNPROCESSABLE_ENTITY).json({
  88. message: 'invalid type: ' + err.paramName,
  89. })
  90. }
  91. if (err.code === 'ENUM_MISMATCH') {
  92. return res.status(HTTPStatus.UNPROCESSABLE_ENTITY).json({
  93. message: 'invalid enum value: ' + err.paramName,
  94. })
  95. }
  96. if (err.code === 'REQUIRED') {
  97. return res.status(HTTPStatus.UNPROCESSABLE_ENTITY).json({
  98. message: err.message,
  99. })
  100. }
  101. next(err)
  102. })
  103. app.use(function (err, req, res, next) {
  104. logger.error(err)
  105. if (res.headersSent) {
  106. return next(err)
  107. }
  108. // Handle errors that specify a statusCode. Some come from our code. Some
  109. // bubble up from AWS SDK, but they sometimes have the statusCode set to
  110. // 200, notably some InternalErrors and TimeoutErrors, so we have to guard
  111. // against that. We also check `status`, but `statusCode` is preferred.
  112. const statusCode = err.statusCode || err.status
  113. if (statusCode && statusCode >= 400 && statusCode < 600) {
  114. res.status(statusCode)
  115. } else {
  116. res.status(HTTPStatus.INTERNAL_SERVER_ERROR)
  117. }
  118. const sendErrorToClient = app.get('env') === 'development'
  119. res.json({
  120. message: err.message,
  121. error: sendErrorToClient ? err : {},
  122. })
  123. })
  124. }
  125. app.setup = async function appSetup() {
  126. await mongodb.client.connect()
  127. logger.info('Connected to MongoDB')
  128. await loadGlobalBlobs()
  129. logger.info('Global blobs loaded')
  130. app.use(helmet())
  131. await setupSwagger()
  132. setupErrorHandling()
  133. }
  134. async function startApp() {
  135. await app.setup()
  136. const port = parseInt(process.env.PORT, 10) || 3100
  137. app.listen(port, err => {
  138. if (err) {
  139. console.error(err)
  140. process.exit(1)
  141. }
  142. Metrics.event_loop.monitor(logger)
  143. Metrics.memory.monitor(logger)
  144. })
  145. }
  146. // Run this if we're called directly
  147. if (!module.parent) {
  148. startApp().catch(err => {
  149. console.error(err)
  150. process.exit(1)
  151. })
  152. }