server.js 4.4 KB

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