sevastos/tiner

View on GitHub
lib/server.js

Summary

Maintainability
A
2 hrs
Test Coverage
'use strict';

var http = require('http'),
    path = require('path'),
    url  = require('url'),
    fs   = require('fs'),
    mime = require(path.join(__dirname, 'mime')),

    noop = function() {};


/**
 * Start server
 *
 * @param  {Number} port
 * @param  {String?} root
 * @param  {Function?} ready
 * @return {Server}
 */
var Server = function Server(port, root, ready) {
  ready = (ready ? ready : noop);
  if (!root || typeof root === 'function') {
    ready = (root ? root : noop);
    root = process.cwd();
  }

  this.root = path.normalize(root);
  this.port = port;
  this.listens = false;
  this.http = http.createServer(Server.receive.bind(this));
  this.http.on('error', function(e){
    Server.onHttpServerError(e, ready);
  });
  this.http.on('close', function(e){
    this.listens = false;
  });
  this.http.listen(port, function() {
  this.listens = true;
    ready();
  });

  return this;
};

/**
 * Handle request
 *
 * @param  {Request} request
 * @param  {Response} response
 */
Server.receive = function(request, response) {
  var uri = request.url;

  uri = url.parse(uri).pathname;
  uri = url.resolve('/', uri);
  uri = uri.replace(/\/~(\/|$)/g, '/\\~/');
  uri = url.resolve('/', uri);

  var file = path.join(this.root, uri);
  Server.serve(file, response);
};

/**
 * Serve file
 *
 * @param  {String} file
 * @param  {Response} response
 */
Server.serve = function(file, response) {

  fs.readFile(file, function(err, data) {
    var status = 418;
    var contentType = 'text/plain';
    var resBody = 'An Unexpected TeaPot';
    var dataType;
    if (err) {
      if (err.code === 'ENOENT') {
        status = 404;
        resBody = ['GET local ' + file, 'Not Found (404)'].join('\n');
      } else if (err.code === 'EISDIR') {
        if (file[file.length - 1] !== path.sep) {
          file += path.sep;
        }
        file += 'index.html';
        Server.serve(file, response);
        return;
      } else {
        console.log('Unexpected error when serving:', file);
        console.log('Error: =>', err);
        status = 500;
        resBody = err;
      }
    } else {
      contentType = mime.lookup(file);
      status = 200;
      dataType = 'binary';
      resBody = data;
    }
    response.setHeader('Content-Type', contentType);
    response.writeHead(status);
    response.write(resBody, dataType);
    response.end();
  });

};

/**
 * Handle node.http server errors
 * @param  {Object}   e    Error object
 * @param  {Function} next
 */
Server.onHttpServerError = function(e, next) {
  switch(e.code) {
    case 'EADDRINUSE':
      e.msg = 'Address/Port is already in use. Try another port.';
      next(e);
      break;
    default:
      e.msg = 'An error occured:' + e.code;
      next(e);
  }
};

/**
 * Shutdown server
 *
 * @param  {Function} next
 */
Server.prototype.shutdown = function(next) {
  if (!this.listens) {
    return next();
  }
  if (this.http) {
    try {
      this.http.close(next);
    } catch (e) {
      console.log('Something unexpected:', e);
      next();
    }
  }
};

module.exports = exports = Server;