From 035b86920ade2c9916684484ec9d231717e018dc Mon Sep 17 00:00:00 2001 From: Rainnny7 Date: Sat, 6 Apr 2024 16:11:03 -0400 Subject: [PATCH] Add req/res transaction logging --- .../java/me/braydon/mc/common/IPUtils.java | 44 ++++++++++ .../mc/controller/PlayerController.java | 2 +- .../me/braydon/mc/log/TransactionLogger.java | 84 +++++++++++++++++++ src/main/java/me/braydon/mc/model/Skin.java | 7 ++ .../me/braydon/mc/service/MojangService.java | 19 +++-- 5 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 src/main/java/me/braydon/mc/common/IPUtils.java create mode 100644 src/main/java/me/braydon/mc/log/TransactionLogger.java create mode 100644 src/main/java/me/braydon/mc/model/Skin.java diff --git a/src/main/java/me/braydon/mc/common/IPUtils.java b/src/main/java/me/braydon/mc/common/IPUtils.java new file mode 100644 index 0000000..a9037c4 --- /dev/null +++ b/src/main/java/me/braydon/mc/common/IPUtils.java @@ -0,0 +1,44 @@ +package me.braydon.mc.common; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.NonNull; +import lombok.experimental.UtilityClass; + +/** + * @author Braydon + */ +@UtilityClass +public final class IPUtils { + private static final String[] IP_HEADERS = new String[] { + "CF-Connecting-IP", + "X-Forwarded-For" + }; + + /** + * Get the real IP from the given request. + * + * @param request the request + * @return the real IP + */ + @NonNull + public static String getRealIp(@NonNull HttpServletRequest request) { + String ip = request.getRemoteAddr(); + for (String headerName : IP_HEADERS) { + String header = request.getHeader(headerName); + if (header == null) { + continue; + } + if (!header.contains(",")) { // Handle single IP + ip = header; + break; + } + // Handle multiple IPs + String[] ips = header.split(","); + for (String ipHeader : ips) { + ip = ipHeader; + break; + } + } + return ip; + } +} diff --git a/src/main/java/me/braydon/mc/controller/PlayerController.java b/src/main/java/me/braydon/mc/controller/PlayerController.java index 2ab7cf4..c332b10 100644 --- a/src/main/java/me/braydon/mc/controller/PlayerController.java +++ b/src/main/java/me/braydon/mc/controller/PlayerController.java @@ -42,6 +42,6 @@ public final class PlayerController { @GetMapping("/{query}") @ResponseBody public ResponseEntity getPlayer(@PathVariable @NonNull String query) throws BadRequestException, ResourceNotFoundException { - return ResponseEntity.ofNullable(mojangService.getPlayer(query)); + return ResponseEntity.ofNullable(mojangService.getPlayer(query, false)); } } \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/log/TransactionLogger.java b/src/main/java/me/braydon/mc/log/TransactionLogger.java new file mode 100644 index 0000000..7bd0eb2 --- /dev/null +++ b/src/main/java/me/braydon/mc/log/TransactionLogger.java @@ -0,0 +1,84 @@ +package me.braydon.mc.log; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import me.braydon.mc.common.IPUtils; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.http.server.ServletServerHttpResponse; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Responsible for logging request and + * response transactions to the terminal. + * + * @author Braydon + */ +@ControllerAdvice +@Slf4j(topic = "Req/Res Transaction") +public class TransactionLogger implements ResponseBodyAdvice { + @Override + public Object beforeBodyWrite(Object body, @NonNull MethodParameter returnType, @NonNull MediaType selectedContentType, + @NonNull Class> selectedConverterType, @NonNull ServerHttpRequest rawRequest, + @NonNull ServerHttpResponse rawResponse) { + HttpServletRequest request = ((ServletServerHttpRequest) rawRequest).getServletRequest(); + HttpServletResponse response = ((ServletServerHttpResponse) rawResponse).getServletResponse(); + + // Get the request ip ip + String ip = IPUtils.getRealIp(request); + + // Getting params + Map params = new HashMap<>(); + for (Entry entry : request.getParameterMap().entrySet()) { + params.put(entry.getKey(), Arrays.toString(entry.getValue())); + } + + // Getting headers + Map headers = new HashMap<>(); + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + headers.put(headerName, request.getHeader(headerName)); + } + + // Log the request + log.info(String.format("[Req] %s | %s | '%s', params=%s, headers=%s", + request.getMethod(), + ip, + request.getRequestURI(), + params, + headers + )); + + // Getting response headers + headers = new HashMap<>(); + for (String headerName : response.getHeaderNames()) { + headers.put(headerName, response.getHeader(headerName)); + } + + // Log the response + log.info(String.format("[Res] %s, headers=%s", + response.getStatus(), + headers + )); + return body; + } + + @Override + public boolean supports(@NonNull MethodParameter returnType, @NonNull Class> converterType) { + return true; + } +} \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/model/Skin.java b/src/main/java/me/braydon/mc/model/Skin.java new file mode 100644 index 0000000..d474320 --- /dev/null +++ b/src/main/java/me/braydon/mc/model/Skin.java @@ -0,0 +1,7 @@ +package me.braydon.mc.model; + +/** + * @author Braydon + */ +public final class Skin { +} \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/service/MojangService.java b/src/main/java/me/braydon/mc/service/MojangService.java index 2bb5525..5e16fe0 100644 --- a/src/main/java/me/braydon/mc/service/MojangService.java +++ b/src/main/java/me/braydon/mc/service/MojangService.java @@ -61,12 +61,13 @@ public final class MojangService { *

* * @param query the query to search for the player by + * @param bypassCache should the cache be bypassed? * @return the player * @throws BadRequestException if the UUID is malformed * @throws ResourceNotFoundException if the player is not found */ @NonNull - public CachedPlayer getPlayer(@NonNull String query) throws BadRequestException, ResourceNotFoundException { + public CachedPlayer getPlayer(@NonNull String query, boolean bypassCache) throws BadRequestException, ResourceNotFoundException { log.info("Requesting player with query: {}", query); UUID uuid; // The player UUID to lookup @@ -84,10 +85,12 @@ public final class MojangService { } // Check the cache for the player - Optional cached = playerCache.findById(uuid); - if (cached.isPresent()) { // Respond with the cache if present - log.info("Found player in cache: {}", uuid); - return cached.get(); + if (!bypassCache) { + Optional cached = playerCache.findById(uuid); + if (cached.isPresent()) { // Respond with the cache if present + log.info("Found player in cache: {}", uuid); + return cached.get(); + } } // Send a request to Mojang requesting @@ -105,8 +108,10 @@ public final class MojangService { profileActions.length == 0 ? null : profileActions, System.currentTimeMillis() ); - playerCache.save(player); - log.info("Cached player: {}", uuid); + if (!bypassCache) { // Store in the cache + playerCache.save(player); + log.info("Cached player: {}", uuid); + } player.setCached(-1L); // Set to -1 to indicate it's not cached in the response return player;