Simple image host.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

server.js 4.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. var http = require("http");
  2. var https = require("https");
  3. var fs = require("fs");
  4. var domain = require("domain");
  5. var zlib = require("zlib");
  6. var wrench = require("wrench");
  7. var pg = require("pg");
  8. var log = require("mlogger");
  9. var loader = require("./lib/loader.js");
  10. var Context = require("./lib/context.js");
  11. var conf = JSON.parse(fs.readFileSync("conf.json", "utf8").replace(/^\s*#.+/gm, ""));
  12. var endpoints = {
  13. //General
  14. "/favicon.ico": "favicon.node.js",
  15. "/global.css": "global.css",
  16. "/global.js": "global.js",
  17. "/404": "404.node.js",
  18. //Index
  19. "/": "index/index.node.js",
  20. "/index/script.js": "index/script.js",
  21. "/index/style.css": "index/style.css",
  22. //Register
  23. "/register": "register/index.node.js",
  24. "/register/style.css": "register/style.css",
  25. "/register/script.js": "register/script.js",
  26. //Profile
  27. "/profile": "profile/index.node.js",
  28. "/profile/style.css": "profile/style.css",
  29. "/profile/script.js": "profile/script.js",
  30. //Settings
  31. "/settings": "settings/index.node.js",
  32. "/settings/style.css": "settings/style.css",
  33. "/settings/script.js": "settings/script.js",
  34. //Viewer
  35. "/view": "view/index.node.js",
  36. "/view/style.css": "view/style.css",
  37. "/view/script.js": "view/script.js",
  38. //Plain images
  39. "/i": "i/index.node.js",
  40. //API
  41. "/api/template": "api/template.node.js",
  42. "/api/image_create": "api/image_create.node.js",
  43. "/api/collection_create": "api/collection_create.node.js",
  44. "/api/collection_delete": "api/collection_delete.node.js",
  45. "/api/account_create": "api/account_create.node.js",
  46. "/api/account_login": "api/account_login.node.js",
  47. "/api/account_logout": "api/account_logout.node.js",
  48. "/api/account_change_password": "api/account_change_password.node.js"
  49. }
  50. //We cache static resources for a long time. However, we want to invalidate
  51. //the browser's cache whenever a file updates. Therefore, we append
  52. //a number to all static files, and the number increases every time we start
  53. //the server. currentRun is that number.
  54. var currentRun;
  55. try {
  56. currentRun = parseInt(fs.readFileSync(".currentRun", "utf8"));
  57. } catch (err) {
  58. if (err.code === "ENOENT")
  59. currentRun = 0;
  60. else
  61. throw err;
  62. }
  63. currentRun = (currentRun >= conf.max_runs ? 0 : currentRun);
  64. currentRun = (currentRun || 0) + 1;
  65. conf.current_run = currentRun.toString();
  66. fs.writeFileSync(".currentRun", currentRun, "utf8");
  67. var loaded = loader.load(endpoints, conf);
  68. var db = new pg.Client(conf.db);
  69. var gzipCache = {};
  70. //Function to run on each request
  71. function onRequest(req, res) {
  72. var ctx = new Context({
  73. req: req,
  74. res: res,
  75. templates: loaded.templates,
  76. views: loaded.views,
  77. db: db,
  78. conf: conf
  79. });
  80. var ep = loaded.endpoints[req.url.split("?")[0]];
  81. //If the file doesn't exist, we 404.
  82. if (ep === undefined) {
  83. ep = loaded.endpoints["/404"];
  84. ctx.setStatus(404);
  85. }
  86. //Execute if it's a .node.js, or just respond with the contents of the file
  87. if (ep.func) {
  88. ep.func(ctx);
  89. } else {
  90. //Cache content for a while
  91. ctx.setHeader("Cache-Control", "public, max-age="+conf.cache_max_age);
  92. //Set appropriate content-type headers
  93. if (ep.mimeType)
  94. ctx.setHeader("Content-Type", ep.mimeType);
  95. //Gzip and such
  96. if (ctx.shouldGzip && gzipCache[req.url]) {
  97. ctx.end(gzipCache[req.url], true);
  98. } else if (ctx.shouldGzip) {
  99. zlib.gzip(ep.str, function(err, res) {
  100. gzipCache[req.url] = res;
  101. ctx.end(res, true);
  102. });
  103. } else {
  104. ctx.end(ep.str);
  105. }
  106. }
  107. }
  108. //Initiate a postgresql client
  109. db.connect(function() {
  110. //Create HTTP or HTTPS server
  111. var server;
  112. if (conf.use_https) {
  113. server = https.createServer(conf.https, onRequest);
  114. } else {
  115. server = http.createServer(onRequest);
  116. }
  117. server.listen(conf.port);
  118. log.info("Listening on port "+conf.port+".");
  119. purgeCollections();
  120. });
  121. //On an interval, delete old collections from anonymous users
  122. function purgeCollections() {
  123. var timeout = conf.purge_collections_timeout;
  124. db.query(
  125. "DELETE FROM collections "+
  126. "WHERE user_id IS NULL "+
  127. "AND date_created < NOW() - INTERVAL '"+timeout+"' "+
  128. "RETURNING id",
  129. function(err, res) {
  130. if (err)
  131. throw err;
  132. res.rows.forEach(function(row) {
  133. wrench.rmdirSyncRecursive(
  134. conf.dir.imgs+"/"+
  135. row.id
  136. );
  137. });
  138. if (res.rowCount > 0) {
  139. log.info(
  140. "Deleted "+res.rowCount+" collections "+
  141. "from over "+timeout+" ago."
  142. );
  143. }
  144. }
  145. );
  146. }
  147. setTimeout(purgeCollections, conf.purge_collections_interval);
  148. //We don't want to crash even if something throws an uncaught exception.
  149. if (!conf.debug) {
  150. var d = domain.create();
  151. d.on("error", function(err) {
  152. console.trace(err);
  153. });
  154. process.on("uncaughtException", function(err) {
  155. console.trace(err);
  156. });
  157. }