|
@@ -1,81 +1,119 @@
|
|
|
-var http_code = require("http").STATUS_CODES;
|
|
|
var logging = require("./logging");
|
|
|
var request = require("request");
|
|
|
var config = require("../config");
|
|
|
var skins = require("./skins");
|
|
|
+var http = require("http");
|
|
|
require("./object-patch");
|
|
|
|
|
|
var session_url = "https://sessionserver.mojang.com/session/minecraft/profile/";
|
|
|
var textures_url = "https://textures.minecraft.net/texture/";
|
|
|
|
|
|
+// count requests made to session_url in the last 1000ms
|
|
|
+var session_requests = [];
|
|
|
+
|
|
|
var exp = {};
|
|
|
|
|
|
+function req_count() {
|
|
|
+ var index = session_requests.findIndex((i) => i >= Date.now() - 1000);
|
|
|
+ if (index >= 0) {
|
|
|
+ return session_requests.length - index;
|
|
|
+ } else {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+exp.resetCounter = function() {
|
|
|
+ var count = req_count();
|
|
|
+ if (count) {
|
|
|
+ var logfunc = count >= config.server.sessions_rate_limit ? logging.warn : logging.debug;
|
|
|
+ logfunc('Clearing old session requests (count was ' + count + ')');
|
|
|
+ session_requests.splice(0, session_requests.length - count);
|
|
|
+ } else {
|
|
|
+ session_requests = []
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// performs a GET request to the +url+
|
|
|
// +options+ object includes these options:
|
|
|
// encoding (string), default is to return a buffer
|
|
|
// callback: the body, response,
|
|
|
// and error buffer. get_from helper method is available
|
|
|
exp.get_from_options = function(rid, url, options, callback) {
|
|
|
- request.get({
|
|
|
- url: url,
|
|
|
- headers: {
|
|
|
- "User-Agent": "Crafatar (+https://crafatar.com)"
|
|
|
- },
|
|
|
- timeout: config.server.http_timeout,
|
|
|
- followRedirect: false,
|
|
|
- encoding: options.encoding || null,
|
|
|
- }, function(error, response, body) {
|
|
|
- // log url + code + description
|
|
|
- var code = response && response.statusCode;
|
|
|
-
|
|
|
- var logfunc = code && (code < 400 || code === 404) ? logging.debug : logging.warn;
|
|
|
- logfunc(rid, url, code || error && error.code, http_code[code]);
|
|
|
-
|
|
|
- // not necessarily used
|
|
|
- var e = new Error(code);
|
|
|
+ var session_req = url.startsWith(session_url);
|
|
|
+
|
|
|
+ // This is to prevent being blocked by CloudFront for exceeding the rate limit
|
|
|
+ if (session_req && req_count() >= config.server.sessions_rate_limit) {
|
|
|
+ var e = new Error("Skipped, rate limit exceeded");
|
|
|
e.name = "HTTP";
|
|
|
- e.code = "HTTPERROR";
|
|
|
-
|
|
|
- switch (code) {
|
|
|
- case 200:
|
|
|
- case 301:
|
|
|
- case 302: // never seen, but mojang might use it in future
|
|
|
- case 307: // never seen, but mojang might use it in future
|
|
|
- case 308: // never seen, but mojang might use it in future
|
|
|
- // these are okay
|
|
|
- break;
|
|
|
- case 204: // no content, used like 404 by mojang. making sure it really has no content
|
|
|
- case 404:
|
|
|
- // can be cached as null
|
|
|
- body = null;
|
|
|
- break;
|
|
|
- case 403: // Blocked by CloudFront :(
|
|
|
- case 429: // this shouldn't usually happen, but occasionally does
|
|
|
- case 500:
|
|
|
- case 502: // CloudFront can't reach mojang origin
|
|
|
- case 503:
|
|
|
- case 504:
|
|
|
- // we don't want to cache this
|
|
|
- error = error || e;
|
|
|
- body = null;
|
|
|
- break;
|
|
|
- default:
|
|
|
- if (!error) {
|
|
|
- // Probably 500 or the likes
|
|
|
- logging.error(rid, "Unexpected response:", code, body);
|
|
|
- }
|
|
|
- error = error || e;
|
|
|
- body = null;
|
|
|
- break;
|
|
|
- }
|
|
|
+ e.code = "RATELIMIT";
|
|
|
|
|
|
- if (body && !body.length) {
|
|
|
- // empty response
|
|
|
- body = null;
|
|
|
- }
|
|
|
+ var response = new http.IncomingMessage();
|
|
|
+ response.statusCode = 403;
|
|
|
|
|
|
- callback(body, response, error);
|
|
|
- });
|
|
|
+ callback(null, response, e);
|
|
|
+ } else {
|
|
|
+ session_req && session_requests.push(Date.now());
|
|
|
+ request.get({
|
|
|
+ url: url,
|
|
|
+ headers: {
|
|
|
+ "User-Agent": "Crafatar (+https://crafatar.com)"
|
|
|
+ },
|
|
|
+ timeout: config.server.http_timeout,
|
|
|
+ followRedirect: false,
|
|
|
+ encoding: options.encoding || null,
|
|
|
+ }, function(error, response, body) {
|
|
|
+ // log url + code + description
|
|
|
+ var code = response && response.statusCode;
|
|
|
+
|
|
|
+ var logfunc = code && (code < 400 || code === 404) ? logging.debug : logging.warn;
|
|
|
+ logfunc(rid, url, code || error && error.code, http.STATUS_CODES[code]);
|
|
|
+
|
|
|
+ // not necessarily used
|
|
|
+ var e = new Error(code);
|
|
|
+ e.name = "HTTP";
|
|
|
+ e.code = "HTTPERROR";
|
|
|
+
|
|
|
+ switch (code) {
|
|
|
+ case 200:
|
|
|
+ case 301:
|
|
|
+ case 302: // never seen, but mojang might use it in future
|
|
|
+ case 307: // never seen, but mojang might use it in future
|
|
|
+ case 308: // never seen, but mojang might use it in future
|
|
|
+ // these are okay
|
|
|
+ break;
|
|
|
+ case 204: // no content, used like 404 by mojang. making sure it really has no content
|
|
|
+ case 404:
|
|
|
+ // can be cached as null
|
|
|
+ body = null;
|
|
|
+ break;
|
|
|
+ case 403: // Blocked by CloudFront :(
|
|
|
+ case 429: // this shouldn't usually happen, but occasionally does
|
|
|
+ case 500:
|
|
|
+ case 502: // CloudFront can't reach mojang origin
|
|
|
+ case 503:
|
|
|
+ case 504:
|
|
|
+ // we don't want to cache this
|
|
|
+ error = error || e;
|
|
|
+ body = null;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ if (!error) {
|
|
|
+ // Probably 500 or the likes
|
|
|
+ logging.error(rid, "Unexpected response:", code, body);
|
|
|
+ }
|
|
|
+ error = error || e;
|
|
|
+ body = null;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (body && !body.length) {
|
|
|
+ // empty response
|
|
|
+ body = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ callback(body, response, error);
|
|
|
+ });
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
// helper method for get_from_options, no options required
|