Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial commit

  • Loading branch information...
commit 95c302c8981ae2e4b2436985493688b705ee48f2 0 parents
@dresende authored
15 README.md
@@ -0,0 +1,15 @@
+# Navajo HTTP Server
+
+The purpose of this project is to create a small and simple web server with some
+of the most common features of the Apache2 server. For now it's not really usable.
+
+## Features Planned
+
+- Apache2 log format
+- Ability to use logrotate
+- Ability to have specific configuration for specific folders
+- Virtual hosting
+- Run javascript on the server
+- Use Connect to use all server cores
+- Have WebSocket available for javascript running on the server, out of the box
+- Simple API for the files running on the server
292 core/print.js
@@ -0,0 +1,292 @@
+exports.log = log;
+exports.logFormat = "%a - - %t \"%r\" %>s %b %{Referer}i %{User-Agent}i";
+
+function log(req, status, file, size) {
+ console.log(exports.logFormat.replace(/\%(\x3e?[a-z]|\{[a-z\-]+\}[i])/ig, function (m) {
+ switch (m) {
+ case "%a": return req.connection.remoteAddress;
+ case "%b": return size || "-";
+ case "%B": return size || "0";
+ case "%f": return file || "-";
+ case "%H": return "HTTP";
+ case "%m": return req.method;
+ case "%r": return [ req.method, req.url, "HTTP/" + req.httpVersion ].join(" ");
+ case "%>s":return status;
+ case "%t": return "[" + date("d/M/Y:H:i:s O") + "]";
+ case "%U": return req.url;
+ default:
+ var n = m.match(/\%{([a-z\-]+)\}([a-z])/i);
+ if (!n) return "?";
+
+ switch (n[2]) {
+ case "i":
+ var h = n[1].toLowerCase();
+ return "\"" + (req.headers.hasOwnProperty(h) ? req.headers[h] : "-") + "\"";
+ }
+
+ return "?";
+ }
+ }));
+}
+
+function date (format, timestamp) {
+ // http://kevin.vanzonneveld.net
+ // + original by: Carlos R. L. Rodrigues (http://www.jsfromhell.com)
+ // + parts by: Peter-Paul Koch (http://www.quirksmode.org/js/beat.html)
+ // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ // + improved by: MeEtc (http://yass.meetcweb.com)
+ // + improved by: Brad Touesnard
+ // + improved by: Tim Wiel
+ // + improved by: Bryan Elliott
+ //
+ // + improved by: Brett Zamir (http://brett-zamir.me)
+ // + improved by: David Randall
+ // + input by: Brett Zamir (http://brett-zamir.me)
+ // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ // + improved by: Brett Zamir (http://brett-zamir.me)
+ // + improved by: Brett Zamir (http://brett-zamir.me)
+ // + improved by: Theriault
+ // + derived from: gettimeofday
+ // + input by: majak
+ // + bugfixed by: majak
+ // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
+ // + input by: Alex
+ // + bugfixed by: Brett Zamir (http://brett-zamir.me)
+ // + improved by: Theriault
+ // + improved by: Brett Zamir (http://brett-zamir.me)
+ // + improved by: Theriault
+ // + improved by: Thomas Beaucourt (http://www.webapp.fr)
+ // + improved by: JT
+ // + improved by: Theriault
+ // + improved by: Rafał Kukawski (http://blog.kukawski.pl)
+ // + input by: Martin
+ // + input by: Alex Wilson
+ // % note 1: Uses global: php_js to store the default timezone
+ // % note 2: Although the function potentially allows timezone info (see notes), it currently does not set
+ // % note 2: per a timezone specified by date_default_timezone_set(). Implementers might use
+ // % note 2: this.php_js.currentTimezoneOffset and this.php_js.currentTimezoneDST set by that function
+ // % note 2: in order to adjust the dates in this function (or our other date functions!) accordingly
+ // * example 1: date('H:m:s \\m \\i\\s \\m\\o\\n\\t\\h', 1062402400);
+ // * returns 1: '09:09:40 m is month'
+ // * example 2: date('F j, Y, g:i a', 1062462400);
+ // * returns 2: 'September 2, 2003, 2:26 am'
+ // * example 3: date('Y W o', 1062462400);
+ // * returns 3: '2003 36 2003'
+ // * example 4: x = date('Y m d', (new Date()).getTime()/1000);
+ // * example 4: (x+'').length == 10 // 2009 01 09
+ // * returns 4: true
+ // * example 5: date('W', 1104534000);
+ // * returns 5: '53'
+ // * example 6: date('B t', 1104534000);
+ // * returns 6: '999 31'
+ // * example 7: date('W U', 1293750000.82); // 2010-12-31
+ // * returns 7: '52 1293750000'
+ // * example 8: date('W', 1293836400); // 2011-01-01
+ // * returns 8: '52'
+ // * example 9: date('W Y-m-d', 1293974054); // 2011-01-02
+ // * returns 9: '52 2011-01-02'
+ var that = this,
+ jsdate, f, formatChr = /\\?([a-z])/gi,
+ formatChrCb,
+ // Keep this here (works, but for code commented-out
+ // below for file size reasons)
+ //, tal= [],
+ _pad = function (n, c) {
+ if ((n = n + '').length < c) {
+ return new Array((++c) - n.length).join('0') + n;
+ }
+ return n;
+ },
+ txt_words = ["Sun", "Mon", "Tues", "Wednes", "Thurs", "Fri", "Satur", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
+ formatChrCb = function (t, s) {
+ return f[t] ? f[t]() : s;
+ };
+ f = {
+ // Day
+ d: function () { // Day of month w/leading 0; 01..31
+ return _pad(f.j(), 2);
+ },
+ D: function () { // Shorthand day name; Mon...Sun
+ return f.l().slice(0, 3);
+ },
+ j: function () { // Day of month; 1..31
+ return jsdate.getDate();
+ },
+ l: function () { // Full day name; Monday...Sunday
+ return txt_words[f.w()] + 'day';
+ },
+ N: function () { // ISO-8601 day of week; 1[Mon]..7[Sun]
+ return f.w() || 7;
+ },
+ S: function () { // Ordinal suffix for day of month; st, nd, rd, th
+ var j = f.j();
+ return j > 4 || j < 21 ? 'th' : {1: 'st', 2: 'nd', 3: 'rd'}[j % 10] || 'th';
+ },
+ w: function () { // Day of week; 0[Sun]..6[Sat]
+ return jsdate.getDay();
+ },
+ z: function () { // Day of year; 0..365
+ var a = new Date(f.Y(), f.n() - 1, f.j()),
+ b = new Date(f.Y(), 0, 1);
+ return Math.round((a - b) / 864e5) + 1;
+ },
+
+ // Week
+ W: function () { // ISO-8601 week number
+ var a = new Date(f.Y(), f.n() - 1, f.j() - f.N() + 3),
+ b = new Date(a.getFullYear(), 0, 4);
+ return _pad(1 + Math.round((a - b) / 864e5 / 7), 2);
+ },
+
+ // Month
+ F: function () { // Full month name; January...December
+ return txt_words[6 + f.n()];
+ },
+ m: function () { // Month w/leading 0; 01...12
+ return _pad(f.n(), 2);
+ },
+ M: function () { // Shorthand month name; Jan...Dec
+ return f.F().slice(0, 3);
+ },
+ n: function () { // Month; 1...12
+ return jsdate.getMonth() + 1;
+ },
+ t: function () { // Days in month; 28...31
+ return (new Date(f.Y(), f.n(), 0)).getDate();
+ },
+
+ // Year
+ L: function () { // Is leap year?; 0 or 1
+ return new Date(f.Y(), 1, 29).getMonth() === 1 | 0;
+ },
+ o: function () { // ISO-8601 year
+ var n = f.n(),
+ W = f.W(),
+ Y = f.Y();
+ return Y + (n === 12 && W < 9 ? -1 : n === 1 && W > 9);
+ },
+ Y: function () { // Full year; e.g. 1980...2010
+ return jsdate.getFullYear();
+ },
+ y: function () { // Last two digits of year; 00...99
+ return (f.Y() + "").slice(-2);
+ },
+
+ // Time
+ a: function () { // am or pm
+ return jsdate.getHours() > 11 ? "pm" : "am";
+ },
+ A: function () { // AM or PM
+ return f.a().toUpperCase();
+ },
+ B: function () { // Swatch Internet time; 000..999
+ var H = jsdate.getUTCHours() * 36e2,
+ // Hours
+ i = jsdate.getUTCMinutes() * 60,
+ // Minutes
+ s = jsdate.getUTCSeconds(); // Seconds
+ return _pad(Math.floor((H + i + s + 36e2) / 86.4) % 1e3, 3);
+ },
+ g: function () { // 12-Hours; 1..12
+ return f.G() % 12 || 12;
+ },
+ G: function () { // 24-Hours; 0..23
+ return jsdate.getHours();
+ },
+ h: function () { // 12-Hours w/leading 0; 01..12
+ return _pad(f.g(), 2);
+ },
+ H: function () { // 24-Hours w/leading 0; 00..23
+ return _pad(f.G(), 2);
+ },
+ i: function () { // Minutes w/leading 0; 00..59
+ return _pad(jsdate.getMinutes(), 2);
+ },
+ s: function () { // Seconds w/leading 0; 00..59
+ return _pad(jsdate.getSeconds(), 2);
+ },
+ u: function () { // Microseconds; 000000-999000
+ return _pad(jsdate.getMilliseconds() * 1000, 6);
+ },
+
+ // Timezone
+ e: function () { // Timezone identifier; e.g. Atlantic/Azores, ...
+ // The following works, but requires inclusion of the very large
+ // timezone_abbreviations_list() function.
+/* return this.date_default_timezone_get();
+*/
+ throw 'Not supported (see source code of date() for timezone on how to add support)';
+ },
+ I: function () { // DST observed?; 0 or 1
+ // Compares Jan 1 minus Jan 1 UTC to Jul 1 minus Jul 1 UTC.
+ // If they are not equal, then DST is observed.
+ var a = new Date(f.Y(), 0),
+ // Jan 1
+ c = Date.UTC(f.Y(), 0),
+ // Jan 1 UTC
+ b = new Date(f.Y(), 6),
+ // Jul 1
+ d = Date.UTC(f.Y(), 6); // Jul 1 UTC
+ return 0 + ((a - c) !== (b - d));
+ },
+ O: function () { // Difference to GMT in hour format; e.g. +0200
+ var a = jsdate.getTimezoneOffset();
+ return (a > 0 ? "-" : "+") + _pad(Math.abs(a / 60 * 100), 4);
+ },
+ P: function () { // Difference to GMT w/colon; e.g. +02:00
+ var O = f.O();
+ return (O.substr(0, 3) + ":" + O.substr(3, 2));
+ },
+ T: function () { // Timezone abbreviation; e.g. EST, MDT, ...
+ // The following works, but requires inclusion of the very
+ // large timezone_abbreviations_list() function.
+/* var abbr = '', i = 0, os = 0, default = 0;
+ if (!tal.length) {
+ tal = that.timezone_abbreviations_list();
+ }
+ if (that.php_js && that.php_js.default_timezone) {
+ default = that.php_js.default_timezone;
+ for (abbr in tal) {
+ for (i=0; i < tal[abbr].length; i++) {
+ if (tal[abbr][i].timezone_id === default) {
+ return abbr.toUpperCase();
+ }
+ }
+ }
+ }
+ for (abbr in tal) {
+ for (i = 0; i < tal[abbr].length; i++) {
+ os = -jsdate.getTimezoneOffset() * 60;
+ if (tal[abbr][i].offset === os) {
+ return abbr.toUpperCase();
+ }
+ }
+ }
+*/
+ return 'UTC';
+ },
+ Z: function () { // Timezone offset in seconds (-43200...50400)
+ return -jsdate.getTimezoneOffset() * 60;
+ },
+
+ // Full Date/Time
+ c: function () { // ISO-8601 date.
+ return 'Y-m-d\\Th:i:sP'.replace(formatChr, formatChrCb);
+ },
+ r: function () { // RFC 2822
+ return 'D, d M Y H:i:s O'.replace(formatChr, formatChrCb);
+ },
+ U: function () { // Seconds since UNIX epoch
+ return jsdate.getTime() / 1000 | 0;
+ }
+ };
+ this.date = function (format, timestamp) {
+ that = this;
+ jsdate = ((typeof timestamp === 'undefined') ? new Date() : // Not provided
+ (timestamp instanceof Date) ? new Date(timestamp) : // JS Date()
+ new Date(timestamp * 1000) // UNIX timestamp (auto-convert to int)
+ );
+ return format.replace(formatChr, formatChrCb);
+ };
+ return this.date(format, timestamp);
+}
97 core/utils.js
@@ -0,0 +1,97 @@
+var path = require("path"),
+ fs = require("fs"),
+ print = require("./print"),
+ config = {};
+
+exports.loadConfig = loadConfig;
+exports.processRequest = processRequest;
+exports.setConfig = function (conf) {
+ config = conf;
+}
+
+function loadConfig(path, cb) {
+ fs.readFile(path, function (err, data) {
+ if (err) {
+ return cb(err);
+ }
+ try {
+ data = JSON.parse(data);
+ } catch (e) {
+ return cb(e);
+ }
+
+ return cb(null, data);
+ });
+}
+
+function processRequest(req, res) {
+ replyTo(req.url, req, res);
+}
+
+function replyTo(url, req, res) {
+ var real_path = path.normalize(path.join(config.root, url));
+
+ if (real_path.substr(-1) == "/") {
+ return replyWithIndex(real_path, config.index, 0, req, res);
+ }
+
+ path.exists(real_path, function (exists) {
+ if (exists) {
+ return streamFile(real_path, req, res);
+ }
+ return replyNotFound(req, res);
+ })
+}
+
+function replyWithIndex(base_path, files, index, req, res) {
+ path.exists(base_path + files[index], function (exists) {
+ if (exists) {
+ return streamFile(base_path + files[index], req, res);
+ }
+ if (files.length > index + 1) {
+ return replyWithIndex(base_path, files, index + 1, req, res);
+ }
+ return replyNotFound(req, res);
+ });
+}
+
+function streamFile(file, req, res) {
+ var fd = fs.createReadStream(file);
+
+ fs.stat(file, function (err, stat) {
+ if (!err) {
+ res.setHeader("Content-Length", stat.size);
+ }
+
+ fd.on("data", function (chunk) {
+ res.write(chunk);
+ });
+ fd.on("end", function() {
+ res.end();
+
+ if (err) {
+ print.log(req, 200, file);
+ } else {
+ print.log(req, 200, file, stat.size);
+ }
+ });
+ });
+}
+
+function replyNotFound(req, res) {
+ print.log(req, 404);
+
+ res.writeHead(404, "Not Found", {
+ "Server": "nody"
+ });
+ res.end("404 - Not Found");
+}
+
+function replyInternalError(req, res) {
+ print.log(req, 500);
+
+ res.writeHead(500, "Internal Server Error", {
+ "Server": "nody"
+ });
+ res.end("500 - Internal Server Error");
+}
4 navajo.conf
@@ -0,0 +1,4 @@
+{
+ "bind" : "127.0.0.1:80",
+ "index" : [ "index.html" ]
+}
46 navajo.js
@@ -0,0 +1,46 @@
+/**
+ * Navajo - NodeJS HTTP Server
+ **/
+var utils = require("./core/utils"),
+ config;
+
+// default configuration
+config = {
+ "bind" : "0.0.0.0:80",
+ "root" : __dirname + "/www/",
+ "index" : null
+};
+
+// load configuration
+utils.loadConfig("./navajo.conf", function (err, conf) {
+ if (err) {
+ console.log("ERROR");
+ console.log(err);
+ return;
+ }
+ for (k in conf) {
+ if (conf.hasOwnProperty(k)) config[k] = conf[k];
+ }
+
+ utils.setConfig(config);
+
+ var http = require("http"),
+ server = http.createServer(utils.processRequest),
+ host = "0.0.0.0",
+ port = 80;
+
+ // parse bind (can be host, port or host:port)
+ if (config.bind.indexOf(":") != -1) {
+ host = config.bind.substr(0, config.bind.indexOf(":"));
+ port = parseInt(config.bind.substr(config.bind.indexOf(":") + 1), 10);
+ } else if (config.bind.match(/\d+\.\d+\.\d+\.\d+/)) {
+ host = config.bind;
+ } else if (config.bind.length > 0) {
+ port = parseInt(config.bind, 10);
+ }
+
+ // start server
+ server.listen(port, host, function () {
+ console.log("Server started on %s:%d", host, port);
+ });
+});
9 www/index.html
@@ -0,0 +1,9 @@
+<html>
+ <head>
+ <title>Test</title>
+ <link rel="stylesheet" type="text/css" href="screen.css" />
+ </head>
+ <body>
+ Root file
+ </body>
+</html>
4 www/screen.css
@@ -0,0 +1,4 @@
+body {
+ background: #ccc;
+ color: #444;
+}
Please sign in to comment.
Something went wrong with that request. Please try again.