response.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  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. mojang/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. 4: "server error;cached" // tried to check but ran into server error, using cached version
  12. };
  13. // print these, but without stacktrace
  14. var silent_errors = ["ETIMEDOUT", "ESOCKETTIMEDOUT", "ECONNRESET", "EHOSTUNREACH", "ECONNREFUSED", "HTTPERROR", "RATELIMIT"];
  15. // handles HTTP responses
  16. // +request+ a http.IncomingMessage
  17. // +response+ a http.ServerResponse
  18. // +result+ an object with:
  19. // * status: see human_status, required for images without err
  20. // * redirect: redirect URL
  21. // * body: file or message, required unless redirect is present or status is < 0
  22. // * type: a valid Content-Type for the body, defaults to "text/plain"
  23. // * hash: image hash, required when body is an image
  24. // * err: a possible Error
  25. // * code: override HTTP response code when status is < 0
  26. module.exports = function(request, response, result) {
  27. // These headers are the same for every response
  28. var headers = {
  29. "Content-Type": result.body && result.type || "text/plain",
  30. "Content-Length": Buffer.from(result.body || "").length,
  31. "Cache-Control": "max-age=" + config.caching.browser,
  32. "Response-Time": Date.now() - request.start,
  33. "X-Request-ID": request.id,
  34. "Access-Control-Allow-Origin": "*",
  35. };
  36. response.on("finish", function() {
  37. logging.log(request.id, request.method, request.url.href, response.statusCode, headers["Response-Time"] + "ms", "(" + (human_status[result.status] || "-") + ")");
  38. });
  39. response.on("error", function(err) {
  40. logging.error(request.id, err);
  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. // use crc32 as a hash function for Etag
  57. var etag = "\"" + crc(result.body || "") + "\"";
  58. // handle etag caching
  59. var incoming_etag = request.headers["if-none-match"];
  60. // also respond with 304 on server error (use client's version)
  61. // don't respond with 304 when debugging is enabled
  62. if (incoming_etag && (incoming_etag === etag || result.status === -1 && !config.server.debug_enabled)) {
  63. response.writeHead(304, headers);
  64. response.end();
  65. return;
  66. }
  67. if (result.redirect) {
  68. headers.Location = result.redirect;
  69. response.writeHead(307, headers);
  70. response.end();
  71. return;
  72. }
  73. if (result.status === -2) {
  74. response.writeHead(result.code || 422, headers);
  75. } else if (result.status === -1) {
  76. // server errors shouldn't be cached
  77. headers["Cache-Control"] = "no-cache, max-age=0";
  78. if (result.body && result.hash && !result.hash.startsWith("mhf_")) {
  79. headers["Warning"] = '110 Crafatar "Response is Stale"'
  80. headers["Etag"] = etag;
  81. result.code = result.code || 200;
  82. }
  83. if (result.err && result.err.code === "ENOENT") {
  84. result.code = result.code || 500;
  85. }
  86. if (!result.code) {
  87. // Don't use 502 on Cloudflare
  88. // As they will show their own error page instead
  89. // https://support.cloudflare.com/hc/en-us/articles/200172706
  90. result.code = config.caching.cloudflare ? 500 : 502;
  91. }
  92. response.writeHead(result.code, headers);
  93. } else {
  94. if (result.body) {
  95. if (result.status === 4) {
  96. headers["Warning"] = '111 Crafatar "Revalidation Failed"'
  97. }
  98. headers["Etag"] = etag;
  99. response.writeHead(200, headers);
  100. } else {
  101. response.writeHead(404, headers);
  102. }
  103. }
  104. response.end(result.body);
  105. };