server.js 4.5 KB

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