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

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