response.js 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. var logging = require("./logging");
  2. var config = require("../config");
  3. var crc = require("crc").crc32;
  4. var human_status = {
  5. "-2": "user error", // e.g. invalid size
  6. "-1": "server error", // e.g. network issues
  7. 0: "none", // cached as null (user has no skin)
  8. 1: "cached", // found on disk
  9. 2: "downloaded", // profile downloaded, skin downloaded from mojang servers
  10. 3: "checked", // profile re-downloaded (was too old), has no skin or skin cached
  11. };
  12. // print these, but without stacktrace
  13. var silent_errors = ["ETIMEDOUT", "ESOCKETTIMEDOUT", "ECONNRESET", "EHOSTUNREACH", "ECONNREFUSED", "TooManyRequestsException"];
  14. // handles HTTP responses
  15. // +request+ a http.IncomingMessage
  16. // +response+ a http.ServerResponse
  17. // +result+ an object with:
  18. // * status: see human_status, required for images without err
  19. // * redirect: redirect URL
  20. // * body: file or message, required unless redirect is present or status is < 0
  21. // * type: a valid Content-Type for the body, defaults to "text/plain"
  22. // * hash: image hash, required when body is an image
  23. // * err: a possible Error
  24. module.exports = function(request, response, result) {
  25. response.on("close", function() {
  26. logging.warn(request.id, "Connection closed");
  27. });
  28. response.on("finish", function() {
  29. logging.log(request.method, request.url.href, request.id, response.statusCode, headers["Response-Time"] + "ms", "(" + (human_status[result.status] || "-") + ")");
  30. });
  31. response.on("error", function(err) {
  32. logging.error(request.id, err);
  33. });
  34. // These headers are the same for every response
  35. var headers = {
  36. "Content-Type": result.body && result.type || "text/plain",
  37. "Cache-Control": "max-age=" + config.caching.browser + ", public",
  38. "Response-Time": Date.now() - request.start,
  39. "X-Request-ID": request.id,
  40. "Access-Control-Allow-Origin": "*"
  41. };
  42. if (result.err) {
  43. var silent = silent_errors.indexOf(result.err.code) !== -1;
  44. if (result.err.stack && !silent) {
  45. logging.error(request.id, result.err.stack);
  46. } else if (silent) {
  47. logging.warn(request.id, result.err);
  48. } else {
  49. logging.error(request.id, result.err);
  50. }
  51. result.status = -1;
  52. }
  53. if (result.status !== undefined && result.status !== null) {
  54. headers["X-Storage-Type"] = human_status[result.status];
  55. }
  56. if (result.body) {
  57. // use Mojang's image hash if available
  58. // use crc32 as a hash function otherwise
  59. var etag = result.hash && result.hash.substr(0, 10) || crc(result.body);
  60. headers.Etag = "\"" + etag + "\"";
  61. // handle etag caching
  62. var incoming_etag = request.headers["if-none-match"];
  63. if (incoming_etag && incoming_etag === headers.Etag) {
  64. response.writeHead(304, headers);
  65. response.end();
  66. return;
  67. }
  68. }
  69. if (result.redirect) {
  70. headers.Location = result.redirect;
  71. response.writeHead(307, headers);
  72. response.end();
  73. return;
  74. }
  75. if (result.status === -2) {
  76. response.writeHead(result.code || 422, headers);
  77. response.end(result.body);
  78. } else if (result.status === -1) {
  79. response.writeHead(500, headers);
  80. response.end(result.body);
  81. } else {
  82. response.writeHead(result.body ? 200 : 404, headers);
  83. response.end(result.body);
  84. }
  85. };