Selaa lähdekoodia

Namespace and move config to root directory, closes #124

Jake 10 vuotta sitten
vanhempi
sitoutus
a2e0edc491
16 muutettua tiedostoa jossa 210 lisäystä ja 101 poistoa
  1. 37 0
      config.example.js
  2. 37 0
      config.js
  3. 3 3
      lib/cache.js
  4. 10 10
      lib/cleaner.js
  5. 0 25
      lib/config.example.js
  6. 11 11
      lib/helpers.js
  7. 3 3
      lib/logging.js
  8. 2 2
      lib/networking.js
  9. 2 2
      lib/response.js
  10. 3 3
      lib/routes/avatars.js
  11. 1 1
      lib/routes/index.js
  12. 3 5
      lib/routes/renders.js
  13. 2 2
      lib/server.js
  14. 6 6
      lib/views/index.jade
  15. 86 24
      test/test.js
  16. 4 4
      www.js

+ 37 - 0
config.example.js

@@ -0,0 +1,37 @@
+var config = {
+  avatars: {
+    min_size: 1,                    // for avatars
+    max_size: 512,                  // for avatars; too big values might lead to slow response time or DoS
+    default_size: 160               // for avatars; size to be used when no size given
+  },
+  renders: {
+    min_scale: 1,                   // for 3D renders
+    max_scale: 10,                  // for 3D renders; too big values might lead to slow response time or DoS
+    default_scale: 6                // for 3D renders; scale to be used when no scale given
+  },
+  cleaner: {
+    interval: 1800,                 // seconds interval: deleting images if disk size at limit
+    disk_limit: 10240,              // min allowed available KB on disk to trigger cleaning
+    redis_limit: 24576,             // max allowed used KB on redis to trigger redis flush
+    amount: 50000                   // amount of avatar (and their helm) files to clean
+  },
+  directories: {
+    faces_dir: "images/faces/",     // directory where faces are kept. should have trailing "/"
+    helms_dir: "images/helms/",     // directory where helms are kept. should have trailing "/"
+    skins_dir: "images/skins/",     // directory where skins are kept. should have trailing "/"
+    renders_dir: "images/renders/", // Directory where rendered skins are kept. should have trailing "/"
+    capes_dir: "images/capes/"      // directory where capes are kept. should have trailing "/"
+  },
+  caching: {
+    local_cache_time: 1200,         // seconds until we will check if the image changed. should be > 60 to prevent mojang 429 response
+    browser_cache_time: 3600        // seconds until browser will request image again
+  },
+  server: {
+    http_timeout: 1000,             // ms until connection to mojang is dropped
+    debug_enabled: false,           // enables logging.debug
+    clusters: 1,                    // We recommend not using multiple clusters YET, see issue #80
+    log_time: true                  // set to false if you use an external logger that provides timestamps
+  }
+};
+
+module.exports = config;

+ 37 - 0
config.js

@@ -0,0 +1,37 @@
+var config = {
+  avatars: {
+    min_size: 1,                    // for avatars
+    max_size: 512,                  // for avatars; too big values might lead to slow response time or DoS
+    default_size: 160               // for avatars; size to be used when no size given
+  },
+  renders: {
+    min_scale: 1,                   // for 3D renders
+    max_scale: 10,                  // for 3D renders; too big values might lead to slow response time or DoS
+    default_scale: 6                // for 3D renders; scale to be used when no scale given
+  },
+  cleaner: {
+    interval: 1800,                 // seconds interval: deleting images if disk size at limit
+    disk_limit: 10240,              // min allowed available KB on disk to trigger cleaning
+    redis_limit: 24576,             // max allowed used KB on redis to trigger redis flush
+    amount: 50000                   // amount of avatar (and their helm) files to clean
+  },
+  directories: {
+    faces_dir: "images/faces/",     // directory where faces are kept. should have trailing "/"
+    helms_dir: "images/helms/",     // directory where helms are kept. should have trailing "/"
+    skins_dir: "images/skins/",     // directory where skins are kept. should have trailing "/"
+    renders_dir: "images/renders/", // Directory where rendered skins are kept. should have trailing "/"
+    capes_dir: "images/capes/"      // directory where capes are kept. should have trailing "/"
+  },
+  caching: {
+    local_cache_time: 1200,         // seconds until we will check if the image changed. should be > 60 to prevent mojang 429 response
+    browser_cache_time: 3600        // seconds until browser will request image again
+  },
+  server: {
+    http_timeout: 1000,             // ms until connection to mojang is dropped
+    debug_enabled: false,           // enables logging.debug
+    clusters: 1,                    // We recommend not using multiple clusters YET, see issue #80
+    log_time: true                  // set to false if you use an external logger that provides timestamps
+  }
+};
+
+module.exports = config;

