25 Commits

Author SHA1 Message Date
5bbcadd3e1 Update dependency org.springframework.boot:spring-boot-starter-parent to v3.3.5 2024-10-24 14:01:15 +00:00
Lee
0bc614ce39 Merge pull request 'Update dependency com.influxdb:influxdb-client-java to v7.2.0' (#44) from renovate/com.influxdb-influxdb-client-java-7.x into master
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 2m36s
Reviewed-on: #44
2024-08-13 17:28:18 +00:00
Lee
499c54c8cf Merge pull request 'Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.14.0' (#46) from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Has been cancelled
Reviewed-on: #46
2024-08-13 17:28:09 +00:00
Lee
41f7ca07b0 Merge pull request 'Update dependency com.influxdb:influxdb-spring to v7.2.0' (#45) from renovate/com.influxdb-influxdb-spring-7.x into master
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Has been cancelled
Reviewed-on: #45
2024-08-13 17:26:51 +00:00
5ec61940ac Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.14.0 2024-08-13 09:01:06 +00:00
6665e8a655 Update dependency com.influxdb:influxdb-spring to v7.2.0 2024-08-12 08:01:06 +00:00
07562eb94d Update dependency com.influxdb:influxdb-client-java to v7.2.0 2024-08-12 08:01:04 +00:00
Lee
a78adf67c7 Merge pull request 'Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.13.0' (#43) from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 2m13s
Reviewed-on: #43
2024-07-31 15:59:25 +00:00
fc1f51da75 Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.13.0 2024-07-31 10:00:30 +00:00
c796875d8c increase the timeout for mojang servers
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 3m12s
2024-07-30 22:06:01 +01:00
82fb2a3d23 Merge remote-tracking branch 'origin/master'
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 3m15s
2024-07-30 22:00:30 +01:00
2e326bb7be oopsie 2024-07-30 22:00:07 +01:00
Lee
c5bf941c54 Merge pull request 'Update dependency de.flapdoodle.embed:de.flapdoodle.embed.mongo.spring3x to v4.16.1' (#22) from renovate/de.flapdoodle.embed-de.flapdoodle.embed.mongo.spring3x-4.x into master
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 2m0s
Reviewed-on: #22
2024-07-30 20:55:26 +00:00
Lee
0eb965a26d Merge pull request 'Update dependency org.springframework.boot:spring-boot-starter-parent to v3.3.2' (#42) from renovate/org.springframework.boot-spring-boot-starter-parent-3.x into master
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 2m58s
Reviewed-on: #42
2024-07-30 20:52:18 +00:00
Lee
5481c9302c Merge pull request 'Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.12.1' (#21) from renovate/io.sentry-sentry-spring-boot-starter-jakarta-7.x into master
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Has been cancelled
Reviewed-on: #21
2024-07-30 20:52:12 +00:00
b7834ab389 change how the mojang server status' are fetched
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 2m9s
2024-07-30 21:04:37 +01:00
bb651bd88b Merge remote-tracking branch 'origin/master'
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 3m47s
2024-07-30 20:52:09 +01:00
2b017f9ef7 fix mojang status endpoint 2024-07-30 20:49:36 +01:00
d83391de33 Update dependency org.springframework.boot:spring-boot-starter-parent to v3.3.2 2024-07-26 12:03:27 +00:00
76bef70473 Update dependency io.sentry:sentry-spring-boot-starter-jakarta to v7.12.1 2024-07-25 13:00:32 +00:00
4aa5b0a90d Update dependency de.flapdoodle.embed:de.flapdoodle.embed.mongo.spring3x to v4.16.1 2024-07-18 16:00:22 +00:00
Lee
6a44618ae9 Merge pull request 'Update dependency org.codehaus.plexus:plexus-archiver to v4.10.0' (#20) from renovate/org.codehaus.plexus-plexus-archiver-4.x into master
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 2m12s
Reviewed-on: #20
2024-07-06 18:48:57 +00:00
146d053af8 Update dependency org.codehaus.plexus:plexus-archiver to v4.10.0 2024-07-06 08:00:23 +00:00
Lee
796146c039 Merge pull request 'Update dependency de.flapdoodle.embed:de.flapdoodle.embed.mongo.spring3x to v4.15.0' (#10) from renovate/de.flapdoodle.embed-de.flapdoodle.embed.mongo.spring3x-4.x into master
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 2m44s
Reviewed-on: #10
2024-07-06 07:34:23 +00:00
fa92791b56 Update dependency de.flapdoodle.embed:de.flapdoodle.embed.mongo.spring3x to v4.15.0 2024-07-04 18:00:28 +00:00
12 changed files with 162 additions and 92 deletions

12
pom.xml
View File

@ -17,7 +17,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.1</version>
<version>3.3.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
@ -133,20 +133,20 @@
<dependency>
<groupId>io.sentry</groupId>
<artifactId>sentry-spring-boot-starter-jakarta</artifactId>
<version>7.11.0</version>
<version>7.14.0</version>
</dependency>
<!-- InfluxDB Metrics -->
<dependency>
<groupId>com.influxdb</groupId>
<artifactId>influxdb-spring</artifactId>
<version>7.1.0</version>
<version>7.2.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.influxdb</groupId>
<artifactId>influxdb-client-java</artifactId>
<version>7.1.0</version>
<version>7.2.0</version>
</dependency>
<!-- DNS Lookup -->
@ -176,7 +176,7 @@
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-archiver</artifactId>
<version>4.9.2</version>
<version>4.10.0</version>
</dependency>
<!-- Tests -->
@ -194,7 +194,7 @@
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo.spring3x</artifactId>
<version>4.12.6</version>
<version>4.16.1</version>
<scope>test</scope>
</dependency>
</dependencies>

View File

@ -22,7 +22,7 @@ public final class AppConfig {
private static boolean isRunningTest = true;
static {
try {
Class.forName("org.junit.Test");
Class.forName("org.junit.jupiter.engine.JupiterTestEngine");
} catch (ClassNotFoundException e) {
isRunningTest = false;
}

View 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(10);
/**
* 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, then 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
}
}

View File

@ -12,23 +12,21 @@ import org.springframework.web.bind.annotation.RestController;
import xyz.mcutils.backend.model.cache.CachedEndpointStatus;
import xyz.mcutils.backend.service.MojangService;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@RestController
@Tag(name = "Mojang Controller", description = "The Mojang Controller is used to get information about the Mojang APIs.")
@RequestMapping(value = "/mojang/", produces = MediaType.APPLICATION_JSON_VALUE)
@Tag(name = "Mojang Controller", description = "The Mojang Controller is used to get information about the Mojang APIs.")
public class MojangController {
@Autowired
private MojangService mojangService;
@ResponseBody
@GetMapping(value = "/status")
public ResponseEntity<CachedEndpointStatus> getStatus() {
CachedEndpointStatus status = mojangService.getMojangApiStatus();
public ResponseEntity<?> getStatus() {
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(1, TimeUnit.MINUTES).cachePublic())
.body(status);
.body(Map.of("endpoints", mojangService.getMojangServerStatus()));
}
}

View File

@ -16,8 +16,8 @@ import xyz.mcutils.backend.service.PlayerService;
import java.util.concurrent.TimeUnit;
@RestController
@Tag(name = "Player Controller", description = "The Player Controller is used to get information about a player.")
@RequestMapping(value = "/player/")
@Tag(name = "Player Controller", description = "The Player Controller is used to get information about a player.")
public class PlayerController {
private final PlayerService playerService;

View File

@ -16,8 +16,8 @@ import java.util.Map;
import java.util.concurrent.TimeUnit;
@RestController
@Tag(name = "Server Controller", description = "The Server Controller is used to get information about a server.")
@RequestMapping(value = "/server/")
@Tag(name = "Server Controller", description = "The Server Controller is used to get information about a server.")
public class ServerController {
private final ServerService serverService;

View File

@ -1,6 +1,7 @@
package xyz.mcutils.backend.exception;
import io.micrometer.common.lang.NonNull;
import io.sentry.Sentry;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
@ -39,6 +40,7 @@ public final class ExceptionControllerAdvice {
}
if (status == null) { // Fallback to 500
status = HttpStatus.INTERNAL_SERVER_ERROR;
Sentry.captureException(ex); // Capture the exception with Sentry
}
return new ResponseEntity<>(new ErrorResponse(status, message), status);
}

View File

@ -8,10 +8,13 @@ import lombok.Setter;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
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.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Setter @Getter @EqualsAndHashCode(callSuper = false)
@RedisHash(value = "mojangEndpointStatus", timeToLive = 60L) // 1 minute (in seconds)
@ -26,11 +29,21 @@ public class CachedEndpointStatus extends CachedResponse implements Serializable
/**
* 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());
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);
}
}
}

View File

@ -1,11 +0,0 @@
package xyz.mcutils.backend.repository.redis;
import org.springframework.data.repository.CrudRepository;
import xyz.mcutils.backend.model.cache.CachedEndpointStatus;
/**
* A cache repository for {@link CachedEndpointStatus}'s.
*
* @author Braydon
*/
public interface EndpointStatusRepository extends CrudRepository<CachedEndpointStatus, String> { }

View File

@ -3,6 +3,7 @@ package xyz.mcutils.backend.service;
import com.maxmind.geoip2.DatabaseReader;
import com.maxmind.geoip2.exception.GeoIp2Exception;
import com.maxmind.geoip2.model.CityResponse;
import io.sentry.Sentry;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.codehaus.plexus.archiver.tar.TarGZipUnArchiver;
@ -68,6 +69,7 @@ public class MaxMindService {
return database.city(InetAddress.getByName(ip));
} catch (IOException | GeoIp2Exception e) {
log.error("Failed to lookup the GeoIP information for '{}'", ip, e);
Sentry.captureException(e);
return null;
}
}

View File

@ -9,26 +9,23 @@ import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import net.jodah.expiringmap.ExpirationPolicy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import xyz.mcutils.backend.common.AppConfig;
import xyz.mcutils.backend.common.ExpiringSet;
import xyz.mcutils.backend.common.MojangServer;
import xyz.mcutils.backend.common.WebRequest;
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.MojangUsernameToUuidToken;
import xyz.mcutils.backend.repository.redis.EndpointStatusRepository;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Service @Log4j2(topic = "Mojang Service") @Getter
@Service
@Log4j2(topic = "Mojang Service")
@Getter
public class MojangService {
/**
@ -50,19 +47,9 @@ public class MojangService {
private static final long FETCH_BLOCKED_SERVERS_INTERVAL = TimeUnit.HOURS.toMillis(1L);
/**
* Information about the Mojang API endpoints.
* The interval to fetch the Mojang server status.
*/
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
private EndpointStatusRepository mojangEndpointStatusRepository;
private static final long FETCH_MOJANG_SERVERS_STATUS_INTERVAL = TimeUnit.MINUTES.toMillis(1L);
/**
* A list of banned server hashes provided by Mojang.
@ -82,6 +69,11 @@ public class MojangService {
*/
private final ExpiringSet<String> blockedServersCache = new ExpiringSet<>(ExpirationPolicy.CREATED, 10L, TimeUnit.MINUTES);
/**
* The status of the Mojang API.
*/
private final List<Map<String, Object>> mojangServerStatus = new ArrayList<>();
public MojangService() {
new Timer().scheduleAtFixedRate(new TimerTask() {
@Override
@ -89,6 +81,33 @@ public class MojangService {
fetchBlockedServers();
}
}, 0L, FETCH_BLOCKED_SERVERS_INTERVAL);
new Timer().scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
log.info("Fetching Mojang Server status...");
Map<MojangServer, MojangServer.Status> mojangServers = new HashMap<>();
Arrays.stream(MojangServer.values()).parallel().forEach(server -> {
log.info("Pinging {}...", server.getEndpoint());
MojangServer.Status status = server.getStatus(); // Retrieve the server status
log.info("Retrieved status of {}: {}", server.getEndpoint(), status.name());
mojangServers.put(server, status); // Cache the server status
});
mojangServerStatus.clear();
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());
mojangServerStatus.add(serverStatus);
}
log.info("Fetched Mojang Server status for {} endpoints", mojangServers.size());
}
}, 0L, FETCH_MOJANG_SERVERS_STATUS_INTERVAL);
}
/**
@ -107,6 +126,8 @@ public class MojangService {
}
bannedServerHashes = Collections.synchronizedList(hashes);
log.info("Fetched {} banned server hashes", bannedServerHashes.size());
} catch (IOException e) {
log.error("Failed to fetch blocked servers from Mojang", e);
}
}
@ -177,46 +198,6 @@ public class MojangService {
return blocked;
}
/**
* Gets the status of the Mojang APIs.
*
* @return the status
*/
public CachedEndpointStatus getMojangApiStatus() {
log.info("Getting Mojang API status");
Optional<CachedEndpointStatus> endpointStatus = mojangEndpointStatusRepository.findById(MOJANG_ENDPOINT_STATUS_KEY);
if (endpointStatus.isPresent() && AppConfig.isProduction()) {
log.info("Got cached Mojang API status");
return endpointStatus.get();
}
MOJANG_ENDPOINTS.parallelStream().forEach(endpoint -> {
try {
long start = System.currentTimeMillis();
InetAddress address = InetAddress.getByName(endpoint.getHostname());
if (address.isReachable((int) TimeUnit.SECONDS.toMillis(4))) { // Check if the endpoint is reachable
endpoint.setStatus(EndpointStatus.Status.ONLINE);
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());
CachedEndpointStatus status = new CachedEndpointStatus(
MOJANG_ENDPOINT_STATUS_KEY,
MOJANG_ENDPOINTS
);
mojangEndpointStatusRepository.save(status);
status.getCache().setCached(false);
return status;
}
/**
* Gets the Session Server profile of the
* player with the given UUID.