server.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. #!/usr/bin/env node
  2. var querystring = require("querystring");
  3. var response = require("./response");
  4. var toobusy = require("toobusy-js");
  5. var logging = require("./logging");
  6. var config = require("../config");
  7. var http = require("http");
  8. var mime = require("mime");
  9. var path = require("path");
  10. var url = require("url");
  11. var fs = require("fs");
  12. var server = null;
  13. var routes = {
  14. index: require("./routes/index"),
  15. avatars: require("./routes/avatars"),
  16. skins: require("./routes/skins"),
  17. renders: require("./routes/renders"),
  18. capes: require("./routes/capes"),
  19. };
  20. // serves assets from lib/public
  21. function asset_request(req, callback) {
  22. var filename = path.join(__dirname, "public", req.url.path_list.join("/"));
  23. fs.access(filename, function(fs_err) {
  24. if (!fs_err) {
  25. fs.readFile(filename, function(err, data) {
  26. callback({
  27. body: data,
  28. type: mime.getType(filename),
  29. err: err,
  30. });
  31. });
  32. } else {
  33. callback({
  34. body: "Not found",
  35. status: -2,
  36. code: 404,
  37. });
  38. }
  39. });
  40. }
  41. // generates a 12 character random string
  42. function request_id() {
  43. return Math.random().toString(36).substring(2, 14);
  44. }
  45. // splits a URL path into an Array
  46. // the path is resolved and decoded
  47. function path_list(pathname) {
  48. // remove double and trailing slashes
  49. pathname = pathname.replace(/\/\/+/g, "/").replace(/(.)\/$/, "$1");
  50. var list = pathname.split("/");
  51. list.shift();
  52. for (var i = 0; i < list.length; i++) {
  53. // URL decode
  54. list[i] = querystring.unescape(list[i]);
  55. }
  56. return list;
  57. }
  58. // handles the +req+ by routing to the request to the appropriate module
  59. function requestHandler(req, res) {
  60. req.url = url.parse(req.url, true);
  61. req.url.query = req.url.query || {};
  62. req.url.path_list = path_list(req.url.pathname);
  63. req.id = request_id();
  64. req.start = Date.now();
  65. var local_path = req.url.path_list[0];
  66. logging.debug(req.id, req.method, req.url.href);
  67. toobusy.maxLag(200);
  68. if (toobusy() && !process.env.TRAVIS) {
  69. response(req, res, {
  70. status: -1,
  71. body: "Server is over capacity :/",
  72. err: "Too busy",
  73. code: 503,
  74. });
  75. return;
  76. }
  77. if (req.method === "GET" || req.method === "HEAD") {
  78. try {
  79. switch (local_path) {
  80. case "":
  81. routes.index(req, function(result) {
  82. response(req, res, result);
  83. });
  84. break;
  85. case "avatars":
  86. routes.avatars(req, function(result) {
  87. response(req, res, result);
  88. });
  89. break;
  90. case "skins":
  91. routes.skins(req, function(result) {
  92. response(req, res, result);
  93. });
  94. break;
  95. case "renders":
  96. routes.renders(req, function(result) {
  97. response(req, res, result);
  98. });
  99. break;
  100. case "capes":
  101. routes.capes(req, function(result) {
  102. response(req, res, result);
  103. });
  104. break;
  105. default:
  106. asset_request(req, function(result) {
  107. response(req, res, result);
  108. });
  109. }
  110. } catch(e) {
  111. var error = JSON.stringify(req.headers) + "\n" + e.stack;
  112. response(req, res, {
  113. status: -1,
  114. body: config.server.debug_enabled ? error : "Internal Server Error",
  115. err: error,
  116. });
  117. }
  118. } else {
  119. response(req, res, {
  120. status: -2,
  121. body: "Method Not Allowed",
  122. code: 405,
  123. });
  124. }
  125. }
  126. var exp = {};
  127. exp.boot = function(callback) {
  128. var port = config.server.port;
  129. var bind_ip = config.server.bind;
  130. server = http.createServer(requestHandler).listen(port, bind_ip, function() {
  131. logging.log("Server running on http://" + bind_ip + ":" + port + "/");
  132. if (callback) {
  133. callback();
  134. }
  135. });
  136. // stop accepting new connections,
  137. // wait for established connections to finish (30s max),
  138. // then exit
  139. process.on("SIGTERM", function() {
  140. logging.warn("Got SIGTERM, no longer accepting new connections!");
  141. setTimeout(function() {
  142. logging.error("Dropping connections after 30s. Force quit.");
  143. process.exit(1);
  144. }, 30000);
  145. server.close(function() {
  146. logging.log("All connections closed, shutting down.");
  147. process.exit();
  148. });
  149. });
  150. };
  151. exp.close = function(callback) {
  152. server.close(callback);
  153. };
  154. module.exports = exp;
  155. if (require.main === module) {
  156. logging.error("Please use 'npm start' or 'www.js'");
  157. process.exit(1);
  158. }