UNPKG

expresser

Version:

A ready to use Node.js web app wrapper, built on top of Express.

245 lines (197 loc) 9.97 kB
# EXPRESSER APP # ----------------------------------------------------------------------------- # The Express app server. By default it will run on HTTP port 8080 # <!-- # @see settings.app # --> class App express = require "express" fs = require "fs" http = require "http" https = require "https" lodash = require "lodash" net = require "net" path = require "path" # Current node environment and HTTP server handler are set on init. nodeEnv = null # Internal modules will be set on `init`. firewall = null logger = null settings = null sockets = null utils = null # @property [Object] Exposes the Express HTTP or HTTPS `server` object. server: null # @property [Array<Object>] Array of additional middlewares to be used # by the Express server. These will be called before anything is processed, # so should be used for things that need immediate processing # (firewall, for example). prependMiddlewares: [] # @property [Array<Object>] Array of additional middlewares to be used # by the Express server. Please note that if you're adding middlewares # manually you must do it BEFORE calling `init`. appendMiddlewares: [] # INIT # -------------------------------------------------------------------------- # Init the Express server. Firewall and Sockets modules will be # used only if available and enabled on the settings. # @param [Object] options App init options. If passed as an array, assume it's the array with extra middlewares. # @option options [Array] appendMiddlewares Array with extra middlewares to be loaded. init: (options) => if lodash.isArray options options = {appendMiddlewares: options} else if not options? options = {} # Load settings and utils. settings = require "./settings.coffee" utils = require "./utils.coffee" nodeEnv = process.env.NODE_ENV # Set default error handler. @setErrorHandler() # Require logger. logger = require "./logger.coffee" logger.debug "App", "init", options # Configure Express server and start server. @configureServer options @startServer() # Log process termination to the console. This will force flush any buffered logs to disk. setErrorHandler: => process.on "exit", (sig) -> console.warn "App", "Terminating Expresser app...", Date(Date.now()), sig try logger.flushLocal() catch ex console.warn "App", "Could not flush buffered logs to disk.", ex.message # Configure the server. Set views, options, use Express modules, etc. configureServer: (options) => midBodyParser = require "body-parser" midCookieParser = require "cookie-parser" midSession = require "cookie-session" midCompression = require "compression" if nodeEnv is "development" midErrorHandler = require "errorhandler" # Create express v4 app. @server = express() settings.updateFromPaaS() if settings.app.paas # Set view options, use Jade for HTML templates. @server.set "views", settings.path.viewsDir @server.set "view engine", settings.app.viewEngine @server.set "view options", { layout: false } # Check for extra middlewares to be added before any other middlewares. if options.prependMiddlewares? if lodash.isArray options.prependMiddlewares @prependMiddlewares.push mw for mw in options.prependMiddlewares else @prependMiddlewares.push options.prependMiddlewares # Prepend middlewares, if any was specified. if @prependMiddlewares.length > 0 @server.use mw for mw in @prependMiddlewares # Enable firewall? if settings.firewall?.enabled firewall = require "./firewall.coffee" firewall.bind @server # Use Express basic handlers. @server.use midBodyParser.json() @server.use midBodyParser.urlencoded {extended: true} @server.use midCookieParser settings.app.cookieSecret if settings.app.cookieEnabled @server.use midSession {secret: settings.app.sessionSecret} if settings.app.sessionEnabled # Use HTTP compression only if enabled on settings. @server.use midCompression if settings.app.compressionEnabled # Fix connect assets helper context. connectAssetsOptions = lodash.cloneDeep settings.app.connectAssets connectAssetsOptions.helperContext = @server.locals # Connect assets and dynamic compiling. ConnectAssets = (require "connect-assets") connectAssetsOptions @server.use ConnectAssets # Check for extra middlewares to be added. if options.appendMiddlewares? if lodash.isArray options.appendMiddlewares @appendMiddlewares.push mw for mw in options.appendMiddlewares else @appendMiddlewares.push options.appendMiddlewares # Add more middlewares, if any (for example passport for authentication). if @appendMiddlewares.length > 0 @server.use mw for mw in @appendMiddlewares # Configure development environment to dump exceptions and show stack. if nodeEnv is "development" @server.use midErrorHandler {dumpExceptions: true, showStack: true} # Configure production environment. if nodeEnv is "production" @server.use midErrorHandler() # Use Express static routing. @server.use express.static settings.path.publicDir # If debug is on, log requests to the console. if settings.general.debug @server.use (req, res, next) => ip = utils.getClientIP req method = req.method url = req.url # Check if request flash is present before logging. if req.flash? and lodash.isFunction req.flash console.log "Request from #{ip}", method, url, req.flash() else console.log "Request from #{ip}", method, url next() if next? # Start the server using HTTP or HTTPS, depending on the settings. startServer: => if settings.app.ssl.enabled and settings.app.ssl.keyFile? and settings.app.ssl.certFile? sslKeyFile = utils.getFilePath settings.app.ssl.keyFile sslCertFile = utils.getFilePath settings.app.ssl.certFile # Certificate files were found? Proceed, otherwise alert the user and throw an error. if sslKeyFile? and sslCertFile? sslKey = fs.readFileSync sslKeyFile, {encoding: settings.general.encoding} sslCert = fs.readFileSync sslCertFile, {encoding: settings.general.encoding} sslOptions = {key: sslKey, cert: sslCert} server = https.createServer sslOptions, @server else throw new Error "The certificate files could not be found. Please check the 'settings.app.ssl' path settings." else server = http.createServer @server # Enable sockets? if settings.sockets?.enabled sockets = @expresser.sockets sockets.bind server if settings.app.ip? and settings.app.ip isnt "" server.listen settings.app.port, settings.app.ip logger.info "App", settings.app.title, "Listening on #{settings.app.ip} - #{settings.app.port}" else server.listen settings.app.port logger.info "App", settings.app.title, "Listening on #{settings.app.port}" # Using SSL and redirector port is set? Then create the http server. if settings.app.ssl.enabled and settings.app.ssl.redirectorPort > 0 logger.info "App", "#{settings.app.title} will redirect HTTP #{settings.app.ssl.redirectorPort} to HTTPS on #{settings.app.port}." redirServer = express() redirServer.get "*", (req, res) -> res.redirect "https://#{req.hostname}:#{settings.app.port}#{req.url}" @redirectorServer = http.createServer redirServer @redirectorServer.listen settings.app.ssl.redirectorPort # HELPER AND UTILS # -------------------------------------------------------------------------- # Helper to render pages. The request, response and view are mandatory, # and the options argument is optional. # @param [Object] req The request object. # @param [Object] res The response object. # @param [String] view The view name. # @param [Object] options Options passed to the view, optional. renderView: (req, res, view, options) => options = {} if not options? options.device = utils.getClientDevice req options.title = settings.app.title if not options.title? res.render view, options logger.debug "App", "Render", view, options # Helper to send error responses. When the server can't return a valid result, # send an error response with the specified status code. # @param [Object] req The response object. # @param [String] message The message to be sent to the client. # @param [Integer] statusCode The response status code, optional, default is 500. renderError: (res, message, statusCode) => message = JSON.stringify message statusCode = 500 if not statusCode? res.statusCode = 500 res.send "Server error: #{message}" logger.error "App", "HTTP Error", statusCode, message, res # Singleton implementation # -------------------------------------------------------------------------- App.getInstance = -> @instance = new App() if not @instance? return @instance module.exports = exports = App.getInstance()