app.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. /*
  2. * decaffeinate suggestions:
  3. * DS102: Remove unnecessary code created because of implicit returns
  4. * DS207: Consider shorter variations of null checks
  5. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  6. */
  7. const Metrics = require('metrics-sharelatex')
  8. Metrics.initialize('filestore')
  9. const express = require('express')
  10. const bodyParser = require('body-parser')
  11. let logger = require('logger-sharelatex')
  12. logger.initialize('filestore')
  13. const settings = require('settings-sharelatex')
  14. const request = require('request')
  15. const fileController = require('./app/js/FileController')
  16. const bucketController = require('./app/js/BucketController')
  17. const keyBuilder = require('./app/js/KeyBuilder')
  18. const healthCheckController = require('./app/js/HealthCheckController')
  19. const domain = require('domain')
  20. let appIsOk = true
  21. const app = express()
  22. if ((settings.sentry != null ? settings.sentry.dsn : undefined) != null) {
  23. logger.initializeErrorReporting(settings.sentry.dsn)
  24. }
  25. Metrics.open_sockets.monitor(logger)
  26. if (Metrics.event_loop != null) {
  27. Metrics.event_loop.monitor(logger)
  28. }
  29. Metrics.memory.monitor(logger)
  30. app.use(Metrics.http.monitor(logger))
  31. app.use(function(req, res, next) {
  32. Metrics.inc('http-request')
  33. return next()
  34. })
  35. app.use(function(req, res, next) {
  36. const requestDomain = domain.create()
  37. requestDomain.add(req)
  38. requestDomain.add(res)
  39. requestDomain.on('error', function(err) {
  40. try {
  41. // request a shutdown to prevent memory leaks
  42. beginShutdown()
  43. if (!res.headerSent) {
  44. res.send(500, 'uncaught exception')
  45. }
  46. logger = require('logger-sharelatex')
  47. req = {
  48. body: req.body,
  49. headers: req.headers,
  50. url: req.url,
  51. key: req.key,
  52. statusCode: req.statusCode
  53. }
  54. err = {
  55. message: err.message,
  56. stack: err.stack,
  57. name: err.name,
  58. type: err.type,
  59. arguments: err.arguments
  60. }
  61. return logger.err(
  62. { err, req, res },
  63. 'uncaught exception thrown on request'
  64. )
  65. } catch (exception) {
  66. return logger.err(
  67. { err: exception },
  68. 'exception in request domain handler'
  69. )
  70. }
  71. })
  72. return requestDomain.run(next)
  73. })
  74. app.use(function(req, res, next) {
  75. if (!appIsOk) {
  76. // when shutting down, close any HTTP keep-alive connections
  77. res.set('Connection', 'close')
  78. }
  79. return next()
  80. })
  81. Metrics.injectMetricsRoute(app)
  82. app.head(
  83. '/project/:project_id/file/:file_id',
  84. keyBuilder.userFileKey,
  85. fileController.getFileHead
  86. )
  87. app.get(
  88. '/project/:project_id/file/:file_id',
  89. keyBuilder.userFileKey,
  90. fileController.getFile
  91. )
  92. app.post(
  93. '/project/:project_id/file/:file_id',
  94. keyBuilder.userFileKey,
  95. fileController.insertFile
  96. )
  97. app.put(
  98. '/project/:project_id/file/:file_id',
  99. keyBuilder.userFileKey,
  100. bodyParser.json(),
  101. fileController.copyFile
  102. )
  103. app.del(
  104. '/project/:project_id/file/:file_id',
  105. keyBuilder.userFileKey,
  106. fileController.deleteFile
  107. )
  108. app.head(
  109. '/template/:template_id/v/:version/:format',
  110. keyBuilder.templateFileKey,
  111. fileController.getFileHead
  112. )
  113. app.get(
  114. '/template/:template_id/v/:version/:format',
  115. keyBuilder.templateFileKey,
  116. fileController.getFile
  117. )
  118. app.get(
  119. '/template/:template_id/v/:version/:format/:sub_type',
  120. keyBuilder.templateFileKey,
  121. fileController.getFile
  122. )
  123. app.post(
  124. '/template/:template_id/v/:version/:format',
  125. keyBuilder.templateFileKey,
  126. fileController.insertFile
  127. )
  128. app.head(
  129. '/project/:project_id/public/:public_file_id',
  130. keyBuilder.publicFileKey,
  131. fileController.getFileHead
  132. )
  133. app.get(
  134. '/project/:project_id/public/:public_file_id',
  135. keyBuilder.publicFileKey,
  136. fileController.getFile
  137. )
  138. app.post(
  139. '/project/:project_id/public/:public_file_id',
  140. keyBuilder.publicFileKey,
  141. fileController.insertFile
  142. )
  143. app.put(
  144. '/project/:project_id/public/:public_file_id',
  145. keyBuilder.publicFileKey,
  146. bodyParser.json(),
  147. fileController.copyFile
  148. )
  149. app.del(
  150. '/project/:project_id/public/:public_file_id',
  151. keyBuilder.publicFileKey,
  152. fileController.deleteFile
  153. )
  154. app.get(
  155. '/project/:project_id/size',
  156. keyBuilder.publicProjectKey,
  157. fileController.directorySize
  158. )
  159. app.get('/bucket/:bucket/key/*', bucketController.getFile)
  160. app.get('/heapdump', (req, res) =>
  161. require('heapdump').writeSnapshot(
  162. '/tmp/' + Date.now() + '.filestore.heapsnapshot',
  163. (err, filename) => res.send(filename)
  164. )
  165. )
  166. app.post('/shutdown', function(req, res) {
  167. appIsOk = false
  168. return res.send()
  169. })
  170. app.get('/status', function(req, res) {
  171. if (appIsOk) {
  172. return res.send('filestore sharelatex up')
  173. } else {
  174. logger.log('app is not ok - shutting down')
  175. return res.send('server is being shut down', 500)
  176. }
  177. })
  178. app.get('/health_check', healthCheckController.check)
  179. app.get('*', (req, res) => res.send(404))
  180. var beginShutdown = function() {
  181. if (appIsOk) {
  182. appIsOk = false
  183. // hard-terminate this process if graceful shutdown fails
  184. const killTimer = setTimeout(() => process.exit(1), 120 * 1000)
  185. if (typeof killTimer.unref === 'function') {
  186. killTimer.unref()
  187. } // prevent timer from keeping process alive
  188. server.close(function() {
  189. logger.log('closed all connections')
  190. Metrics.close()
  191. return typeof process.disconnect === 'function'
  192. ? process.disconnect()
  193. : undefined
  194. })
  195. return logger.log('server will stop accepting connections')
  196. }
  197. }
  198. const port = settings.internal.filestore.port || 3009
  199. const host = '0.0.0.0'
  200. if (!module.parent) {
  201. // Called directly
  202. var server = app.listen(port, host, error =>
  203. logger.info(`Filestore starting up, listening on ${host}:${port}`)
  204. )
  205. }
  206. module.exports = app
  207. process.on('SIGTERM', function() {
  208. logger.log('filestore got SIGTERM, shutting down gracefully')
  209. return beginShutdown()
  210. })
  211. if (global.gc != null) {
  212. let oneMinute
  213. const gcTimer = setInterval(function() {
  214. global.gc()
  215. return logger.log(process.memoryUsage(), 'global.gc')
  216. }, 3 * (oneMinute = 60 * 1000))
  217. gcTimer.unref()
  218. }