2014-02-12 12:27:43 -05:00
|
|
|
CompileController = require "./app/js/CompileController"
|
|
|
|
Settings = require "settings-sharelatex"
|
|
|
|
logger = require "logger-sharelatex"
|
|
|
|
logger.initialize("clsi")
|
2016-03-30 09:35:47 -04:00
|
|
|
if Settings.sentry?.dsn?
|
|
|
|
logger.initializeErrorReporting(Settings.sentry.dsn)
|
|
|
|
|
2014-02-12 12:27:43 -05:00
|
|
|
smokeTest = require "smoke-test-sharelatex"
|
2015-09-21 09:04:08 -04:00
|
|
|
ContentTypeMapper = require "./app/js/ContentTypeMapper"
|
2014-02-12 12:27:43 -05:00
|
|
|
|
2014-10-28 08:07:17 -04:00
|
|
|
Path = require "path"
|
2014-12-04 17:07:37 -05:00
|
|
|
fs = require "fs"
|
2014-10-28 08:07:17 -04:00
|
|
|
|
2014-05-09 09:55:37 -04:00
|
|
|
Metrics = require "metrics-sharelatex"
|
|
|
|
Metrics.initialize("clsi")
|
|
|
|
Metrics.open_sockets.monitor(logger)
|
2015-08-14 09:47:42 -04:00
|
|
|
Metrics.memory.monitor(logger)
|
2014-05-09 09:55:37 -04:00
|
|
|
|
2014-02-12 12:27:43 -05:00
|
|
|
ProjectPersistenceManager = require "./app/js/ProjectPersistenceManager"
|
2015-02-27 08:16:01 -05:00
|
|
|
OutputCacheManager = require "./app/js/OutputCacheManager"
|
2014-02-12 12:27:43 -05:00
|
|
|
|
|
|
|
require("./app/js/db").sync()
|
|
|
|
|
|
|
|
express = require "express"
|
2014-05-19 07:18:57 -04:00
|
|
|
bodyParser = require "body-parser"
|
2014-02-12 12:27:43 -05:00
|
|
|
app = express()
|
|
|
|
|
2014-05-09 09:55:37 -04:00
|
|
|
app.use Metrics.http.monitor(logger)
|
|
|
|
|
2014-05-22 07:18:56 -04:00
|
|
|
# Compile requests can take longer than the default two
|
|
|
|
# minutes (including file download time), so bump up the
|
|
|
|
# timeout a bit.
|
2014-10-17 05:14:23 -04:00
|
|
|
TIMEOUT = 6 * 60 * 1000
|
2014-05-22 07:18:56 -04:00
|
|
|
app.use (req, res, next) ->
|
|
|
|
req.setTimeout TIMEOUT
|
|
|
|
res.setTimeout TIMEOUT
|
|
|
|
next()
|
|
|
|
|
2016-03-31 07:12:25 -04:00
|
|
|
app.param 'project_id', (req, res, next, project_id) ->
|
|
|
|
if project_id?.match /^[a-zA-Z0-9_-]+$/
|
|
|
|
next()
|
|
|
|
else
|
|
|
|
next new Error("invalid project id")
|
|
|
|
|
2016-05-19 11:40:12 -04:00
|
|
|
app.param 'user_id', (req, res, next, project_id) ->
|
|
|
|
if project_id?.match /^[a-zA-Z0-9_-]+$/
|
|
|
|
next()
|
|
|
|
else
|
|
|
|
next new Error("invalid user id")
|
|
|
|
|
2016-05-13 05:10:48 -04:00
|
|
|
app.param 'build_id', (req, res, next, build_id) ->
|
|
|
|
if build_id?.match OutputCacheManager.BUILD_REGEX
|
|
|
|
next()
|
|
|
|
else
|
|
|
|
next new Error("invalid build id #{build_id}")
|
|
|
|
|
|
|
|
|
2014-05-19 15:03:05 -04:00
|
|
|
app.post "/project/:project_id/compile", bodyParser.json(limit: "5mb"), CompileController.compile
|
2014-05-19 07:18:57 -04:00
|
|
|
app.delete "/project/:project_id", CompileController.clearCache
|
2014-02-12 12:27:43 -05:00
|
|
|
|
2014-04-08 10:18:56 -04:00
|
|
|
app.get "/project/:project_id/sync/code", CompileController.syncFromCode
|
|
|
|
app.get "/project/:project_id/sync/pdf", CompileController.syncFromPdf
|
2015-06-08 17:35:24 -04:00
|
|
|
app.get "/project/:project_id/wordcount", CompileController.wordcount
|
2016-04-20 10:38:05 -04:00
|
|
|
app.get "/project/:project_id/status", CompileController.status
|
2014-04-08 10:18:56 -04:00
|
|
|
|
2015-02-27 08:57:57 -05:00
|
|
|
ForbidSymlinks = require "./app/js/StaticServerForbidSymlinks"
|
2015-02-25 12:05:19 -05:00
|
|
|
|
2015-02-27 08:57:57 -05:00
|
|
|
# create a static server which does not allow access to any symlinks
|
|
|
|
# avoids possible mismatch of root directory between middleware check
|
|
|
|
# and serving the files
|
|
|
|
staticServer = ForbidSymlinks express.static, Settings.path.compilesDir, setHeaders: (res, path, stat) ->
|
2014-10-28 08:07:17 -04:00
|
|
|
if Path.basename(path) == "output.pdf"
|
2014-12-02 09:30:13 -05:00
|
|
|
# Calculate an etag in the same way as nginx
|
|
|
|
# https://github.com/tj/send/issues/65
|
|
|
|
etag = (path, stat) ->
|
|
|
|
'"' + Math.ceil(+stat.mtime / 1000).toString(16) +
|
|
|
|
'-' + Number(stat.size).toString(16) + '"'
|
|
|
|
res.set("Etag", etag(path, stat))
|
2015-09-21 09:04:08 -04:00
|
|
|
res.set("Content-Type", ContentTypeMapper.map(path))
|
2014-12-02 09:30:13 -05:00
|
|
|
|
2016-05-19 11:40:12 -04:00
|
|
|
app.get "/project/:project_id/user/:user_id/build/:build_id/output/*", (req, res, next) ->
|
|
|
|
# for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId)
|
|
|
|
req.url = "/#{req.params.project_id}-#{req.params.user_id}/" + OutputCacheManager.path(req.params.build_id, "/#{req.params[0]}")
|
|
|
|
staticServer(req, res, next)
|
|
|
|
|
2016-05-13 05:10:48 -04:00
|
|
|
app.get "/project/:project_id/build/:build_id/output/*", (req, res, next) ->
|
|
|
|
# for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId)
|
|
|
|
req.url = "/#{req.params.project_id}/" + OutputCacheManager.path(req.params.build_id, "/#{req.params[0]}")
|
|
|
|
staticServer(req, res, next)
|
|
|
|
|
2015-02-27 08:57:57 -05:00
|
|
|
app.get "/project/:project_id/output/*", (req, res, next) ->
|
2015-02-27 08:16:01 -05:00
|
|
|
if req.query?.build? && req.query.build.match(OutputCacheManager.BUILD_REGEX)
|
2015-02-27 08:57:57 -05:00
|
|
|
# for specific build get the path from the OutputCacheManager (e.g. .clsi/buildId)
|
|
|
|
req.url = "/#{req.params.project_id}/" + OutputCacheManager.path(req.query.build, "/#{req.params[0]}")
|
2015-02-25 12:05:19 -05:00
|
|
|
else
|
|
|
|
req.url = "/#{req.params.project_id}/#{req.params[0]}"
|
2014-12-04 18:54:22 -05:00
|
|
|
staticServer(req, res, next)
|
2014-02-12 12:27:43 -05:00
|
|
|
|
2016-03-30 09:35:47 -04:00
|
|
|
app.get "/oops", (req, res, next) ->
|
|
|
|
logger.error {err: "hello"}, "test error"
|
|
|
|
res.send "error\n"
|
|
|
|
|
|
|
|
|
2014-02-12 12:27:43 -05:00
|
|
|
app.get "/status", (req, res, next) ->
|
|
|
|
res.send "CLSI is alive\n"
|
|
|
|
|
2014-06-05 10:51:24 -04:00
|
|
|
resCacher =
|
|
|
|
contentType:(@setContentType)->
|
|
|
|
send:(@code, @body)->
|
|
|
|
|
2014-06-05 11:13:06 -04:00
|
|
|
#default the server to be down
|
|
|
|
code:500
|
|
|
|
body:{}
|
|
|
|
setContentType:"application/json"
|
|
|
|
|
2014-08-19 07:11:56 -04:00
|
|
|
if Settings.smokeTest
|
|
|
|
do runSmokeTest = ->
|
|
|
|
logger.log("running smoke tests")
|
|
|
|
smokeTest.run(require.resolve(__dirname + "/test/smoke/js/SmokeTests.js"))({}, resCacher)
|
|
|
|
setTimeout(runSmokeTest, 20 * 1000)
|
2014-06-05 10:51:24 -04:00
|
|
|
|
|
|
|
app.get "/health_check", (req, res)->
|
2014-06-05 11:13:06 -04:00
|
|
|
res.contentType(resCacher?.setContentType)
|
2015-05-12 06:40:29 -04:00
|
|
|
res.status(resCacher?.code).send(resCacher?.body)
|
2014-02-12 12:27:43 -05:00
|
|
|
|
2015-03-16 11:02:45 -04:00
|
|
|
profiler = require "v8-profiler"
|
|
|
|
app.get "/profile", (req, res) ->
|
|
|
|
time = parseInt(req.query.time || "1000")
|
|
|
|
profiler.startProfiling("test")
|
|
|
|
setTimeout () ->
|
|
|
|
profile = profiler.stopProfiling("test")
|
|
|
|
res.json(profile)
|
|
|
|
, time
|
|
|
|
|
2015-04-09 09:40:02 -04:00
|
|
|
app.get "/heapdump", (req, res)->
|
|
|
|
require('heapdump').writeSnapshot '/tmp/' + Date.now() + '.clsi.heapsnapshot', (err, filename)->
|
|
|
|
res.send filename
|
|
|
|
|
2014-02-12 12:27:43 -05:00
|
|
|
app.use (error, req, res, next) ->
|
|
|
|
logger.error err: error, "server error"
|
2015-05-12 06:40:29 -04:00
|
|
|
res.sendStatus(error?.statusCode || 500)
|
2014-02-12 12:27:43 -05:00
|
|
|
|
|
|
|
app.listen port = (Settings.internal?.clsi?.port or 3013), host = (Settings.internal?.clsi?.host or "localhost"), (error) ->
|
2015-04-30 10:03:41 -04:00
|
|
|
logger.info "CLSI starting up, listening on #{host}:#{port}"
|
2014-02-12 12:27:43 -05:00
|
|
|
|
|
|
|
setInterval () ->
|
|
|
|
ProjectPersistenceManager.clearExpiredProjects()
|
|
|
|
, tenMinutes = 10 * 60 * 1000
|
2016-04-08 08:31:23 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
net = require('net')
|
|
|
|
os = require('os')
|
|
|
|
|
|
|
|
server = net.createServer (socket) ->
|
2016-04-11 08:47:06 -04:00
|
|
|
socket.on "error", (err)->
|
2016-04-12 08:32:58 -04:00
|
|
|
if err.code == "ECONNRESET"
|
|
|
|
# this always comes up, we don't know why
|
|
|
|
return
|
2016-04-11 08:47:06 -04:00
|
|
|
logger.err err:err, "error with socket on load check"
|
2016-04-12 05:49:45 -04:00
|
|
|
socket.destroy()
|
|
|
|
|
2016-04-11 08:47:06 -04:00
|
|
|
currentLoad = os.loadavg()[0]
|
2016-04-08 09:14:05 -04:00
|
|
|
availableWorkingCpus = os.cpus().length - 1
|
2016-04-11 08:47:06 -04:00
|
|
|
freeLoad = availableWorkingCpus - currentLoad
|
2016-04-08 09:14:05 -04:00
|
|
|
freeLoadPercentage = Math.round((freeLoad / availableWorkingCpus) * 100)
|
2016-04-12 05:49:45 -04:00
|
|
|
if freeLoadPercentage <= 0
|
2016-04-11 08:47:06 -04:00
|
|
|
freeLoadPercentage = 1 # when its 0 the server is set to drain and will move projects to different servers
|
2016-04-12 05:49:45 -04:00
|
|
|
socket.write("up, #{freeLoadPercentage}%\n", "ASCII")
|
|
|
|
socket.end()
|
2016-04-08 08:31:23 -04:00
|
|
|
|
2016-05-19 11:23:33 -04:00
|
|
|
server.listen load_port = (Settings.internal?.clsi?.load_port or 3044), ->
|
|
|
|
logger.info "tcp load endpoint listening on port #{load_port}"
|
2016-04-12 05:49:45 -04:00
|
|
|
# telnet 127.0.0.1 3044
|
2016-04-08 08:31:23 -04:00
|
|
|
|
|
|
|
|