@@ -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> |