diff --git a/pom.xml b/pom.xml index 01110f9..bda4788 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,12 @@ spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-websocket + + org.springframework.boot diff --git a/src/main/java/xyz/mcutils/backend/common/WebRequest.java b/src/main/java/xyz/mcutils/backend/common/WebRequest.java index 9fc3680..c87b9d5 100644 --- a/src/main/java/xyz/mcutils/backend/common/WebRequest.java +++ b/src/main/java/xyz/mcutils/backend/common/WebRequest.java @@ -1,6 +1,5 @@ package xyz.mcutils.backend.common; -import com.google.gson.JsonObject; import lombok.experimental.UtilityClass; import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatusCode; diff --git a/src/main/java/xyz/mcutils/backend/config/WebSocketConfig.java b/src/main/java/xyz/mcutils/backend/config/WebSocketConfig.java new file mode 100644 index 0000000..ee4c3c1 --- /dev/null +++ b/src/main/java/xyz/mcutils/backend/config/WebSocketConfig.java @@ -0,0 +1,22 @@ +package xyz.mcutils.backend.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; +import xyz.mcutils.backend.service.MetricService; +import xyz.mcutils.backend.websocket.MetricsWebSocketHandler; + +@Configuration +@EnableWebSocket +public class WebSocketConfig implements WebSocketConfigurer { + + @Autowired + private MetricService metricService; + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + registry.addHandler(new MetricsWebSocketHandler(metricService), "/websocket/metrics").setAllowedOrigins("*"); + } +} diff --git a/src/main/java/xyz/mcutils/backend/model/cache/CachedEndpointStatus.java b/src/main/java/xyz/mcutils/backend/model/cache/CachedEndpointStatus.java index a48270c..d526ec6 100644 --- a/src/main/java/xyz/mcutils/backend/model/cache/CachedEndpointStatus.java +++ b/src/main/java/xyz/mcutils/backend/model/cache/CachedEndpointStatus.java @@ -2,14 +2,16 @@ package xyz.mcutils.backend.model.cache; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonUnwrapped; -import lombok.*; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import lombok.ToString; 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 java.io.Serializable; -import java.util.Map; @Setter @Getter @ToString @RedisHash(value = "mojangEndpointStatus", timeToLive = 60L) // 1 minute (in seconds) diff --git a/src/main/java/xyz/mcutils/backend/model/metric/WebsocketMetrics.java b/src/main/java/xyz/mcutils/backend/model/metric/WebsocketMetrics.java new file mode 100644 index 0000000..f1e46ff --- /dev/null +++ b/src/main/java/xyz/mcutils/backend/model/metric/WebsocketMetrics.java @@ -0,0 +1,15 @@ +package xyz.mcutils.backend.model.metric; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Map; + +@AllArgsConstructor +@Getter +public class WebsocketMetrics { + /** + * The metrics to send to the client. + */ + private final Map metrics; +} diff --git a/src/main/java/xyz/mcutils/backend/model/mojang/EndpointStatus.java b/src/main/java/xyz/mcutils/backend/model/mojang/EndpointStatus.java index 8edb7fc..bf46307 100644 --- a/src/main/java/xyz/mcutils/backend/model/mojang/EndpointStatus.java +++ b/src/main/java/xyz/mcutils/backend/model/mojang/EndpointStatus.java @@ -1,12 +1,7 @@ package xyz.mcutils.backend.model.mojang; -import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.NonNull; -import org.springframework.data.annotation.Id; -import xyz.mcutils.backend.common.CachedResponse; -import xyz.mcutils.backend.model.cache.CachedEndpointStatus; import java.util.Map; diff --git a/src/main/java/xyz/mcutils/backend/websocket/MetricsWebSocketHandler.java b/src/main/java/xyz/mcutils/backend/websocket/MetricsWebSocketHandler.java new file mode 100644 index 0000000..7c3010c --- /dev/null +++ b/src/main/java/xyz/mcutils/backend/websocket/MetricsWebSocketHandler.java @@ -0,0 +1,54 @@ +package xyz.mcutils.backend.websocket; + +import lombok.extern.log4j.Log4j2; +import org.jetbrains.annotations.NotNull; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; +import xyz.mcutils.backend.Main; +import xyz.mcutils.backend.common.Timer; +import xyz.mcutils.backend.model.metric.WebsocketMetrics; +import xyz.mcutils.backend.service.MetricService; +import xyz.mcutils.backend.service.metric.metrics.TotalRequestsMetric; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@Log4j2(topic = "WebSocket/Metrics") +public class MetricsWebSocketHandler extends TextWebSocketHandler { + private final long interval = TimeUnit.SECONDS.toMillis(5); + public final List sessions = new ArrayList<>(); + + public MetricsWebSocketHandler(MetricService metricService) { + Timer.scheduleRepeating(() -> { + try { + WebsocketMetrics metrics = new WebsocketMetrics(Map.of( + "totalRequests", metricService.getMetric(TotalRequestsMetric.class).getValue()) + ); + + for (WebSocketSession session : sessions) { + session.sendMessage(new TextMessage(Main.GSON.toJson(metrics))); + } + } catch (Exception e) { + log.error("An error occurred while sending metrics to the client", e); + } + }, interval, interval); + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + log.info("WebSocket connection established with session id: {}", session.getId()); + + sessions.add(session); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, @NotNull CloseStatus status) { + log.info("WebSocket connection closed with session id: {}", session.getId()); + + sessions.remove(session); + } +} \ No newline at end of file