Add Mojang status route
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m35s

This commit is contained in:
Braydon 2024-04-13 11:39:51 -04:00
parent 6d112ac658
commit 6c6b9349f2
5 changed files with 216 additions and 8 deletions

@ -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
}
}

@ -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<Map<String, String>> getStatus() throws BadRequestException {
Map<String, String> response = new HashMap<>();
for (Map.Entry<MojangServer, MojangServer.Status> entry : mojangService.getMojangServerStatuses().entrySet()) {
response.put(entry.getKey().getEndpoint(), entry.getValue().name());
}
return ResponseEntity.ok(response);
}
}

@ -50,7 +50,7 @@ import org.springframework.web.bind.annotation.*;
@Tag(name = "Player Controller", description = "The controller for handling player related requests.") @Tag(name = "Player Controller", description = "The controller for handling player related requests.")
public final class PlayerController { public final class PlayerController {
/** /**
* The Mojang service to use for player information. * The Mojang service to use.
*/ */
@NonNull private final MojangService mojangService; @NonNull private final MojangService mojangService;

@ -52,7 +52,7 @@ import java.util.Map;
@Tag(name = "Server Controller", description = "The controller for handling server related requests.") @Tag(name = "Server Controller", description = "The controller for handling server related requests.")
public final class ServerController { public final class ServerController {
/** /**
* The Mojang service to use for server information. * The Mojang service to use.
*/ */
@NonNull private final MojangService mojangService; @NonNull private final MojangService mojangService;

@ -28,6 +28,7 @@ import com.google.common.base.Splitter;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.hash.Hashing; import com.google.common.hash.Hashing;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
@ -77,11 +78,9 @@ import java.util.concurrent.TimeUnit;
@Service @Service
@Log4j2(topic = "Mojang Service") @Log4j2(topic = "Mojang Service")
public final class MojangService { public final class MojangService {
private static final String SESSION_SERVER_ENDPOINT = "https://sessionserver.mojang.com"; private static final String UUID_TO_PROFILE = MojangServer.SESSION.getEndpoint() + "/session/minecraft/profile/%s";
private static final String API_ENDPOINT = "https://api.mojang.com"; private static final String USERNAME_TO_UUID = MojangServer.API.getEndpoint() + "/users/profiles/minecraft/%s";
private static final String UUID_TO_PROFILE = SESSION_SERVER_ENDPOINT + "/session/minecraft/profile/%s"; private static final String FETCH_BLOCKED_SERVERS = MojangServer.SESSION.getEndpoint() + "/blockedservers";
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 int DEFAULT_PART_TEXTURE_SIZE = 128; private static final int DEFAULT_PART_TEXTURE_SIZE = 128;
private static final int MAX_PART_TEXTURE_SIZE = 512; private static final int MAX_PART_TEXTURE_SIZE = 512;
@ -102,7 +101,7 @@ public final class MojangService {
@NonNull private final PlayerCacheRepository playerCache; @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; @NonNull private final SkinPartTextureCacheRepository skinPartTextureCache;
@ -111,6 +110,11 @@ public final class MojangService {
*/ */
@NonNull private final MinecraftServerCacheRepository minecraftServerCache; @NonNull private final MinecraftServerCacheRepository minecraftServerCache;
/**
* Mapped statuses for {@link MojangServer}'s.
*/
@Getter private final Map<MojangServer, MojangServer.Status> mojangServerStatuses = Collections.synchronizedMap(new HashMap<>());
/** /**
* A list of banned server hashes provided by Mojang. * A list of banned server hashes provided by Mojang.
* <p> * <p>
@ -140,6 +144,15 @@ public final class MojangService {
@PostConstruct @PostConstruct
public void onInitialize() { 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 // Schedule a task to fetch blocked
// servers from Mojang every 15 minutes. // servers from Mojang every 15 minutes.
new Timer().scheduleAtFixedRate(new TimerTask() { 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. * Fetch a list of blocked servers from Mojang.
*/ */