/* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ /* eslint-disable node/no-deprecated-api */ const Metrics = require('metrics-sharelatex') Metrics.initialize('filestore') const express = require('express') const bodyParser = require('body-parser') let logger = require('logger-sharelatex') logger.initialize('filestore') const settings = require('settings-sharelatex') const fileController = require('./app/js/FileController') const bucketController = require('./app/js/BucketController') const keyBuilder = require('./app/js/KeyBuilder') const healthCheckController = require('./app/js/HealthCheckController') const domain = require('domain') let appIsOk = true const app = express() if ((settings.sentry != null ? settings.sentry.dsn : undefined) != null) { logger.initializeErrorReporting(settings.sentry.dsn) } Metrics.open_sockets.monitor(logger) if (Metrics.event_loop != null) { Metrics.event_loop.monitor(logger) } Metrics.memory.monitor(logger) app.use(Metrics.http.monitor(logger)) app.use(function(req, res, next) { Metrics.inc('http-request') return next() }) app.use(function(req, res, next) { const requestDomain = domain.create() requestDomain.add(req) requestDomain.add(res) requestDomain.on('error', function(err) { try { // request a shutdown to prevent memory leaks beginShutdown() if (!res.headerSent) { res.send(500, 'uncaught exception') } logger = require('logger-sharelatex') req = { body: req.body, headers: req.headers, url: req.url, key: req.key, statusCode: req.statusCode } err = { message: err.message, stack: err.stack, name: err.name, type: err.type, arguments: err.arguments } return logger.err( { err, req, res }, 'uncaught exception thrown on request' ) } catch (exception) { return logger.err( { err: exception }, 'exception in request domain handler' ) } }) return requestDomain.run(next) }) app.use(function(req, res, next) { if (!appIsOk) { // when shutting down, close any HTTP keep-alive connections res.set('Connection', 'close') } return next() }) Metrics.injectMetricsRoute(app) app.head( '/project/:project_id/file/:file_id', keyBuilder.userFileKey, fileController.getFileHead ) app.get( '/project/:project_id/file/:file_id', keyBuilder.userFileKey, fileController.getFile ) app.post( '/project/:project_id/file/:file_id', keyBuilder.userFileKey, fileController.insertFile ) app.put( '/project/:project_id/file/:file_id', keyBuilder.userFileKey, bodyParser.json(), fileController.copyFile ) app.del( '/project/:project_id/file/:file_id', keyBuilder.userFileKey, fileController.deleteFile ) app.head( '/template/:template_id/v/:version/:format', keyBuilder.templateFileKey, fileController.getFileHead ) app.get( '/template/:template_id/v/:version/:format', keyBuilder.templateFileKey, fileController.getFile ) app.get( '/template/:template_id/v/:version/:format/:sub_type', keyBuilder.templateFileKey, fileController.getFile ) app.post( '/template/:template_id/v/:version/:format', keyBuilder.templateFileKey, fileController.insertFile ) app.head( '/project/:project_id/public/:public_file_id', keyBuilder.publicFileKey, fileController.getFileHead ) app.get( '/project/:project_id/public/:public_file_id', keyBuilder.publicFileKey, fileController.getFile ) app.post( '/project/:project_id/public/:public_file_id', keyBuilder.publicFileKey, fileController.insertFile ) app.put( '/project/:project_id/public/:public_file_id', keyBuilder.publicFileKey, bodyParser.json(), fileController.copyFile ) app.del( '/project/:project_id/public/:public_file_id', keyBuilder.publicFileKey, fileController.deleteFile ) app.get( '/project/:project_id/size', keyBuilder.publicProjectKey, fileController.directorySize ) app.get('/bucket/:bucket/key/*', bucketController.getFile) app.get('/heapdump', (req, res, next) => require('heapdump').writeSnapshot( '/tmp/' + Date.now() + '.filestore.heapsnapshot', (err, filename) => { if (err) { return next(err) } res.send(filename) } ) ) app.post('/shutdown', function(req, res) { appIsOk = false return res.send() }) app.get('/status', function(req, res) { if (appIsOk) { return res.send('filestore sharelatex up') } else { logger.log('app is not ok - shutting down') return res.send('server is being shut down', 500) } }) app.get('/health_check', healthCheckController.check) app.get('*', (req, res) => res.send(404)) var beginShutdown = function() { if (appIsOk) { appIsOk = false // hard-terminate this process if graceful shutdown fails const killTimer = setTimeout(() => process.exit(1), 120 * 1000) if (typeof killTimer.unref === 'function') { killTimer.unref() } // prevent timer from keeping process alive server.close(function() { logger.log('closed all connections') Metrics.close() return typeof process.disconnect === 'function' ? process.disconnect() : undefined }) return logger.log('server will stop accepting connections') } } const port = settings.internal.filestore.port || 3009 const host = '0.0.0.0' if (!module.parent) { // Called directly var server = app.listen(port, host, error => { if (error) { logger.error('Error starting Filestore', error) throw error } logger.info(`Filestore starting up, listening on ${host}:${port}`) }) } module.exports = app process.on('SIGTERM', function() { logger.log('filestore got SIGTERM, shutting down gracefully') return beginShutdown() }) if (global.gc != null) { const oneMinute = 60 * 1000 const gcTimer = setInterval(function() { global.gc() return logger.log(process.memoryUsage(), 'global.gc') }, 3 * oneMinute) gcTimer.unref() }