diff --git a/source/funkin/backend/utils/HttpUtil.hx b/source/funkin/backend/utils/HttpUtil.hx index 1fc78f509b..de390c822e 100644 --- a/source/funkin/backend/utils/HttpUtil.hx +++ b/source/funkin/backend/utils/HttpUtil.hx @@ -2,19 +2,32 @@ package funkin.backend.utils; import haxe.Http; +/** + * Utility class for making synchronous HTTP requests with automatic redirect handling. + * All methods are blocking and throw `HttpError` on failure. Redirects (301, 302, 307, 308) are followed recursively using the `Location` response header. + */ final class HttpUtil { + /** + * The User-Agent header sent with every request. + * Defaults to `Flags.USER_AGENT`. + */ public static var userAgent:String = Flags.USER_AGENT; + /** + * Makes a synchronous GET request and returns the response body as a string. + * Automatically follows redirects. + * @param url The URL to request. + * @return The response body as a `String`. + * @throws HttpError If the request fails, redirects without a `Location` header, or returns an empty response. + */ public static function requestText(url:String):String { var result:String = null; var error:HttpError = null; var redirected:Bool = false; - var h = new Http(url); h.setHeader("User-Agent", userAgent); - h.onStatus = function(status) { redirected = isRedirect(status); @@ -27,38 +40,37 @@ final class HttpUtil error = new HttpError("Missing Location header in redirect", url, status, true); } }; - h.onData = function(data) { if (result == null) result = data; }; - h.onError = function(msg) { error = new HttpError(msg, url); }; - h.request(false); - if (error != null) throw error; - if (result == null) throw new HttpError("Unknown error or empty response", url); - return result; } + /** + * Makes a synchronous GET request and returns the response body as raw bytes. + * This automatically follows redirects. + * @param url The URL to request. + * @return The response body as `haxe.io.Bytes`. + * @throws HttpError If the request fails, redirects without a `Location` header, or returns an empty response. + */ public static function requestBytes(url:String):haxe.io.Bytes { var result:haxe.io.Bytes = null; var error:HttpError = null; var redirected:Bool = false; - var h = new Http(url); h.setHeader("User-Agent", userAgent); - h.onStatus = function(status) { redirected = isRedirect(status); @@ -71,29 +83,27 @@ final class HttpUtil error = new HttpError("Missing Location header in redirect", url, status, true); } }; - h.onBytes = function(data) { if (result == null) result = data; }; - h.onError = function(msg) { error = new HttpError(msg, url); }; - h.request(false); - if (error != null) throw error; - if (result == null) throw new HttpError("Unknown error or empty byte response", url); - return result; } + /** + * Checks whether an internet connection is available by pinging (or requesting) `google.com`. + * @return `true` if the request succeeded, `false` if it threw an `HttpError`. + */ public static function hasInternet():Bool { try { @@ -105,11 +115,16 @@ final class HttpUtil } } + /** + * Returns whether an HTTP status code represents a redirect. + * It handles 301, 302, 307, and 308. + * @param status The HTTP status code to check. + * @return `true` if the status is a redirect code. + */ private static function isRedirect(status:Int):Bool { switch (status) { - // 301: Moved Permanently, 302: Found (Moved Temporarily), 307: Temporary Redirect, 308: Permanent Redirect - Nex case 301 | 302 | 307 | 308: Logs.traceColored([Logs.logText('[Connection Status] ', BLUE), Logs.logText('Redirected with status code: ', YELLOW), Logs.logText('$status', GREEN)], VERBOSE); return true; @@ -118,31 +133,48 @@ final class HttpUtil } } -private class HttpError { +/** + * Represents an HTTP error with context about the failed request. + */ +private class HttpError +{ + /** The error message. */ public var message:String; + /** The URL that triggered the error. */ public var url:String; + /** The HTTP status code, or `-1` if not applicable. */ public var status:Int; + /** Whether the error occurred during a redirect. */ public var redirected:Bool; - public function new(message:String, url:String, ?status:Int = -1, ?redirected:Bool = false) { + /** + * @param message Description of the Error + * @param url The URL associated with the failed request. + * @param status HTTP status code. Defaults to `-1`. + * @param redirected Whether this error occurred mid-redirect. Defaults to `false`. + */ + public function new(message:String, url:String, ?status:Int = -1, ?redirected:Bool = false) + { this.message = message; this.url = url; this.status = status; this.redirected = redirected; } - public function toString():String { + /** + * Returns a formatted string representation of the error. + * The format should look like this: `[HttpError] | Status: N | (Redirected) | URL: ... | Message: ...` + * @return An error summary in a string + */ + public function toString():String + { var parts:Array = ['[HttpError]']; - if (status != -1) parts.push('Status: $status'); - if (redirected) parts.push('(Redirected)'); - parts.push('URL: $url'); parts.push('Message: $message'); - return parts.join(' | '); } }