Add Redis caching for Minecraft servers

This commit is contained in:
Braydon 2024-04-06 22:54:31 -04:00
parent f99ffbf1e6
commit 3183c56d78
6 changed files with 80 additions and 8 deletions

@ -3,6 +3,7 @@ package me.braydon.mc.controller;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;
import me.braydon.mc.model.MinecraftServer;
import me.braydon.mc.model.cache.CachedMinecraftServer;
import me.braydon.mc.service.MojangService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
@ -38,8 +39,8 @@ public final class ServerController {
*/
@GetMapping("/{platform}/{hostname}")
@ResponseBody
public ResponseEntity<MinecraftServer> getServer(@PathVariable @NonNull String platform,
@PathVariable @NonNull String hostname
public ResponseEntity<CachedMinecraftServer> getServer(@PathVariable @NonNull String platform,
@PathVariable @NonNull String hostname
) {
return ResponseEntity.ofNullable(mojangService.getMinecraftServer(platform, hostname));
}

@ -62,9 +62,9 @@ public class MinecraftServer {
private final int max;
/**
* A sample of players on this server.
* A sample of players on this server, null or empty if no sample.
*/
@NonNull private final Sample[] sample;
private final Sample[] sample;
/**
* A sample player.

@ -0,0 +1,31 @@
package me.braydon.mc.model.cache;
import lombok.*;
import me.braydon.mc.model.MinecraftServer;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import java.io.Serializable;
/**
* @author Braydon
*/
@AllArgsConstructor @Setter @Getter @ToString
@RedisHash(value = "server", timeToLive = 60L) // 1 minute (in seconds)
public final class CachedMinecraftServer implements Serializable {
/**
* The hostname of the cached server.
*/
@Id @NonNull private transient final String hostname;
/**
* The cached server.
*/
@NonNull private final MinecraftServer value;
/**
* The unix timestamp of when this
* server was cached, -1 if not cached.
*/
private long cached;
}

@ -1,6 +1,8 @@
package me.braydon.mc.model.response;
import lombok.*;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
import org.springframework.http.HttpStatus;
import java.util.Date;

@ -0,0 +1,11 @@
package me.braydon.mc.repository;
import me.braydon.mc.model.cache.CachedMinecraftServer;
import org.springframework.data.repository.CrudRepository;
/**
* A cache repository for {@link CachedMinecraftServer}'s.
*
* @author Braydon
*/
public interface MinecraftServerCacheRepository extends CrudRepository<CachedMinecraftServer, String> { }

@ -12,10 +12,12 @@ import me.braydon.mc.exception.impl.BadRequestException;
import me.braydon.mc.exception.impl.InvalidMinecraftServerPlatform;
import me.braydon.mc.exception.impl.ResourceNotFoundException;
import me.braydon.mc.model.*;
import me.braydon.mc.model.cache.CachedMinecraftServer;
import me.braydon.mc.model.cache.CachedPlayer;
import me.braydon.mc.model.cache.CachedPlayerName;
import me.braydon.mc.model.token.MojangProfileToken;
import me.braydon.mc.model.token.MojangUsernameToUUIDToken;
import me.braydon.mc.repository.MinecraftServerCacheRepository;
import me.braydon.mc.repository.PlayerCacheRepository;
import me.braydon.mc.repository.PlayerNameCacheRepository;
import org.springframework.beans.factory.annotation.Autowired;
@ -49,10 +51,17 @@ public final class MojangService {
*/
@NonNull private final PlayerCacheRepository playerCache;
/**
* The cache repository for {@link MinecraftServer}'s.
*/
@NonNull private final MinecraftServerCacheRepository minecraftServerCache;
@Autowired
public MojangService(@NonNull PlayerNameCacheRepository playerNameCache, @NonNull PlayerCacheRepository playerCache) {
public MojangService(@NonNull PlayerNameCacheRepository playerNameCache, @NonNull PlayerCacheRepository playerCache,
@NonNull MinecraftServerCacheRepository minecraftServerCache) {
this.playerNameCache = playerNameCache;
this.playerCache = playerCache;
this.minecraftServerCache = minecraftServerCache;
}
/**
@ -143,19 +152,37 @@ public final class MojangService {
* @throws ResourceNotFoundException if the server isn't found
*/
@NonNull
public MinecraftServer getMinecraftServer(@NonNull String platformName, @NonNull String hostname)
public CachedMinecraftServer getMinecraftServer(@NonNull String platformName, @NonNull String hostname)
throws BadRequestException, InvalidMinecraftServerPlatform, ResourceNotFoundException {
MinecraftServer.Platform platform = EnumUtils.getEnumConstant(MinecraftServer.Platform.class, platformName.toUpperCase());
if (platform == null) { // Invalid platform
throw new InvalidMinecraftServerPlatform();
}
String lookupHostname = hostname; // The hostname used to lookup the server
// Check the cache for the server
Optional<CachedMinecraftServer> cached = minecraftServerCache.findById(hostname);
if (cached.isPresent()) { // Respond with the cache if present
log.info("Found server in cache: {}", hostname);
return cached.get();
}
InetSocketAddress address = DNSUtils.resolveSRV(hostname); // Resolve the SRV record
int port = platform.getDefaultPort(); // Port to ping
if (address != null) { // SRV was resolved, use the hostname and port
hostname = address.getHostName();
port = address.getPort();
}
return platform.getPinger().ping(hostname, port); // Ping the server and return with the response
// Build our server model, cache it, and then return it
CachedMinecraftServer minecraftServer = new CachedMinecraftServer(
lookupHostname,
platform.getPinger().ping(hostname, port),
System.currentTimeMillis()
);
minecraftServerCache.save(minecraftServer);
log.info("Cached server: {}", hostname);
minecraftServer.setCached(-1L); // Set to -1 to indicate it's not cached in the response
return minecraftServer;
}
/**