app.coffee 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. Metrics = require("metrics-sharelatex")
  2. Settings = require "settings-sharelatex"
  3. Metrics.initialize(Settings.appName or "real-time")
  4. async = require("async")
  5. logger = require "logger-sharelatex"
  6. logger.initialize("real-time")
  7. Metrics.event_loop.monitor(logger)
  8. express = require("express")
  9. session = require("express-session")
  10. redis = require("redis-sharelatex")
  11. if Settings.sentry?.dsn?
  12. logger.initializeErrorReporting(Settings.sentry.dsn)
  13. sessionRedisClient = redis.createClient(Settings.redis.websessions)
  14. RedisStore = require('connect-redis')(session)
  15. SessionSockets = require('session.socket.io')
  16. CookieParser = require("cookie-parser")
  17. DrainManager = require("./app/js/DrainManager")
  18. # Set up socket.io server
  19. app = express()
  20. Metrics.injectMetricsRoute(app)
  21. server = require('http').createServer(app)
  22. io = require('socket.io').listen(server)
  23. # Bind to sessions
  24. sessionStore = new RedisStore(client: sessionRedisClient)
  25. cookieParser = CookieParser(Settings.security.sessionSecret)
  26. sessionSockets = new SessionSockets(io, sessionStore, cookieParser, Settings.cookieName)
  27. io.configure ->
  28. io.enable('browser client minification')
  29. io.enable('browser client etag')
  30. # Fix for Safari 5 error of "Error during WebSocket handshake: location mismatch"
  31. # See http://answers.dotcloud.com/question/578/problem-with-websocket-over-ssl-in-safari-with
  32. io.set('match origin protocol', true)
  33. # gzip uses a Node 0.8.x method of calling the gzip program which
  34. # doesn't work with 0.6.x
  35. #io.enable('browser client gzip')
  36. io.set('transports', ['websocket', 'flashsocket', 'htmlfile', 'xhr-polling', 'jsonp-polling'])
  37. io.set('log level', 1)
  38. app.get "/", (req, res, next) ->
  39. res.send "real-time-sharelatex is alive"
  40. app.get "/status", (req, res, next) ->
  41. res.send "real-time-sharelatex is alive"
  42. app.get "/debug/events", (req, res, next) ->
  43. Settings.debugEvents = parseInt(req.query?.count,10) || 20
  44. logger.log {count: Settings.debugEvents}, "starting debug mode"
  45. res.send "debug mode will log next #{Settings.debugEvents} events"
  46. rclient = require("redis-sharelatex").createClient(Settings.redis.realtime)
  47. app.get "/health_check/redis", (req, res, next) ->
  48. rclient.healthCheck (error) ->
  49. if error?
  50. logger.err {err: error}, "failed redis health check"
  51. res.sendStatus 500
  52. else
  53. res.sendStatus 200
  54. Metrics.injectMetricsRoute(app)
  55. Router = require "./app/js/Router"
  56. Router.configure(app, io, sessionSockets)
  57. WebsocketLoadBalancer = require "./app/js/WebsocketLoadBalancer"
  58. WebsocketLoadBalancer.listenForEditorEvents(io)
  59. DocumentUpdaterController = require "./app/js/DocumentUpdaterController"
  60. DocumentUpdaterController.listenForUpdatesFromDocumentUpdater(io)
  61. port = Settings.internal.realTime.port
  62. host = Settings.internal.realTime.host
  63. server.listen port, host, (error) ->
  64. throw error if error?
  65. logger.info "realtime starting up, listening on #{host}:#{port}"
  66. # Stop huge stack traces in logs from all the socket.io parsing steps.
  67. Error.stackTraceLimit = 10
  68. shutdownCleanly = (signal) ->
  69. connectedClients = io.sockets.clients()?.length
  70. if connectedClients == 0
  71. logger.log("no clients connected, exiting")
  72. process.exit()
  73. else
  74. logger.log {connectedClients}, "clients still connected, not shutting down yet"
  75. setTimeout () ->
  76. shutdownCleanly(signal)
  77. , 10000
  78. forceDrain = ->
  79. logger.log {delay_ms:Settings.forceDrainMsDelay}, "starting force drain after timeout"
  80. setTimeout ()->
  81. logger.log "starting drain"
  82. DrainManager.startDrain(io, 4)
  83. , Settings.forceDrainMsDelay
  84. shutDownInProgress = false
  85. if Settings.forceDrainMsDelay?
  86. Settings.forceDrainMsDelay = parseInt(Settings.forceDrainMsDelay, 10)
  87. logger.log forceDrainMsDelay: Settings.forceDrainMsDelay,"forceDrainMsDelay enabled"
  88. for signal in ['SIGINT', 'SIGHUP', 'SIGQUIT', 'SIGUSR1', 'SIGUSR2', 'SIGTERM', 'SIGABRT']
  89. process.on signal, ->
  90. if shutDownInProgress
  91. logger.log signal: signal, "shutdown already in progress, ignoring signal"
  92. return
  93. else
  94. shutDownInProgress = true
  95. logger.log signal: signal, "received interrupt, cleaning up"
  96. shutdownCleanly(signal)
  97. forceDrain()
  98. if Settings.continualPubsubTraffic
  99. console.log "continualPubsubTraffic enabled"
  100. pubSubClient = redis.createClient(Settings.redis.documentupdater)
  101. publishJob = (channel, cb)->
  102. json = JSON.stringify({health_check:true, date: new Date().toString()})
  103. logger.debug {channel:channel}, "sending pub to keep connection alive"
  104. pubSubClient.publish channel, json, (err)->
  105. if err?
  106. logger.err {err, channel}, "error publishing pubsub traffic to redis"
  107. cb(err)
  108. runPubSubTraffic = ->
  109. async.map ["applied-ops", "editor-events"], publishJob, (err)->
  110. setTimeout(runPubSubTraffic, 1000 * 60)
  111. runPubSubTraffic()