diff --git a/src/main/java/me/braydon/mc/common/MojangServer.java b/src/main/java/me/braydon/mc/common/MojangServer.java new file mode 100644 index 0000000..349e507 --- /dev/null +++ b/src/main/java/me/braydon/mc/common/MojangServer.java @@ -0,0 +1,101 @@ +/* + * MIT License + * + * Copyright (c) 2024 Braydon (Rainnny). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package me.braydon.mc.common; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NonNull; +import lombok.ToString; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * Represents a service provided by Mojang. + * + * @author Braydon + */ +@AllArgsConstructor @Getter @ToString +public enum MojangServer { + SESSION("https://sessionserver.mojang.com"), + API("https://api.mojang.com"), + TEXTURES("https://textures.minecraft.net"), + ASSETS("https://assets.mojang.com"), + LIBRARIES("https://libraries.minecraft.net"); + + private static final int STATUS_TIMEOUT = 7000; + + /** + * The endpoint of this service. + */ + @NonNull private final String endpoint; + + /** + * Get the status of this service. + * + * @return the service status + */ + @NonNull + public Status getStatus() { + try { + InetAddress address = InetAddress.getByName(endpoint.substring(8)); + long before = System.currentTimeMillis(); + if (address.isReachable(STATUS_TIMEOUT)) { + // The time it took to reach the host is 75% of + // the timeout, consider it to be degraded. + if ((System.currentTimeMillis() - before) > STATUS_TIMEOUT * 0.75D) { + return Status.DEGRADED; + } + return Status.ONLINE; + } + } catch (UnknownHostException ex) { + ex.printStackTrace(); + } catch (IOException ignored) { + // We can safely ignore any errors, we're simply checking + // if the host is reachable, if it's not, it's offline. + } + return Status.OFFLINE; + } + + /** + * The status of a service. + */ + public enum Status { + /** + * The service is online and accessible. + */ + ONLINE, + + /** + * The service is online, but is experiencing degraded performance. + */ + DEGRADED, + + /** + * The service is offline and inaccessible. + */ + OFFLINE + } +} \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/controller/MojangController.java b/src/main/java/me/braydon/mc/controller/MojangController.java new file mode 100644 index 0000000..2ff2c5e --- /dev/null +++ b/src/main/java/me/braydon/mc/controller/MojangController.java @@ -0,0 +1,78 @@ +/* + * MIT License + * + * Copyright (c) 2024 Braydon (Rainnny). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package me.braydon.mc.controller; + +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.NonNull; +import lombok.extern.log4j.Log4j2; +import me.braydon.mc.common.MojangServer; +import me.braydon.mc.exception.impl.BadRequestException; +import me.braydon.mc.service.MojangService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +/** + * The controller for handling + * Mojang related requests. + * + * @author Braydon + */ +@RestController +@RequestMapping(value = "/mojang", produces = MediaType.APPLICATION_JSON_VALUE) +@Log4j2(topic = "Mojang Controller") +@Tag(name = "Mojang Controller", description = "The controller for handling Mojang related requests.") +public final class MojangController { + /** + * The Mojang service to use. + */ + @NonNull private final MojangService mojangService; + + @Autowired + public MojangController(@NonNull MojangService mojangService) { + this.mojangService = mojangService; + } + + /** + * A GET route to get the status of Mojang servers. + * + * @return the status response + */ + @GetMapping("/status") + @ResponseBody + public ResponseEntity> getStatus() throws BadRequestException { + Map response = new HashMap<>(); + for (Map.Entry entry : mojangService.getMojangServerStatuses().entrySet()) { + response.put(entry.getKey().getEndpoint(), entry.getValue().name()); + } + return ResponseEntity.ok(response); + } +} \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/controller/PlayerController.java b/src/main/java/me/braydon/mc/controller/PlayerController.java index 62974ef..1a6561b 100644 --- a/src/main/java/me/braydon/mc/controller/PlayerController.java +++ b/src/main/java/me/braydon/mc/controller/PlayerController.java @@ -50,7 +50,7 @@ import org.springframework.web.bind.annotation.*; @Tag(name = "Player Controller", description = "The controller for handling player related requests.") public final class PlayerController { /** - * The Mojang service to use for player information. + * The Mojang service to use. */ @NonNull private final MojangService mojangService; diff --git a/src/main/java/me/braydon/mc/controller/ServerController.java b/src/main/java/me/braydon/mc/controller/ServerController.java index a2c501a..46217a0 100644 --- a/src/main/java/me/braydon/mc/controller/ServerController.java +++ b/src/main/java/me/braydon/mc/controller/ServerController.java @@ -52,7 +52,7 @@ import java.util.Map; @Tag(name = "Server Controller", description = "The controller for handling server related requests.") public final class ServerController { /** - * The Mojang service to use for server information. + * The Mojang service to use. */ @NonNull private final MojangService mojangService; diff --git a/src/main/java/me/braydon/mc/service/MojangService.java b/src/main/java/me/braydon/mc/service/MojangService.java index 0d51095..03ea8b2 100644 --- a/src/main/java/me/braydon/mc/service/MojangService.java +++ b/src/main/java/me/braydon/mc/service/MojangService.java @@ -28,6 +28,7 @@ import com.google.common.base.Splitter; import com.google.common.collect.Lists; import com.google.common.hash.Hashing; import jakarta.annotation.PostConstruct; +import lombok.Getter; import lombok.NonNull; import lombok.SneakyThrows; import lombok.extern.log4j.Log4j2; @@ -77,11 +78,9 @@ import java.util.concurrent.TimeUnit; @Service @Log4j2(topic = "Mojang Service") public final class MojangService { - private static final String SESSION_SERVER_ENDPOINT = "https://sessionserver.mojang.com"; - private static final String API_ENDPOINT = "https://api.mojang.com"; - private static final String UUID_TO_PROFILE = SESSION_SERVER_ENDPOINT + "/session/minecraft/profile/%s"; - private static final String USERNAME_TO_UUID = API_ENDPOINT + "/users/profiles/minecraft/%s"; - private static final String FETCH_BLOCKED_SERVERS = SESSION_SERVER_ENDPOINT + "/blockedservers"; + private static final String UUID_TO_PROFILE = MojangServer.SESSION.getEndpoint() + "/session/minecraft/profile/%s"; + private static final String USERNAME_TO_UUID = MojangServer.API.getEndpoint() + "/users/profiles/minecraft/%s"; + private static final String FETCH_BLOCKED_SERVERS = MojangServer.SESSION.getEndpoint() + "/blockedservers"; private static final int DEFAULT_PART_TEXTURE_SIZE = 128; private static final int MAX_PART_TEXTURE_SIZE = 512; @@ -102,7 +101,7 @@ public final class MojangService { @NonNull private final PlayerCacheRepository playerCache; /** - * The cache repository for {@link Skin.Part}'s. + * The cache repository for {@link ISkinPart}'s. */ @NonNull private final SkinPartTextureCacheRepository skinPartTextureCache; @@ -111,6 +110,11 @@ public final class MojangService { */ @NonNull private final MinecraftServerCacheRepository minecraftServerCache; + /** + * Mapped statuses for {@link MojangServer}'s. + */ + @Getter private final Map mojangServerStatuses = Collections.synchronizedMap(new HashMap<>()); + /** * A list of banned server hashes provided by Mojang. *

@@ -140,6 +144,15 @@ public final class MojangService { @PostConstruct public void onInitialize() { + // Schedule a task to fetch statuses + // of Mojang servers every few minutes + new Timer().scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + fetchMojangServerStatuses(); + } + }, 0L, 60L * 3L * 1000L); + // Schedule a task to fetch blocked // servers from Mojang every 15 minutes. new Timer().scheduleAtFixedRate(new TimerTask() { @@ -501,6 +514,22 @@ public final class MojangService { } } + /** + * Fetch the statuses of {@link MojangServer}'s. + */ + @SneakyThrows + private void fetchMojangServerStatuses() { + log.info("Checking Mojang server statuses..."); + long before = System.currentTimeMillis(); + for (MojangServer server : MojangServer.values()) { + log.info("Pinging {}...", server.getEndpoint()); + MojangServer.Status status = server.getStatus(); // Retrieve the server status + log.info("Retrieved status of {}: {}", server.getEndpoint(), status.name()); + mojangServerStatuses.put(server, status); // Cache the server status + } + log.info("Mojang server status check took {}ms", System.currentTimeMillis() - before); + } + /** * Fetch a list of blocked servers from Mojang. */