diff --git a/app.js b/app.js deleted file mode 100644 index 64f3fbc..0000000 --- a/app.js +++ /dev/null @@ -1,60 +0,0 @@ -var express = require("express"); -var path = require("path"); -var logger = require("morgan"); -var cookieParser = require("cookie-parser"); -var bodyParser = require("body-parser"); - -var routes = require("./routes/index"); -var avatars = require("./routes/avatars"); -var skins = require("./routes/skins"); -var renders = require("./routes/renders"); - -var app = express(); - -// view engine setup -app.set("views", path.join(__dirname, "views")); -app.set("view engine", "jade"); - -app.use(logger("dev")); -app.use(bodyParser.json()); -app.use(bodyParser.urlencoded({ extended: false })); -app.use(cookieParser()); -app.use(express.static(path.join(__dirname, "public"))); - -app.use("/", routes); -app.use("/avatars", avatars); -app.use("/skins", skins); -app.use("/renders", renders); - -// catch 404 and forward to error handler -app.use(function(req, res, next) { - var err = new Error("Not Found"); - err.status = 404; - next(err); -}); - -// error handlers - -// development error handler -// will print stacktrace -if (app.get("env") === "development") { - app.use(function(err, req, res, next) { - res.status(err.status || 500); - res.render("error", { - message: err.message, - error: err - }); - }); -} - -// production error handler -// no stacktraces leaked to user -app.use(function(err, req, res, next) { - res.status(err.status || 500); - res.render("error", { - message: err.message, - error: {} - }); -}); - -module.exports = app; \ No newline at end of file diff --git a/package.json b/package.json index 26fc719..18e3c5c 100644 --- a/package.json +++ b/package.json @@ -25,18 +25,13 @@ "test": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" }, "dependencies": { - "body-parser": "~1.10.0", "canvas": "1.1.6", - "cookie-parser": "~1.3.3", "coveralls": "^2.11.2", - "debug": "~2.1.1", - "express": "~4.10.6", "istanbul": "^0.3.2", "jade": "~1.8.2", "lwip": "0.0.6", "mocha": "2.1.0", "mocha-lcov-reporter": "0.0.1", - "morgan": "~1.5.0", "redis": "0.12.1", "request": "2.51.0", "node-df": "0.1.1" diff --git a/routes/avatars.js b/routes/avatars.js index 3d50b32..c597eba 100644 --- a/routes/avatars.js +++ b/routes/avatars.js @@ -1,4 +1,3 @@ -var router = require("express").Router(); var networking = require("../modules/networking"); var logging = require("../modules/logging"); var helpers = require("../modules/helpers"); @@ -13,23 +12,31 @@ var human_status = { "-1": "error" }; -/* GET avatar request. */ -router.get("/:uuid.:ext?", function(req, res) { - var uuid = (req.params.uuid || ""); - var size = parseInt(req.query.size) || config.default_size; - var def = req.query.default; - var helm = req.query.hasOwnProperty("helm"); +// GET avatar request +module.exports = function(req, res) { var start = new Date(); + var uuid = (req.url.pathname.split("/")[2] || "").split(".")[0]; + var size = parseInt(req.url.query.size) || config.default_size; + var def = req.url.query.default; + var helm = req.url.query.hasOwnProperty("helm"); var etag = null; // Prevent app from crashing/freezing if (size < config.min_size || size > config.max_size) { // "Unprocessable Entity", valid request, but semantically erroneous: // https://tools.ietf.org/html/rfc4918#page-78 - res.status(422).send("422 Invalid size"); + res.writeHead(422, { + "Content-Type": "text/plain", + "Response-Time": new Date() - start + }); + res.end("Invalid Size"); return; } else if (!helpers.uuid_valid(uuid)) { - res.status(422).send("422 Invalid UUID"); + res.writeHead(422, { + "Content-Type": "text/plain", + "Response-Time": new Date() - start + }); + res.end("Invalid UUID"); return; } @@ -43,7 +50,7 @@ router.get("/:uuid.:ext?", function(req, res) { logging.error(uuid + " " + err); } etag = hash && hash.substr(0, 32) || "none"; - var matches = req.get("If-None-Match") == '"' + etag + '"'; + var matches = req.headers["if-none-match"] == '"' + etag + '"'; if (image) { var http_status = 200; if (matches) { @@ -51,7 +58,7 @@ router.get("/:uuid.:ext?", function(req, res) { } else if (err) { http_status = 503; } - logging.debug("Etag: " + req.get("If-None-Match")); + logging.debug("Etag: " + req.headers["if-none-match"]); logging.debug("matches: " + matches); logging.log("status: " + http_status); sendimage(http_status, status, image); @@ -94,7 +101,4 @@ router.get("/:uuid.:ext?", function(req, res) { }); res.end(http_status == 304 ? null : image); } -}); - - -module.exports = router; \ No newline at end of file +}; \ No newline at end of file diff --git a/routes/index.js b/routes/index.js index c0b0304..cd7b7b4 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,15 +1,17 @@ -var express = require("express"); var config = require("../modules/config"); -var router = express.Router(); +var jade = require("jade"); -/* GET home page. */ -router.get("/", function(req, res) { - res.render("index", { +// compile jade +var index = jade.compileFile(__dirname + "/../views/index.jade"); + +module.exports = function(req, res) { + var html = index({ title: "Crafatar", - domain: "https://" + req.headers.host, + domain: "https://" + "req.hostname", config: config }); -}); - - -module.exports = router; \ No newline at end of file + res.writeHead(200, { + "Content-Length": html.length + }); + res.end(html); +}; \ No newline at end of file diff --git a/routes/renders.js b/routes/renders.js index d391859..80ca4aa 100644 --- a/routes/renders.js +++ b/routes/renders.js @@ -1,4 +1,3 @@ -var router = require("express").Router(); var logging = require("../modules/logging"); var helpers = require("../modules/helpers"); var config = require("../modules/config"); @@ -14,33 +13,45 @@ var human_status = { "-1": "error" }; -// valid types: head, body. helmet is query param +// valid types: head, body +// helmet is query param +// TODO: The Type logic should be two separate GET functions once response methods are extracted -// The Type logic should be two separate GET -// functions once response methods are extracted -router.get("/:type/:uuid.:ext?", function(req, res) { - var raw_type = req.params.type; +// GET render request +module.exports = function(req, res) { + var start = new Date(); + var raw_type = (req.url.pathname.split("/")[2] || ""); - // Check valid type for now + // validate type if (raw_type != "body" && raw_type != "head") { - res.status(404).send("404 Invalid Render Type"); + res.writeHead(422, { + "Content-Type": "text/plain", + "Response-Time": new Date() - start + }); + res.end("Invalid Render Type"); return; } var body = raw_type == "body"; - var uuid = req.params.uuid; - var def = req.query.default; - var scale = parseInt(req.query.scale) || config.default_scale; - var helm = req.query.hasOwnProperty("helm"); - var start = new Date(); + var uuid = (req.url.pathname.split("/")[3] || "").split(".")[0]; + var def = req.url.query.default; + var scale = parseInt(req.url.query.scale) || config.default_scale; + var helm = req.url.query.hasOwnProperty("helm"); var etag = null; if (scale < config.min_scale || scale > config.max_scale) { - // Preventing from OOM crashes. - res.status(422).send("422 Invalid Scale"); + res.writeHead(422, { + "Content-Type": "text/plain", + "Response-Time": new Date() - start + }); + res.end("422 Invalid Scale"); return; } else if (!helpers.uuid_valid(uuid)) { - res.status(422).send("422 Invalid UUID"); + res.writeHead(422, { + "Content-Type": "text/plain", + "Response-Time": new Date() - start + }); + res.end("422 Invalid UUID"); return; } @@ -54,7 +65,7 @@ router.get("/:type/:uuid.:ext?", function(req, res) { logging.error(uuid + " " + err); } etag = hash && hash.substr(0, 32) || "none"; - var matches = req.get("If-None-Match") == '"' + etag + '"'; + var matches = req.headers["if-none-match"] == '"' + etag + '"'; if (image) { var http_status = 200; if (matches) { @@ -63,7 +74,7 @@ router.get("/:type/:uuid.:ext?", function(req, res) { http_status = 503; } logging.log("matches: " + matches); - logging.log("Etag: " + req.get("If-None-Match")); + logging.log("Etag: " + req.headers["if-none-match"]); logging.log("status: " + http_status); sendimage(http_status, status, image); } else { @@ -119,6 +130,4 @@ router.get("/:type/:uuid.:ext?", function(req, res) { }); res.end(http_status == 304 ? null : image); } -}); - -module.exports = router; \ No newline at end of file +}; \ No newline at end of file diff --git a/routes/skins.js b/routes/skins.js index 0767a2e..0a09918 100644 --- a/routes/skins.js +++ b/routes/skins.js @@ -2,19 +2,22 @@ var networking = require("../modules/networking"); var logging = require("../modules/logging"); var helpers = require("../modules/helpers"); var config = require("../modules/config"); -var router = require("express").Router(); var skins = require("../modules/skins"); var lwip = require("lwip"); -/* GET skin request. */ -router.get("/:uuid.:ext?", function(req, res) { - var uuid = (req.params.uuid || ""); - var def = req.query.default; +// GET skin request +module.exports = function(req, res) { var start = new Date(); + var uuid = (req.url.pathname.split("/")[2] || "").split(".")[0]; + var def = req.url.query.default; var etag = null; if (!helpers.uuid_valid(uuid)) { - res.status(422).send("422 Invalid UUID"); + res.writeHead(422, { + "Content-Type": "text/plain", + "Response-Time": new Date() - start + }); + res.end("Invalid UUID"); return; } @@ -28,7 +31,7 @@ router.get("/:uuid.:ext?", function(req, res) { logging.error(uuid + " " + err); } etag = hash && hash.substr(0, 32) || "none"; - var matches = req.get("If-None-Match") == '"' + etag + '"'; + var matches = req.headers["if-none-match"] == '"' + etag + '"'; if (image) { var http_status = 200; if (matches) { @@ -36,7 +39,7 @@ router.get("/:uuid.:ext?", function(req, res) { } else if (err) { http_status = 503; } - logging.debug("Etag: " + req.get("If-None-Match")); + logging.debug("Etag: " + req.headers["if-none-match"]); logging.debug("matches: " + matches); logging.log("status: " + http_status); sendimage(http_status, image); @@ -81,7 +84,4 @@ router.get("/:uuid.:ext?", function(req, res) { }); res.end(http_status == 304 ? null : image); } -}); - - -module.exports = router; \ No newline at end of file +}; \ No newline at end of file diff --git a/server.js b/server.js index d501c5b..63b3dd8 100644 --- a/server.js +++ b/server.js @@ -1,14 +1,88 @@ #!/usr/bin/env node var config = require("./modules/config"); -var debug = require("debug")("crafatar"); +var logging = require("./modules/logging"); var clean = require("./modules/cleaner"); -var app = require("./app"); +var http = require("http"); +var mime = require("mime"); +var url = require("url"); +var fs = require("fs"); -app.set("port", process.env.PORT || 3000); +var routes = { + index: require("./routes/index"), + avatars: require("./routes/avatars"), + skins: require("./routes/skins"), + renders: require("./routes/renders") +}; -var server = app.listen(app.get("port"), function() { - debug("Crafatar server listening on port " + server.address().port); -}); +function asset_request(req, res) { + var filename = __dirname + "/public/" + req.url.pathname; + fs.exists(filename, function(exists) { + if (exists) { + fs.readFile(filename, function(err, contents) { + if (err) { + res.writeHead(500, {"Content-type" : "text/plain"}); + res.end("Internal Server Error"); + } else { + res.writeHead(200, { + "Content-type" : mime.lookup(filename), + "Content-Length": contents.length + }); + res.end(contents); + } + }); + } else { + res.writeHead(404, { + "Content-type" : "text/plain" + }); + res.end("Not Found"); + } + }); +} + +function requestHandler(req, res) { + var querystring = url.parse(req.url).query; + var request = req; + // we need to use url.parse and give the result to url.parse because nodejs + request.url = url.parse(req.url, querystring); + request.url.query = request.url.query || {}; + + var local_path = request.url.pathname.split("/")[1]; + console.log(request.method + " " + request.url.pathname); + if (request.method == "GET" || request.method == "HEAD") { + try { + switch (local_path) { + case "": + routes.index(request, res); + break; + case "avatars": + routes.avatars(request, res); + break; + case "skins": + routes.skins(request, res); + break; + case "renders": + routes.renders(request, res); + break; + default: + asset_request(request, res); + } + } catch(e) { + var error = JSON.stringify(req.headers) + "\n" + e.stack; + logging.error("Error: " + error); + res.writeHead(500, { + "Content-Type": "text/plain" + }); + res.end(config.debug_enabled ? error : "Internal Server Error"); + } + } else { + res.writeHead(405, { + "Content-Type": "text/plain" + }); + res.end("Method Not Allowed"); + } +} + +http.createServer(requestHandler).listen(process.env.PORT || 3000); // cleaning worker setInterval(clean.run, config.cleaning_interval * 1000); \ No newline at end of file diff --git a/views/error.jade b/views/error.jade deleted file mode 100644 index 00716a4..0000000 --- a/views/error.jade +++ /dev/null @@ -1,8 +0,0 @@ -extends layout - -block content - .container.errmsg - h1= message - h2= error.status - if error.stack - pre #{error.stack}