octoblu/meshblu-server-http

View on GitHub
src/server.coffee

Summary

Maintainability
Test Coverage
_                       = require 'lodash'
colors                  = require 'colors'
Redis                   = require 'ioredis'
octobluExpress          = require 'express-octoblu'
RedisNS                 = require '@octoblu/redis-ns'
RateLimitChecker        = require 'meshblu-core-rate-limit-checker'
JobLogger               = require 'job-logger'
debug                   = require('debug')('meshblu-core-protocol-adapter-http:server')
{ JobManagerRequester } = require 'meshblu-core-job-manager'
enableDestroy           = require 'server-destroy'

Router                  = require './router'
JobToHttp               = require './helpers/job-to-http'
MeshbluAuthParser       = require './helpers/meshblu-auth-parser'

class Server
  constructor: (options)->
    {
      @disableLogging
      @port
      @aliasServerUri
      @redisUri
      @cacheRedisUri
      @namespace
      @maxConnections
      @jobTimeoutSeconds
      @jobLogSampleRate
      @jobLogSampleRateOverrideUuids
      @jobLogRedisUri
      @jobLogQueue
      @requestQueueName
      @responseQueueName
    } = options
    @panic 'missing @jobLogQueue', 2 unless @jobLogQueue?
    @panic 'missing @jobLogRedisUri', 2 unless @jobLogRedisUri?
    @panic 'missing @redisUri', 2 unless @redisUri?
    @panic 'missing @cacheRedisUri', 2 unless @cacheRedisUri?
    @panic 'missing @jobLogSampleRate', 2 unless @jobLogSampleRate?
    @panic 'missing @requestQueueName', 2 if _.isEmpty @requestQueueName
    @panic 'missing @responseQueueName', 2 if _.isEmpty @responseQueueName

    @cacheClient = new Redis @cacheRedisUri, dropBufferSupport: true
    rateLimitCheckerClient = new RedisNS 'meshblu-count', @cacheClient
    @rateLimitChecker = new RateLimitChecker client: rateLimitCheckerClient
    @authParser = new MeshbluAuthParser

  address: =>
    @server.address()

  panic: (message, exitCode, error) =>
    error ?= new Error('generic error')
    console.error colors.red message
    console.error error?.stack
    process.exit exitCode

  run: (callback) =>
    app = octobluExpress({ @disableLogging, bodyLimit: '10mb' })

    app.use '/proofoflife', (req, res) =>
      @jobManager.healthcheck (error, healthy) =>
        return res.sendError error if error?
        return res.sendError new Error("Job Manager Unhealthy") unless healthy
        @cacheClient.set 'test:write', Date.now(), (error) =>
          return res.sendError error if error?
          res.send online: true

    rateLimit = (req, res, next) =>
      as = req.get 'x-meshblu-as'
      auth = @authParser.parse req
      uuid = as ? auth?.uuid
      return next() unless uuid?
      @rateLimitChecker.isLimited {uuid}, (error, result) =>
        return res.sendError error if error?
        return res.status(429).send(message: 'Too Many Requests') if result
        next()
    app.use rateLimit

    jobLogger = new JobLogger
      client: new Redis @jobLogRedisUri, dropBufferSupport: true
      indexPrefix: 'metric:meshblu-core-protocol-adapter-http'
      type: 'meshblu-core-protocol-adapter-http:request'
      jobLogQueue: @jobLogQueue

    @jobManager = new JobManagerRequester {
      @namespace
      @redisUri
      @jobTimeoutSeconds
      @jobLogSampleRate
      @jobLogSampleRateOverrideUuids
      @requestQueueName
      @responseQueueName
      queueTimeoutSeconds: @jobTimeoutSeconds
      maxConnections: 2
    }

    @jobManager.once 'error', (error) =>
      @stop =>
        @panic 'fatal job manager error', 1, error

    @jobManager.once 'factoryCreateError', (error) =>
      @stop =>
        @panic 'fatal job manager factoryCreateError', 1, error

    @jobManager._do = @jobManager.do
    @jobManager.do = (request, callback) =>
      @jobManager._do request, (error, response) =>
        jobLogger.log { error, request, response }, (jobLoggerError) =>
          return callback jobLoggerError if jobLoggerError?
          callback error, response

    @jobManager.start (error) =>
      return callback error if error?

      jobToHttp = new JobToHttp
      router = new Router { @jobManager, jobToHttp }
      router.route app

      @server = app.listen @port, (error) =>
        return callback error if error?
        debug 'meshblu-http listening on port', @port
        callback null
      enableDestroy @server

  destroy: (callback) =>
    @server.destroy =>
      @jobManager.stop callback

  stop: (callback) =>
    @server.close =>
      @jobManager.stop callback

module.exports = Server