forked from MinecraftUtilities/Backend
fix mojang status endpoint
This commit is contained in:
parent
ee5b1f12d8
commit
2b017f9ef7
@ -22,7 +22,7 @@ public final class AppConfig {
|
|||||||
private static boolean isRunningTest = true;
|
private static boolean isRunningTest = true;
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
Class.forName("org.junit.Test");
|
Class.forName("org.junit.jupiter.engine.JupiterTestEngine");
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
isRunningTest = false;
|
isRunningTest = false;
|
||||||
}
|
}
|
||||||
|
85
src/main/java/xyz/mcutils/backend/common/MojangServer.java
Normal file
85
src/main/java/xyz/mcutils/backend/common/MojangServer.java
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package xyz.mcutils.backend.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;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fascinated (fascinated7)
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
public enum MojangServer {
|
||||||
|
SESSION("Session Server", "https://sessionserver.mojang.com"),
|
||||||
|
API("Mojang API", "https://api.mojang.com"),
|
||||||
|
TEXTURES("Textures Server", "https://textures.minecraft.net"),
|
||||||
|
ASSETS("Assets Server", "https://assets.mojang.com"),
|
||||||
|
LIBRARIES("Libraries Server", "https://libraries.minecraft.net"),
|
||||||
|
SERVICES("Minecraft Services", "https://api.minecraftservices.com");
|
||||||
|
|
||||||
|
private static final long STATUS_TIMEOUT = TimeUnit.SECONDS.toMillis(4);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of this server.
|
||||||
|
*/
|
||||||
|
@NonNull private final String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The endpoint of this service.
|
||||||
|
*/
|
||||||
|
@NonNull private final String endpoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ping this service and get the status of it.
|
||||||
|
*
|
||||||
|
* @return the service status
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public Status getStatus() {
|
||||||
|
try {
|
||||||
|
InetAddress address = InetAddress.getByName(endpoint.substring(8));
|
||||||
|
long before = System.currentTimeMillis();
|
||||||
|
if (address.isReachable((int) 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
|
||||||
|
}
|
||||||
|
}
|
@ -24,9 +24,11 @@ public class MojangController {
|
|||||||
|
|
||||||
@ResponseBody
|
@ResponseBody
|
||||||
@GetMapping(value = "/status")
|
@GetMapping(value = "/status")
|
||||||
public ResponseEntity<CachedEndpointStatus> getStatus() {
|
public ResponseEntity<?> getStatus() {
|
||||||
CachedEndpointStatus status = mojangService.getMojangApiStatus();
|
CachedEndpointStatus status = mojangService.getMojangApiStatus();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.cacheControl(CacheControl.maxAge(1, TimeUnit.MINUTES).cachePublic())
|
.cacheControl(CacheControl.maxAge(1, TimeUnit.MINUTES).cachePublic())
|
||||||
.body(status);
|
.body(status);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package xyz.mcutils.backend.exception;
|
package xyz.mcutils.backend.exception;
|
||||||
|
|
||||||
import io.micrometer.common.lang.NonNull;
|
import io.micrometer.common.lang.NonNull;
|
||||||
|
import io.sentry.Sentry;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
@ -39,6 +40,7 @@ public final class ExceptionControllerAdvice {
|
|||||||
}
|
}
|
||||||
if (status == null) { // Fallback to 500
|
if (status == null) { // Fallback to 500
|
||||||
status = HttpStatus.INTERNAL_SERVER_ERROR;
|
status = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||||
|
Sentry.captureException(ex); // Capture the exception with Sentry
|
||||||
}
|
}
|
||||||
return new ResponseEntity<>(new ErrorResponse(status, message), status);
|
return new ResponseEntity<>(new ErrorResponse(status, message), status);
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,13 @@ import lombok.Setter;
|
|||||||
import org.springframework.data.annotation.Id;
|
import org.springframework.data.annotation.Id;
|
||||||
import org.springframework.data.redis.core.RedisHash;
|
import org.springframework.data.redis.core.RedisHash;
|
||||||
import xyz.mcutils.backend.common.CachedResponse;
|
import xyz.mcutils.backend.common.CachedResponse;
|
||||||
import xyz.mcutils.backend.model.mojang.EndpointStatus;
|
import xyz.mcutils.backend.common.MojangServer;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Setter @Getter @EqualsAndHashCode(callSuper = false)
|
@Setter @Getter @EqualsAndHashCode(callSuper = false)
|
||||||
@RedisHash(value = "mojangEndpointStatus", timeToLive = 60L) // 1 minute (in seconds)
|
@RedisHash(value = "mojangEndpointStatus", timeToLive = 60L) // 1 minute (in seconds)
|
||||||
@ -26,11 +29,21 @@ public class CachedEndpointStatus extends CachedResponse implements Serializable
|
|||||||
/**
|
/**
|
||||||
* The endpoint cache.
|
* The endpoint cache.
|
||||||
*/
|
*/
|
||||||
private final List<EndpointStatus> endpoints;
|
private final List<Map<String, Object>> endpoints;
|
||||||
|
|
||||||
public CachedEndpointStatus(@NonNull String id, List<EndpointStatus> endpoints) {
|
public CachedEndpointStatus(@NonNull String id, Map<MojangServer, MojangServer.Status> mojangServers) {
|
||||||
super(Cache.defaultCache());
|
super(Cache.defaultCache());
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.endpoints = endpoints;
|
this.endpoints = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Map.Entry<MojangServer, MojangServer.Status> entry : mojangServers.entrySet()) {
|
||||||
|
MojangServer server = entry.getKey();
|
||||||
|
|
||||||
|
Map<String, Object> serverStatus = new HashMap<>();
|
||||||
|
serverStatus.put("name", server.getName());
|
||||||
|
serverStatus.put("endpoint", server.getEndpoint());
|
||||||
|
serverStatus.put("status", entry.getValue().name());
|
||||||
|
endpoints.add(serverStatus);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,6 +3,7 @@ package xyz.mcutils.backend.service;
|
|||||||
import com.maxmind.geoip2.DatabaseReader;
|
import com.maxmind.geoip2.DatabaseReader;
|
||||||
import com.maxmind.geoip2.exception.GeoIp2Exception;
|
import com.maxmind.geoip2.exception.GeoIp2Exception;
|
||||||
import com.maxmind.geoip2.model.CityResponse;
|
import com.maxmind.geoip2.model.CityResponse;
|
||||||
|
import io.sentry.Sentry;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
import org.codehaus.plexus.archiver.tar.TarGZipUnArchiver;
|
import org.codehaus.plexus.archiver.tar.TarGZipUnArchiver;
|
||||||
@ -68,6 +69,7 @@ public class MaxMindService {
|
|||||||
return database.city(InetAddress.getByName(ip));
|
return database.city(InetAddress.getByName(ip));
|
||||||
} catch (IOException | GeoIp2Exception e) {
|
} catch (IOException | GeoIp2Exception e) {
|
||||||
log.error("Failed to lookup the GeoIP information for '{}'", ip, e);
|
log.error("Failed to lookup the GeoIP information for '{}'", ip, e);
|
||||||
|
Sentry.captureException(e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,16 +13,15 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import xyz.mcutils.backend.common.AppConfig;
|
import xyz.mcutils.backend.common.AppConfig;
|
||||||
import xyz.mcutils.backend.common.ExpiringSet;
|
import xyz.mcutils.backend.common.ExpiringSet;
|
||||||
|
import xyz.mcutils.backend.common.MojangServer;
|
||||||
import xyz.mcutils.backend.common.WebRequest;
|
import xyz.mcutils.backend.common.WebRequest;
|
||||||
import xyz.mcutils.backend.model.cache.CachedEndpointStatus;
|
import xyz.mcutils.backend.model.cache.CachedEndpointStatus;
|
||||||
import xyz.mcutils.backend.model.mojang.EndpointStatus;
|
|
||||||
import xyz.mcutils.backend.model.token.MojangProfileToken;
|
import xyz.mcutils.backend.model.token.MojangProfileToken;
|
||||||
import xyz.mcutils.backend.model.token.MojangUsernameToUuidToken;
|
import xyz.mcutils.backend.model.token.MojangUsernameToUuidToken;
|
||||||
import xyz.mcutils.backend.repository.redis.EndpointStatusRepository;
|
import xyz.mcutils.backend.repository.redis.EndpointStatusRepository;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@ -49,18 +48,6 @@ public class MojangService {
|
|||||||
*/
|
*/
|
||||||
private static final long FETCH_BLOCKED_SERVERS_INTERVAL = TimeUnit.HOURS.toMillis(1L);
|
private static final long FETCH_BLOCKED_SERVERS_INTERVAL = TimeUnit.HOURS.toMillis(1L);
|
||||||
|
|
||||||
/**
|
|
||||||
* Information about the Mojang API endpoints.
|
|
||||||
*/
|
|
||||||
private static final String MOJANG_ENDPOINT_STATUS_KEY = "mojang";
|
|
||||||
private static final List<EndpointStatus> MOJANG_ENDPOINTS = List.of(
|
|
||||||
new EndpointStatus("Minecraft Textures", "textures.minecraft.net"),
|
|
||||||
new EndpointStatus("Minecraft Libraries", "libraries.minecraft.net"),
|
|
||||||
new EndpointStatus("Minecraft Services", "api.minecraftservices.com"),
|
|
||||||
new EndpointStatus("Mojang Assets", "assets.mojang.com"),
|
|
||||||
new EndpointStatus("Mojang API", "api.mojang.com"),
|
|
||||||
new EndpointStatus("Mojang Session Server", "sessionserver.mojang.com"));
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private EndpointStatusRepository mojangEndpointStatusRepository;
|
private EndpointStatusRepository mojangEndpointStatusRepository;
|
||||||
|
|
||||||
@ -107,6 +94,8 @@ public class MojangService {
|
|||||||
}
|
}
|
||||||
bannedServerHashes = Collections.synchronizedList(hashes);
|
bannedServerHashes = Collections.synchronizedList(hashes);
|
||||||
log.info("Fetched {} banned server hashes", bannedServerHashes.size());
|
log.info("Fetched {} banned server hashes", bannedServerHashes.size());
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Failed to fetch blocked servers from Mojang", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,33 +173,24 @@ public class MojangService {
|
|||||||
*/
|
*/
|
||||||
public CachedEndpointStatus getMojangApiStatus() {
|
public CachedEndpointStatus getMojangApiStatus() {
|
||||||
log.info("Getting Mojang API status");
|
log.info("Getting Mojang API status");
|
||||||
Optional<CachedEndpointStatus> endpointStatus = mojangEndpointStatusRepository.findById(MOJANG_ENDPOINT_STATUS_KEY);
|
Optional<CachedEndpointStatus> endpointStatus = mojangEndpointStatusRepository.findById("mojang-servers-status");
|
||||||
if (endpointStatus.isPresent() && AppConfig.isProduction()) {
|
if (endpointStatus.isPresent() && AppConfig.isProduction()) {
|
||||||
log.info("Got cached Mojang API status");
|
log.info("Got cached Mojang API status");
|
||||||
return endpointStatus.get();
|
return endpointStatus.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
MOJANG_ENDPOINTS.parallelStream().forEach(endpoint -> {
|
Map<MojangServer, MojangServer.Status> mojangServers = new HashMap<>();
|
||||||
try {
|
Arrays.stream(MojangServer.values()).parallel().forEach(server -> {
|
||||||
long start = System.currentTimeMillis();
|
log.info("Pinging {}...", server.getEndpoint());
|
||||||
InetAddress address = InetAddress.getByName(endpoint.getHostname());
|
MojangServer.Status status = server.getStatus(); // Retrieve the server status
|
||||||
if (address.isReachable((int) TimeUnit.SECONDS.toMillis(4))) { // Check if the endpoint is reachable
|
log.info("Retrieved status of {}: {}", server.getEndpoint(), status.name());
|
||||||
endpoint.setStatus(EndpointStatus.Status.ONLINE);
|
mojangServers.put(server, status); // Cache the server status
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Check if the endpoint took too long to respond
|
|
||||||
if (System.currentTimeMillis() - start > TimeUnit.SECONDS.toMillis(2)) {
|
|
||||||
endpoint.setStatus(EndpointStatus.Status.DEGRADED);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
endpoint.setStatus(EndpointStatus.Status.OFFLINE);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
log.info("Fetched Mojang API status for {} endpoints", MOJANG_ENDPOINTS.size());
|
log.info("Fetched Mojang API status for {} endpoints", mojangServers.size());
|
||||||
CachedEndpointStatus status = new CachedEndpointStatus(
|
CachedEndpointStatus status = new CachedEndpointStatus(
|
||||||
MOJANG_ENDPOINT_STATUS_KEY,
|
"mojang-servers-status",
|
||||||
MOJANG_ENDPOINTS
|
mojangServers
|
||||||
);
|
);
|
||||||
mojangEndpointStatusRepository.save(status);
|
mojangEndpointStatusRepository.save(status);
|
||||||
status.getCache().setCached(false);
|
status.getCache().setCached(false);
|
||||||
|
Loading…
Reference in New Issue
Block a user