+ 3 - 3
lib/cache.js

@@ -1,6 +1,6 @@
 var logging = require("./logging");
 var node_redis = require("redis");
-var config = require("./config");
+var config = require("../config");
 var path = require("path");
 var url = require("url");
 var fs = require("fs");
@@ -40,7 +40,7 @@ function connect_redis() {
 // the helms file is ignored because we only need 1 file to read/write from
 function update_file_date(rid, skin_hash) {
   if (skin_hash) {
-    var face_path = path.join(__dirname, "..", config.faces_dir, skin_hash + ".png");
+    var face_path = path.join(__dirname, "..", config.directories.faces_dir, skin_hash + ".png");
     fs.exists(face_path, function(exists) {
       if (exists) {
         var date = new Date();
@@ -99,7 +99,7 @@ exp.info = function(callback) {
 // callback: error
 exp.update_timestamp = function(rid, userId, hash, temp, callback) {
   logging.debug(rid, "updating cache timestamp");
-  var sub = temp ? (config.local_cache_time - 60) : 0;
+  var sub = temp ? (config.caching.local_cache_time - 60) : 0;
   var time = Date.now() - sub;
   // store userId in lower case if not null
   userId = userId && userId.toLowerCase();

+ 10 - 10
lib/cleaner.js

@@ -1,5 +1,5 @@
 var logging = require("./logging");
-var config = require("./config");
+var config = require("../config");
 var cache = require("./cache");
 var path = require("path");
 var df = require("node-df");
@@ -23,7 +23,7 @@ function should_clean_redis(callback) {
         logging.debug("used mem:" + info.used_memory);
         var used = parseInt(info.used_memory) / 1024;
         logging.log("RedisCleaner:", used + "KB used");
-        callback(err, used >= config.cleaning_redis_limit);
+        callback(err, used >= config.cleaner.redis_limit);
       } catch(e) {
         callback(e, false);
       }
@@ -35,7 +35,7 @@ function should_clean_redis(callback) {
 // callback: error, true|false
 function should_clean_disk(callback) {
   df({
-    file: path.join(__dirname, "..", config.faces_dir),
+    file: path.join(__dirname, "..", config.directories.faces_dir),
     prefixMultiplier: "KiB",
     isDisplayPrefixMultiplier: false,
     precision: 2
@@ -45,7 +45,7 @@ function should_clean_disk(callback) {
     } else {
       var available = response[0].available;
       logging.log("DiskCleaner:", available + "KB available");
-      callback(err, available < config.cleaning_disk_limit);
+      callback(err, available < config.cleaner.disk_limit);
     }
   });
 }
@@ -71,13 +71,13 @@ exp.run = function() {
       logging.error(err);
     } else if (clean) {
       logging.warn("DiskCleaner: Disk limit reached! Cleaning images now");
-      var facesdir = path.join(__dirname, "..", config.faces_dir);
-      var helmdir = path.join(__dirname, "..", config.helms_dir);
-      var renderdir = path.join(__dirname, "..", config.renders_dir);
-      var skindir = path.join(__dirname, "..", config.skins_dir);
+      var facesdir = path.join(__dirname, "..", config.directories.faces_dir);
+      var helmdir = path.join(__dirname, "..", config.directories.helms_dir);
+      var renderdir = path.join(__dirname, "..", config.directories.renders_dir);
+      var skindir = path.join(__dirname, "..", config.directories.skins_dir);
       fs.readdir(facesdir, function (readerr, files) {
         if (!readerr) {
-          for (var i = 0, l = Math.min(files.length, config.cleaning_amount); i < l; i++) {
+          for (var i = 0, l = Math.min(files.length, config.cleaner.amount); i < l; i++) {
             var filename = files[i];
             if (filename[0] !== ".") {
               fs.unlink(path.join(facesdir, filename), nil);
@@ -89,7 +89,7 @@ exp.run = function() {
       });
       fs.readdir(renderdir, function (readerr, files) {
         if (!readerr) {
-          for (var j = 0, l = Math.min(files.length, config.cleaning_amount); j < l; j++) {
+          for (var j = 0, l = Math.min(files.length, config.cleaner.amount); j < l; j++) {
             var filename = files[j];
             if (filename[0] !== ".") {
               fs.unlink(renderdir + filename, nil);

+ 0 - 25
lib/config.example.js

@@ -1,25 +0,0 @@
-var config = {
-  min_size: 1,                    // for avatars
-  max_size: 512,                  // for avatars; too big values might lead to slow response time or DoS
-  default_size: 160,              // for avatars; size to be used when no size given
-  min_scale: 1,                   // for 3D renders
-  max_scale: 10,                  // for 3D renders; too big values might lead to slow response time or DoS
-  default_scale: 6,               // for 3D renders; scale to be used when no scale given
-  local_cache_time: 1200,         // seconds until we will check if the image changed. should be > 60 to prevent mojang 429 response
-  browser_cache_time: 3600,       // seconds until browser will request image again
-  cleaning_interval: 1800,        // seconds interval: deleting images if disk size at limit
-  cleaning_disk_limit: 10240,     // min allowed available KB on disk to trigger cleaning
-  cleaning_redis_limit: 24576,    // max allowed used KB on redis to trigger redis flush
-  cleaning_amount: 50000,         // amount of avatar (and their helm) files to clean
-  http_timeout: 1000,             // ms until connection to mojang is dropped
-  debug_enabled: false,           // enables logging.debug
-  faces_dir: "images/faces/",     // directory where faces are kept. should have trailing "/"
-  helms_dir: "images/helms/",     // directory where helms are kept. should have trailing "/"
-  skins_dir: "images/skins/",     // directory where skins are kept. should have trailing "/"
-  renders_dir: "images/renders/", // Directory where rendered skins are kept. should have trailing "/"
-  capes_dir: "images/capes/",     // directory where capes are kept. should have trailing "/"
-  clusters: 1,                    // We recommend not using multiple clusters YET, see issue #80
-  log_time: true,                 // set to false if you use an external logger that provides timestamps
-};
-
-module.exports = config;

+ 11 - 11
lib/helpers.js

@@ -1,7 +1,7 @@
 var networking = require("./networking");
 var logging = require("./logging");
 var renders = require("./renders");
-var config = require("./config");
+var config = require("../config");
 var cache = require("./cache");
 var skins = require("./skins");
 var path = require("path");
@@ -30,9 +30,9 @@ function store_skin(rid, userId, profile, cache_details, callback) {
         });
       } else {
         logging.log(rid, "new skin hash:", skin_hash);
-        var facepath = path.join(__dirname, "..", config.faces_dir, skin_hash + ".png");
-        var helmpath = path.join(__dirname, "..", config.helms_dir, skin_hash + ".png");
-        var skinpath = path.join(__dirname, "..", config.skins_dir, skin_hash + ".png");
+        var facepath = path.join(__dirname, "..", config.directories.faces_dir, skin_hash + ".png");
+        var helmpath = path.join(__dirname, "..", config.directories.helms_dir, skin_hash + ".png");
+        var skinpath = path.join(__dirname, "..", config.directories.skins_dir, skin_hash + ".png");
         fs.exists(facepath, function(exists) {
           if (exists) {
             logging.log(rid, "skin already exists, not downloading");
@@ -87,7 +87,7 @@ function store_cape(rid, userId, profile, cache_details, callback) {
         });
       } else {
         logging.log(rid, "new cape hash:", cape_hash);
-        var capepath = path.join(__dirname, "..", config.capes_dir, cape_hash + ".png");
+        var capepath = path.join(__dirname, "..", config.directories.capes_dir, cape_hash + ".png");
         fs.exists(capepath, function(exists) {
           if (exists) {
             logging.log(rid, "cape already exists, not downloading");
@@ -223,7 +223,7 @@ exp.get_image_hash = function(rid, userId, type, callback) {
     if (err) {
       callback(err, -1, null);
     } else {
-      if (cache_details && cache_details[type] !== undefined && cache_details.time + config.local_cache_time * 1000 >= Date.now()) {
+      if (cache_details && cache_details[type] !== undefined && cache_details.time + config.caching.local_cache_time * 1000 >= Date.now()) {
         // use cached image
         logging.log(rid, "userId cached & recently updated");
         callback(null, (cached_hash ? 1 : 0), cached_hash);
@@ -261,8 +261,8 @@ exp.get_image_hash = function(rid, userId, type, callback) {
 exp.get_avatar = function(rid, userId, helm, size, callback) {
   exp.get_image_hash(rid, userId, "skin", function(err, status, skin_hash) {
     if (skin_hash) {
-      var facepath = path.join(__dirname, "..", config.faces_dir, skin_hash + ".png");
-      var helmpath = path.join(__dirname, "..", config.helms_dir, skin_hash + ".png");
+      var facepath = path.join(__dirname, "..", config.directories.faces_dir, skin_hash + ".png");
+      var helmpath = path.join(__dirname, "..", config.directories.helms_dir, skin_hash + ".png");
       var filepath = facepath;
       fs.exists(helmpath, function(exists) {
         if (helm && exists) {
@@ -288,7 +288,7 @@ exp.get_avatar = function(rid, userId, helm, size, callback) {
 exp.get_skin = function(rid, userId, callback) {
   exp.get_image_hash(rid, userId, "skin", function(err, status, skin_hash) {
     if (skin_hash) {
-      var skinpath = path.join(__dirname, "..", config.skins_dir, skin_hash + ".png");
+      var skinpath = path.join(__dirname, "..", config.directories.skins_dir, skin_hash + ".png");
       fs.exists(skinpath, function(exists) {
         if (exists) {
           logging.log(rid, "skin already exists, not downloading");
@@ -323,7 +323,7 @@ exp.get_render = function(rid, userId, scale, helm, body, callback) {
       callback(err, status, skin_hash, null);
       return;
     }
-    var renderpath = path.join(__dirname, "..", config.renders_dir, [skin_hash, scale, get_type(helm, body)].join("-") + ".png");
+    var renderpath = path.join(__dirname, "..", config.directories.renders_dir, [skin_hash, scale, get_type(helm, body)].join("-") + ".png");
     fs.exists(renderpath, function(exists) {
       if (exists) {
         renders.open_render(rid, renderpath, function(render_err, rendered_img) {
@@ -362,7 +362,7 @@ exp.get_cape = function(rid, userId, callback) {
       callback(err, null, status, null);
       return;
     }
-    var capepath = path.join(__dirname, "..", config.capes_dir, cape_hash + ".png");
+    var capepath = path.join(__dirname, "..", config.directories.capes_dir, cape_hash + ".png");
     fs.exists(capepath, function(exists) {
       if (exists) {
         logging.log(rid, "cape already exists, not downloading");

+ 3 - 3
lib/logging.js

@@ -1,5 +1,5 @@
 var cluster = require("cluster");
-var config = require("./config");
+var config = require("../config");
 
 var exp = {};
 
@@ -17,7 +17,7 @@ function join_args(args) {
 // the timestamp can be disabled in the config
 function log(level, args, logger) {
   logger = logger || console.log;
-  var time = config.log_time ? new Date().toISOString() + " " : "";
+  var time = config.server.log_time ? new Date().toISOString() + " " : "";
   var clid = (cluster.worker && cluster.worker.id || "M");
   var lines = join_args(args).split("\n");
   for (var i = 0, l = lines.length; i < l; i++) {
@@ -34,7 +34,7 @@ exp.warn = function() {
 exp.error = function() {
   log("ERROR", arguments, console.error);
 };
-if (config.debug_enabled || process.env.DEBUG === "true") {
+if (config.server.debug_enabled || process.env.DEBUG === "true") {
   exp.debug = function() {
     log("DEBUG", arguments);
   };

+ 2 - 2
lib/networking.js

@@ -1,7 +1,7 @@
 var http_code = require("http").STATUS_CODES;
 var logging = require("./logging");
 var request = require("request");
-var config = require("./config");
+var config = require("../config");
 var skins = require("./skins");
 
 var session_url = "https://sessionserver.mojang.com/session/minecraft/profile/";
@@ -67,7 +67,7 @@ exp.get_from_options = function(rid, url, options, callback) {
     headers: {
       "User-Agent": "https://crafatar.com"
     },
-    timeout: config.http_timeout,
+    timeout: config.server.http_timeout,
     followRedirect: false,
     encoding: (options.encoding || null),
   }, function(error, response, body) {

+ 2 - 2
lib/response.js

@@ -1,5 +1,5 @@
 var logging = require("./logging");
-var config = require("./config");
+var config = require("../config");
 var crc = require("crc").crc32;
 
 var human_status = {
@@ -39,7 +39,7 @@ module.exports = function(request, response, result) {
   // These headers are the same for every response
   var headers = {
     "Content-Type": (result.body && result.type) || "text/plain",
-    "Cache-Control": "max-age=" + config.browser_cache_time + ", public",
+    "Cache-Control": "max-age=" + config.caching.browser_cache_time + ", public",
     "Response-Time": Date.now() - request.start,
     "X-Request-ID": request.id,
     "Access-Control-Allow-Origin": "*"

+ 3 - 3
lib/routes/avatars.js

@@ -1,6 +1,6 @@
 var logging = require("../logging");
 var helpers = require("../helpers");
-var config = require("../config");
+var config = require("../../config");
 var skins = require("../skins");
 var cache = require("../cache");
 var path = require("path");
@@ -45,7 +45,7 @@ function handle_default(img_status, userId, size, def, req, err, callback) {
 // GET avatar request
 module.exports = function(req, callback) {
   var userId = (req.url.path_list[1] || "").split(".")[0];
-  var size = parseInt(req.url.query.size) || config.default_size;
+  var size = parseInt(req.url.query.size) || config.avatars.default_size;
   var def = req.url.query.default;
   var helm = req.url.query.hasOwnProperty("helm");
 
@@ -59,7 +59,7 @@ module.exports = function(req, callback) {
   }
 
   // Prevent app from crashing/freezing
-  if (size < config.min_size || size > config.max_size) {
+  if (size < config.avatars.min_size || size > config.avatars.max_size) {
     // "Unprocessable Entity", valid request, but semantically erroneous:
     // https://tools.ietf.org/html/rfc4918#page-78
     callback({

+ 1 - 1
lib/routes/index.js

@@ -1,4 +1,4 @@
-var config = require("../config");
+var config = require("../../config");
 var path = require("path");
 var jade = require("jade");
 

+ 3 - 5
lib/routes/renders.js

@@ -1,7 +1,7 @@
 var logging = require("../logging");
 var helpers = require("../helpers");
 var renders = require("../renders");
-var config = require("../config");
+var config = require("../../config");
 var cache = require("../cache");
 var skins = require("../skins");
 var path = require("path");
@@ -9,8 +9,6 @@ var fs = require("fs");
 
 // valid types: head, body
 // helmet is query param
-// TODO: The Type logic should be two separate GET functions once response methods are extracted
-
 function handle_default(rid, scale, helm, body, img_status, userId, size, def, req, err, callback) {
   def = def || skins.default_skin(userId);
   if (def !== "steve" && def !== "alex") {
@@ -57,7 +55,7 @@ module.exports = function(req, callback) {
   var body = raw_type === "body";
   var userId = (req.url.path_list[2] || "").split(".")[0];
   var def = req.url.query.default;
-  var scale = parseInt(req.url.query.scale) || config.default_scale;
+  var scale = parseInt(req.url.query.scale) || config.renders.default_scale;
   var helm = req.url.query.hasOwnProperty("helm");
 
   // check for extra paths
@@ -78,7 +76,7 @@ module.exports = function(req, callback) {
     return;
   }
 
-  if (scale < config.min_scale || scale > config.max_scale) {
+  if (scale < config.renders.min_scale || scale > config.renders.max_scale) {
     callback({
       status: -2,
       body: "Invalid Scale"

+ 2 - 2
lib/server.js

@@ -3,7 +3,7 @@ var querystring = require("querystring");
 var response = require("./response");
 var toobusy = require("toobusy-js");
 var logging = require("./logging");
-var config = require("./config");
+var config = require("../config");
 var http = require("http");
 var mime = require("mime");
 var path = require("path");
@@ -116,7 +116,7 @@ function requestHandler(req, res) {
       res.writeHead(500, {
         "Content-Type": "text/plain"
       });
-      res.end(config.debug_enabled ? error : "Internal Server Error");
+      res.end(config.server.debug_enabled ? error : "Internal Server Error");
     }
   } else {
     res.writeHead(405, {

+ 6 - 6
lib/views/index.jade

@@ -56,8 +56,8 @@ block content
                 tr
                   td size
                   td integer
-                  td #{config.default_size}
-                  td The size of the image in pixels, #{config.min_size} - #{config.max_size}.
+                  td #{config.avatars.default_size}
+                  td The size of the image in pixels, #{config.avatars.min_size} - #{config.avatars.max_size}.
                 tr
                   td default
                   td string
@@ -140,8 +140,8 @@ block content
                 tr
                   td scale
                   td integer
-                  td #{config.default_scale}. The actual size differs between the type of render.
-                  td The scale factor of the image #{config.min_scale} - #{config.max_scale}.
+                  td #{config.renders.default_scale}. The actual size differs between the type of render.
+                  td The scale factor of the image #{config.renders.min_scale} - #{config.renders.max_scale}.
                 tr
                   td helm
                   td null
@@ -324,8 +324,8 @@ block content
             a(href="#meta-about-caching")
               h3 About Caching
             p
-              | Crafatar caches skins for #{config.local_cache_time/60} minutes before checking for skin changes.<br>
-              | Images are cached in your browser for #{config.browser_cache_time/60} minutes until a new request to Crafatar is made.<br>
+              | Crafatar caches skins for #{config.caching.local_cache_time/60} minutes before checking for skin changes.<br>
+              | Images are cached in your browser for #{config.caching.browser_cache_time/60} minutes until a new request to Crafatar is made.<br>
               | When you changed your skin you can try clearing your browser cache to see the change faster.
 
 

+ 86 - 24
test/test.js

@@ -5,7 +5,7 @@ var helpers = require("../lib/helpers");
 var logging = require("../lib/logging");
 var cleaner = require("../lib/cleaner");
 var request = require("request");
-var config = require("../lib/config");
+var config = require("../config");
 var server = require("../lib/server");
 var assert = require("assert");
 var skins = require("../lib/skins");
@@ -14,7 +14,7 @@ var crc = require("crc").crc32;
 var fs = require("fs");
 
 // we don't want tests to fail because of slow internet
-config.http_timeout *= 3;
+config.server.http_timeout *= 3;
 
 // no spam
 if (process.env.VERBOSE_TEST !== "true") {
@@ -67,13 +67,13 @@ var ids = [
 
 describe("Crafatar", function() {
   // we might have to make 2 HTTP requests
-  this.timeout(config.http_timeout * 2 + 50);
+  this.timeout(config.server.http_timeout * 2 + 50);
 
   before(function() {
     cache.get_redis().flushall();
     // cause I don't know how big hard drives are these days
-    config.cleaning_disk_limit = Infinity;
-    config.cleaning_redis_limit = Infinity;
+    config.cleaner.disk_limit = Infinity;
+    config.cleaner.redis_limit = Infinity;
     cleaner.run();
   });
 
@@ -170,29 +170,29 @@ describe("Crafatar", function() {
   });
   describe("Errors", function() {
     it("should time out on uuid info download", function(done) {
-      var original_timeout = config.http_timeout;
-      config.http_timeout = 1;
+      var original_timeout = config.server.http_timeout;
+      config.server.http_timeout = 1;
       networking.get_profile(rid, "069a79f444e94726a5befca90e38aaf5", function(err, profile) {
         assert.strictEqual(err.code, "ETIMEDOUT");
-        config.http_timeout = original_timeout;
+        config.server.http_timeout = original_timeout;
         done();
       });
     });
     it("should time out on username info download", function(done) {
-      var original_timeout = config.http_timeout;
-      config.http_timeout = 1;
+      var original_timeout = config.server.http_timeout;
+      config.server.http_timeout = 1;
       networking.get_username_url(rid, "jomo", 0, function(err, url) {
         assert.strictEqual(err.code, "ETIMEDOUT");
-        config.http_timeout = original_timeout;
+        config.server.http_timeout = original_timeout;
         done();
       });
     });
     it("should time out on skin download", function(done) {
       var original_timeout = config.http_timeout;
-      config.http_timeout = 1;
+      config.server.http_timeout = 1;
       networking.get_from(rid, "http://textures.minecraft.net/texture/477be35554684c28bdeee4cf11c591d3c88afb77e0b98da893fd7bc318c65184", function(body, res, error) {
         assert.strictEqual(error.code, "ETIMEDOUT");
-        config.http_timeout = original_timeout;
+        config.server.http_timeout = original_timeout;
         done();
       });
     });
@@ -227,7 +227,7 @@ describe("Crafatar", function() {
       assert("" + res.headers["response-time"]);
       assert(res.headers["x-request-id"]);
       assert.equal(res.headers["access-control-allow-origin"], "*");
-      assert.equal(res.headers["cache-control"], "max-age=" + config.browser_cache_time + ", public");
+      assert.equal(res.headers["cache-control"], "max-age=" + config.caching.browser_cache_time + ", public");
     }
 
     // throws Exception when +url+ is requested with +etag+
@@ -352,6 +352,11 @@ describe("Crafatar", function() {
         etag: '"alex"',
         crc32: [862751081, 809395677]
       },
+      "avatar with non-existent username defaulting to userId": {
+        url: "http://localhost:3000/avatars/0?size=16&default=jeb_",
+        crc32: 0,
+        redirect: "/avatars/jeb_?size=16"
+      },
       "avatar with non-existent username defaulting to url": {
         url: "http://localhost:3000/avatars/0?size=16&default=http://example.com",
         crc32: 0,
@@ -372,6 +377,11 @@ describe("Crafatar", function() {
         etag: '"alex"',
         crc32: [862751081, 809395677]
       },
+      "helm avatar with non-existent username defaulting to userId": {
+        url: "http://localhost:3000/avatars/0?size=16&helm&default=jeb_",
+        crc32: 0,
+        redirect: "/avatars/jeb_?size=16&helm="
+      },
       "helm avatar with non-existent username defaulting to url": {
         url: "http://localhost:3000/avatars/0?size=16&helm&default=http://example.com",
         crc32: 0,
@@ -392,6 +402,11 @@ describe("Crafatar", function() {
         etag: '"alex"',
         crc32: [862751081, 809395677]
       },
+      "avatar with non-existent uuid defaulting to userId": {
+        url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=jeb_",
+        crc32: 0,
+        redirect: "/avatars/jeb_?size=16"
+      },
       "avatar with non-existent uuid defaulting to url": {
         url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=http://example.com",
         crc32: 0,
@@ -412,6 +427,11 @@ describe("Crafatar", function() {
         etag: '"alex"',
         crc32: [862751081, 809395677]
       },
+      "helm avatar with non-existent uuid defaulting to userId": {
+        url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=jeb_",
+        crc32: 0,
+        redirect: "/avatars/jeb_?size=16"
+      },
       "helm avatar with non-existent uuid defaulting to url": {
         url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&helm&default=http://example.com",
         crc32: 0,
@@ -460,6 +480,11 @@ describe("Crafatar", function() {
         etag: '"alex"',
         crc32: 2298915739
       },
+      "skin with non-existent username defaulting to userId": {
+        url: "http://localhost:3000/skins/0?size=16&default=jeb_",
+        crc32: 0,
+        redirect: "/skins/jeb_?size=16"
+      },
       "skin with non-existent username defaulting to url": {
         url: "http://localhost:3000/skins/0?default=http://example.com",
         crc32: 0,
@@ -480,11 +505,18 @@ describe("Crafatar", function() {
         etag: '"alex"',
         crc32: 2298915739
       },
+      "skin with non-existent uuid defaulting to userId": {
+        url: "http://localhost:3000/skins/00000000000000000000000000000000?size=16&default=jeb_",
+        crc32: 0,
+        redirect: "/skins/jeb_?size=16"
+      },
       "skin with non-existent uuid defaulting to url": {
         url: "http://localhost:3000/skins/00000000000000000000000000000000?default=http://example.com",
         crc32: 0,
         redirect: "http://example.com"
       },
+
+
       "head render with existing username": {
         url: "http://localhost:3000/renders/head/jeb_?scale=2",
         etag: '"a846b82963"',
@@ -500,6 +532,11 @@ describe("Crafatar", function() {
         etag: '"alex"',
         crc32: [1240086237, 1108800327]
       },
+      "head render with non-existent useranme defaulting to userId": {
+        url: "http://localhost:3000/avatars/0?scale=2&default=jeb_",
+        crc32: 0,
+        redirect: "/renders/head/jeb_?scale=2"
+      },
       "head render with non-existent username defaulting to url": {
         url: "http://localhost:3000/renders/head/0?scale=2&default=http://example.com",
         crc32: 0,
@@ -520,6 +557,11 @@ describe("Crafatar", function() {
         etag: '"alex"',
         crc32: [2963161105, 1769904825]
       },
+      "helm head render with non-existent username defaulting to userId": {
+        url: "http://localhost:3000/renders/head/0?scale=2&helm&default=jeb_",
+        crc32: 0,
+        redirect: "/renders/head/jeb_?scale=2&helm="
+      },
       "helm head render with non-existent username defaulting to url": {
         url: "http://localhost:3000/renders/head/0?scale=2&helm&default=http://example.com",
         crc32: 0,
@@ -540,6 +582,11 @@ describe("Crafatar", function() {
         etag: '"alex"',
         crc32: [1240086237, 1108800327]
       },
+      "head render with non-existent uuid defaulting to userId": {
+        url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=jeb_",
+        crc32: 0,
+        redirect: "/renders/head/jeb_?scale=2"
+      },
       "head render with non-existent uuid defaulting to url": {
         url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=http://example.com",
         crc32: 0,
@@ -560,6 +607,11 @@ describe("Crafatar", function() {
         etag: '"alex"',
         crc32: [2963161105, 1769904825]
       },
+      "helm head with non-existent uuid defaulting to userId": {
+        url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&helm&default=jeb_",
+        crc32: 0,
+        redirect: "/renders/head/jeb_?scale=2&helm="
+      },
       "helm head render with non-existent uuid defaulting to url": {
         url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&helm&default=http://example.com",
         crc32: 0,
@@ -580,6 +632,11 @@ describe("Crafatar", function() {
         etag: '"alex"',
         crc32: [407932087, 2516216042]
       },
+      "body render with non-existent username defaulting to userId": {
+        url: "http://localhost:3000/renders/body/0?scale=2&default=jeb_",
+        crc32: 0,
+        redirect: "/renders/body/jeb_?scale=2"
+      },
       "body render with non-existent username defaulting to url": {
         url: "http://localhost:3000/renders/body/0?scale=2&default=http://example.com",
         crc32: 0,
@@ -600,6 +657,11 @@ describe("Crafatar", function() {
         etag: '"alex"',
         crc32: [737759773, 66512449]
       },
+      "helm body render with non-existent username defaulting to userId": {
+        url: "http://localhost:3000/renders/body/0?scale=2&helm&default=jeb_",
+        crc32: 0,
+        redirect: "/renders/body/jeb_?scale=2&helm="
+      },
       "helm body render with non-existent username defaulting to url": {
         url: "http://localhost:3000/renders/body/0?scale=2&helm&default=http://example.com",
         crc32: 0,
@@ -620,6 +682,11 @@ describe("Crafatar", function() {
         etag: '"alex"',
         crc32: [407932087, 2516216042]
       },
+      "body render with non-existent uuid defaulting to userId": {
+        url: "http://localhost:3000/renders/body/0?scale=2&default=jeb_",
+        crc32: 0,
+        redirect: "/renders/body/jeb_?scale=2"
+      },
       "body render with non-existent uuid defaulting to url": {
         url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=http://example.com",
         crc32: 0,
@@ -640,11 +707,6 @@ describe("Crafatar", function() {
         etag: '"alex"',
         crc32: [737759773, 66512449]
       },
-      "helm body render with non-existent uuid defaulting to userId": {
-        url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&helm&default=alex",
-        etag: '"alex"',
-        crc32: [737759773, 66512449]
-      },
       "helm body render with non-existent uuid defaulting to url": {
         url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&helm&default=http://example.com",
         crc32: 0,
@@ -696,7 +758,7 @@ describe("Crafatar", function() {
     }
 
     it("should return a 422 (invalid size)", function(done) {
-      var size = config.max_size + 1;
+      var size = config.avatars.max_size + 1;
       request.get("http://localhost:3000/avatars/Jake_0?size=" + size, function(error, res, body) {
         assert.ifError(error);
         assert.strictEqual(res.statusCode, 422);
@@ -705,7 +767,7 @@ describe("Crafatar", function() {
     });
 
     it("should return a 422 (invalid scale)", function(done) {
-      var scale = config.max_scale + 1;
+      var scale = config.renders.max_scale + 1;
       request.get("http://localhost:3000/renders/head/Jake_0?scale=" + scale, function(error, res, body) {
         assert.ifError(error);
         assert.strictEqual(res.statusCode, 422);
@@ -833,12 +895,12 @@ describe("Crafatar", function() {
           console.log("can't run 'checked' test due to Mojang's rate limits :(");
         } else {
           it("should be checked", function(done) {
-            var original_cache_time = config.local_cache_time;
-            config.local_cache_time = 0;
+            var original_cache_time = config.caching.local_cache_time;
+            config.caching.local_cache_time = 0;
             helpers.get_avatar(rid, id, false, 160, function(err, status, image) {
               assert.ifError(err);
               assert.strictEqual(status, 3);
-              config.local_cache_time = original_cache_time;
+              config.caching.local_cache_time = original_cache_time;
               done();
             });
           });

+ 4 - 4
www.js

@@ -1,6 +1,6 @@
 var logging = require("./lib/logging");
 var cleaner = require("./lib/cleaner");
-var config = require("./lib/config");
+var config = require("./config");
 var cluster = require("cluster");
 
 process.on("uncaughtException", function (err) {
@@ -8,8 +8,8 @@ process.on("uncaughtException", function (err) {
 });
 
 if (cluster.isMaster) {
-  var cores = config.clusters || require("os").cpus().length;
-  logging.log("Starting", cores + " workers");
+  var cores = config.server.clusters || require("os").cpus().length;
+  logging.log("Starting", cores + " worker" + (cores > 1 ? "s" : ""));
   for (var i = 0; i < cores; i++) {
     cluster.fork();
   }
@@ -19,7 +19,7 @@ if (cluster.isMaster) {
     setTimeout(cluster.fork, 100);
   });
 
-  setInterval(cleaner.run, config.cleaning_interval * 1000);
+  setInterval(cleaner.run, config.cleaner.interval * 1000);
 } else {
   require("./lib/server.js").boot();
 }