| @@ -3,3 +3,4 @@ node_modules | |||
| npm-debug.log | |||
| imgs | |||
| !imgs/.placeholder | |||
| .currentRun | |||
| @@ -24,5 +24,6 @@ | |||
| "dir": { | |||
| "imgs": "imgs" | |||
| }, | |||
| "debug": false | |||
| "debug": false, | |||
| "maxRuns": 9999 | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| var formidable = require("formidable"); | |||
| var crypto = require("crypto"); | |||
| var zlib = require("zlib"); | |||
| var preprocess = require("./preprocess.js"); | |||
| var sessions = {}; | |||
| @@ -8,7 +9,10 @@ function templatify(str, args, ctx, env) { | |||
| env.url = ctx.req.url; | |||
| str = preprocess(str, { | |||
| session: ctx.session, | |||
| session: function(key) { | |||
| ctx.cachable = false; | |||
| return ctx.session[key]; | |||
| }, | |||
| arg: function(key) { | |||
| return ctx.htmlEntities(args[key]); | |||
| }, | |||
| @@ -22,6 +26,8 @@ function templatify(str, args, ctx, env) { | |||
| return str; | |||
| } | |||
| var cache = {}; | |||
| module.exports = function(options) { | |||
| this.req = options.req; | |||
| this.res = options.res; | |||
| @@ -30,6 +36,11 @@ module.exports = function(options) { | |||
| this.db = options.db; | |||
| this.conf = options.conf; | |||
| this.query = this.req.url.split("?")[1] || ""; | |||
| this.shouldGzip = /gzip/.test(this.req.headers["accept-encoding"]); | |||
| this.cachable = true; | |||
| this.statusCode = 200; | |||
| this.headers = {}; | |||
| if (this.conf.debug) | |||
| this.startTime = new Date(); | |||
| @@ -70,7 +81,7 @@ module.exports = function(options) { | |||
| } | |||
| module.exports.prototype = { | |||
| end: function(str) { | |||
| _end: function(str) { | |||
| if (this.conf.debug) { | |||
| var ms = (new Date().getTime() - this.startTime.getTime()); | |||
| console.log( | |||
| @@ -82,17 +93,32 @@ module.exports.prototype = { | |||
| conole.log(this.req.url); | |||
| } | |||
| if (this.statusCode) | |||
| this.res.writeHead(this.statusCode); | |||
| this.res.writeHead(this.statusCode, this.headers); | |||
| this.res.end(str); | |||
| }, | |||
| end: function(str, alreadyGzipped) { | |||
| if (!this.shouldGzip) | |||
| return this._end(str); | |||
| this.setHeader("Content-Encoding", "gzip"); | |||
| if (alreadyGzipped) | |||
| return this._end(str); | |||
| zlib.gzip(str, function(err, res) { | |||
| if (err) | |||
| throw err; | |||
| this._end(res); | |||
| }.bind(this)); | |||
| }, | |||
| succeed: function(obj) { | |||
| obj = obj || {}; | |||
| obj.success = true; | |||
| this.res.setHeader("Content-Type", "application/json"); | |||
| this.setHeader("Content-Type", "application/json"); | |||
| this.end(JSON.stringify(obj)); | |||
| }, | |||
| @@ -101,7 +127,7 @@ module.exports.prototype = { | |||
| console.log("Sending error to client:"); | |||
| console.trace(err); | |||
| this.res.setHeader("Content-Type", "application/json"); | |||
| this.setHeader("Content-Type", "application/json"); | |||
| obj = {}; | |||
| obj.success = false; | |||
| @@ -122,6 +148,9 @@ module.exports.prototype = { | |||
| if (!str) | |||
| throw new Error("No such view: "+name); | |||
| if (name === "404") | |||
| this.setStatus(404); | |||
| return templatify(str, args, this, {view: name}); | |||
| }, | |||
| @@ -149,6 +178,10 @@ module.exports.prototype = { | |||
| this.statusCode = code; | |||
| }, | |||
| setHeader: function(key, val) { | |||
| this.headers[key] = val; | |||
| }, | |||
| login: function(username, id) { | |||
| this.session.loggedIn = true; | |||
| this.session.username = username; | |||
| @@ -1,4 +1,5 @@ | |||
| var fs = require("fs"); | |||
| var zlib = require("zlib"); | |||
| var minify = require("./minify.js"); | |||
| var includeHtml = require("./includeHtml.js"); | |||
| @@ -22,18 +23,20 @@ exports.load = function(endpoints, conf) { | |||
| //If it doesn't end with .node.js, it's a regular text file and will | |||
| //just be served as is | |||
| } else { | |||
| res.endpoints[i] = fs.readFileSync(conf.webroot+"/"+ep, "utf8"); | |||
| var str = fs.readFileSync(conf.webroot+"/"+ep, "utf8"); | |||
| //If it's an HTML file, we minify it | |||
| if (!conf.minify) { | |||
| //Don't minify unless the conf tells us to | |||
| } else if (/\.html$/.test(ep)) { | |||
| res.endpoints[i] = minify.html(res.endpoints[i]); | |||
| str = minify.html(str); | |||
| } else if (/\.js$/.test(ep)) { | |||
| res.endpoints[i] = minify.js(res.endpoints[i]); | |||
| str = minify.js(str); | |||
| } else if (/\.css$/.test(ep)) { | |||
| res.endpoints[i] = minify.css(res.endpoints[i]); | |||
| str = minify.css(str); | |||
| } | |||
| res.endpoints[i] = str; | |||
| } | |||
| //Errors will usually be because an endpoint doesn't exist | |||
| @@ -2,6 +2,7 @@ var http = require("http"); | |||
| var https = require("https"); | |||
| var fs = require("fs"); | |||
| var domain = require("domain"); | |||
| var zlib = require("zlib"); | |||
| var loader = require("./lib/loader.js"); | |||
| var pg = require("pg"); | |||
| var Context = require("./lib/context.js"); | |||
| @@ -53,10 +54,30 @@ var endpoints = { | |||
| "/api/account_change_password": "api/account_change_password.node.js" | |||
| } | |||
| //We cache static resources for a long time. However, we want to invalidate | |||
| //the browser's cache whenever a file updates. Therefore, we append | |||
| //a number to all static files, and the number increases every time we start | |||
| //the server. currentRun is that number. | |||
| var currentRun; | |||
| try { | |||
| currentRun = parseInt(fs.readFileSync(".currentRun", "utf8")); | |||
| } catch (err) { | |||
| if (err.code === "ENOENT") | |||
| currentRun = 0; | |||
| else | |||
| throw err; | |||
| } | |||
| currentRun = (currentRun >= conf.maxRuns ? 0 : currentRun); | |||
| currentRun = (currentRun || 0) + 1; | |||
| conf.web.currentRun = currentRun.toString(); | |||
| fs.writeFileSync(".currentRun", currentRun, "utf8"); | |||
| var loaded = loader.load(endpoints, conf); | |||
| var db = new pg.Client(conf.db); | |||
| var gzipCache = {}; | |||
| //Function to run on each request | |||
| function onRequest(req, res) { | |||
| var ctx = new Context({ | |||
| @@ -80,13 +101,27 @@ function onRequest(req, res) { | |||
| if (typeof ep == "function") { | |||
| ep(ctx); | |||
| } else { | |||
| ctx.end(ep); | |||
| //Cache content for a year | |||
| ctx.setHeader("Cache-Control", "public, max-age=31536000"); | |||
| //Gzip and such | |||
| if (ctx.shouldGzip && gzipCache[req.url]) { | |||
| ctx.end(gzipCache[req.url], true); | |||
| } else if (ctx.shouldGzip) { | |||
| zlib.gzip(ep, function(err, res) { | |||
| gzipCache[req.url] = res; | |||
| ctx.end(res, true); | |||
| }); | |||
| } else { | |||
| ctx.end(ep); | |||
| } | |||
| } | |||
| } | |||
| //Initiate a postgresql client | |||
| db.connect(function() { | |||
| //Create HTTP or HTTPS server | |||
| var server; | |||
| if (conf.use_https) { | |||
| @@ -109,40 +144,3 @@ if (!conf.debug) { | |||
| console.trace(err); | |||
| }); | |||
| } | |||
| function command(tokens) { | |||
| switch(tokens[0]) { | |||
| //Reload configuration | |||
| case "reload-conf": | |||
| var c = JSON.parse(fs.readFileSync("conf.json")); | |||
| for (var i in c) | |||
| conf[i] = c[i]; | |||
| break; | |||
| //Reload HTML | |||
| case "reload-html": | |||
| var l = loader.load(endpoints, conf); | |||
| for (var i in l) | |||
| loaded[i] = l[i]; | |||
| break; | |||
| //Reload everything | |||
| case "reload": | |||
| command(["reload-conf"]); | |||
| command(["reload-html"]); | |||
| break; | |||
| default: return false; | |||
| } | |||
| return true; | |||
| } | |||
| process.stdin.on("data", function(line) { | |||
| var tokens = line.toString().split(/\s+/); | |||
| if (command(tokens)) { | |||
| return console.log(tokens[0]+" completed successfully."); | |||
| } else { | |||
| return console.log("Command not found: "+tokens[0]); | |||
| } | |||
| }); | |||
| @@ -2,11 +2,11 @@ | |||
| <meta charset="utf-8"> | |||
| <meta name="viewport" content="width=device-width"> | |||
| <link rel="stylesheet" href="/global.css"> | |||
| <link rel="stylesheet" href="/global.css?{{conf#currentRun}}"> | |||
| <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css"> | |||
| <link rel="stylesheet" href="/{{env#view}}/style.css"> | |||
| <link rel="stylesheet" href="/{{env#view}}/style.css?{{conf#currentRun}}"> | |||
| <script src="https://code.jquery.com/jquery-2.1.4.js"></script> | |||
| <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script> | |||
| <script src="/global.js"></script> | |||
| <script src="/{{env#view}}/script.js"></script> | |||
| <script src="/global.js?{{conf#currentRun}}"></script> | |||
| <script src="/{{env#view}}/script.js?{{conf#currentRun}}"></script> | |||