Browse Source

caching and gzipping

master
mort 8 years ago
parent
commit
6f061dbc38
6 changed files with 91 additions and 55 deletions
  1. 1
    0
      .gitignore
  2. 2
    1
      conf.json.example
  3. 40
    7
      lib/context.js
  4. 7
    4
      lib/loader.js
  5. 37
    39
      server.js
  6. 4
    4
      templates/head.html

+ 1
- 0
.gitignore View File

npm-debug.log npm-debug.log
imgs imgs
!imgs/.placeholder !imgs/.placeholder
.currentRun

+ 2
- 1
conf.json.example View File

"dir": { "dir": {
"imgs": "imgs" "imgs": "imgs"
}, },
"debug": false
"debug": false,
"maxRuns": 9999
} }

+ 40
- 7
lib/context.js View File

var formidable = require("formidable"); var formidable = require("formidable");
var crypto = require("crypto"); var crypto = require("crypto");
var zlib = require("zlib");
var preprocess = require("./preprocess.js"); var preprocess = require("./preprocess.js");


var sessions = {}; var sessions = {};
env.url = ctx.req.url; env.url = ctx.req.url;


str = preprocess(str, { str = preprocess(str, {
session: ctx.session,
session: function(key) {
ctx.cachable = false;
return ctx.session[key];
},
arg: function(key) { arg: function(key) {
return ctx.htmlEntities(args[key]); return ctx.htmlEntities(args[key]);
}, },
return str; return str;
} }


var cache = {};

module.exports = function(options) { module.exports = function(options) {
this.req = options.req; this.req = options.req;
this.res = options.res; this.res = options.res;
this.db = options.db; this.db = options.db;
this.conf = options.conf; this.conf = options.conf;
this.query = this.req.url.split("?")[1] || ""; 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) if (this.conf.debug)
this.startTime = new Date(); this.startTime = new Date();


} }


module.exports.prototype = { module.exports.prototype = {
end: function(str) {
_end: function(str) {
if (this.conf.debug) { if (this.conf.debug) {
var ms = (new Date().getTime() - this.startTime.getTime()); var ms = (new Date().getTime() - this.startTime.getTime());
console.log( console.log(
conole.log(this.req.url); conole.log(this.req.url);
} }


if (this.statusCode)
this.res.writeHead(this.statusCode);

this.res.writeHead(this.statusCode, this.headers);
this.res.end(str); 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) { succeed: function(obj) {
obj = obj || {}; obj = obj || {};
obj.success = true; obj.success = true;


this.res.setHeader("Content-Type", "application/json");
this.setHeader("Content-Type", "application/json");


this.end(JSON.stringify(obj)); this.end(JSON.stringify(obj));
}, },
console.log("Sending error to client:"); console.log("Sending error to client:");
console.trace(err); console.trace(err);


this.res.setHeader("Content-Type", "application/json");
this.setHeader("Content-Type", "application/json");


obj = {}; obj = {};
obj.success = false; obj.success = false;
if (!str) if (!str)
throw new Error("No such view: "+name); throw new Error("No such view: "+name);


if (name === "404")
this.setStatus(404);

return templatify(str, args, this, {view: name}); return templatify(str, args, this, {view: name});
}, },


this.statusCode = code; this.statusCode = code;
}, },


setHeader: function(key, val) {
this.headers[key] = val;
},

login: function(username, id) { login: function(username, id) {
this.session.loggedIn = true; this.session.loggedIn = true;
this.session.username = username; this.session.username = username;

+ 7
- 4
lib/loader.js View File

var fs = require("fs"); var fs = require("fs");
var zlib = require("zlib");
var minify = require("./minify.js"); var minify = require("./minify.js");
var includeHtml = require("./includeHtml.js"); var includeHtml = require("./includeHtml.js");


//If it doesn't end with .node.js, it's a regular text file and will //If it doesn't end with .node.js, it's a regular text file and will
//just be served as is //just be served as is
} else { } 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 it's an HTML file, we minify it
if (!conf.minify) { if (!conf.minify) {
//Don't minify unless the conf tells us to //Don't minify unless the conf tells us to
} else if (/\.html$/.test(ep)) { } else if (/\.html$/.test(ep)) {
res.endpoints[i] = minify.html(res.endpoints[i]);
str = minify.html(str);
} else if (/\.js$/.test(ep)) { } else if (/\.js$/.test(ep)) {
res.endpoints[i] = minify.js(res.endpoints[i]);
str = minify.js(str);
} else if (/\.css$/.test(ep)) { } 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 //Errors will usually be because an endpoint doesn't exist

+ 37
- 39
server.js View File

var https = require("https"); var https = require("https");
var fs = require("fs"); var fs = require("fs");
var domain = require("domain"); var domain = require("domain");
var zlib = require("zlib");
var loader = require("./lib/loader.js"); var loader = require("./lib/loader.js");
var pg = require("pg"); var pg = require("pg");
var Context = require("./lib/context.js"); var Context = require("./lib/context.js");
"/api/account_change_password": "api/account_change_password.node.js" "/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 loaded = loader.load(endpoints, conf);


var db = new pg.Client(conf.db); var db = new pg.Client(conf.db);


var gzipCache = {};

//Function to run on each request //Function to run on each request
function onRequest(req, res) { function onRequest(req, res) {
var ctx = new Context({ var ctx = new Context({
if (typeof ep == "function") { if (typeof ep == "function") {
ep(ctx); ep(ctx);
} else { } 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 //Initiate a postgresql client
db.connect(function() { db.connect(function() {
//Create HTTP or HTTPS server //Create HTTP or HTTPS server
var server; var server;
if (conf.use_https) { if (conf.use_https) {
console.trace(err); 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]);
}
});

+ 4
- 4
templates/head.html View File

<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width"> <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="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://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="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>

Loading…
Cancel
Save