From b72a5a8b7a0fc3e1d8eb38f05b796c3be0c28296 Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Wed, 30 Jun 2021 13:06:11 +0200 Subject: [PATCH] Improved resilience of registry communication. --- .../servlet/ServerInfoServlet.java | 43 ++++---- .../aos_server_registry/util/Responses.java | 4 + server/src/main/java/module-info.java | 1 + .../aos_server/RegistryManager.java | 97 +++++++++++++++++++ .../nl/andrewlalis/aos_server/Server.java | 12 ++- .../aos_server/settings/RegistrySettings.java | 5 + .../aos_server/settings/ServerSettings.java | 4 + .../src/main/resources/default_settings.yaml | 3 +- 8 files changed, 146 insertions(+), 23 deletions(-) create mode 100644 server/src/main/java/nl/andrewlalis/aos_server/RegistryManager.java diff --git a/server-registry/src/main/java/nl/andrewlalis/aos_server_registry/servlet/ServerInfoServlet.java b/server-registry/src/main/java/nl/andrewlalis/aos_server_registry/servlet/ServerInfoServlet.java index 92caba3..c1af99c 100644 --- a/server-registry/src/main/java/nl/andrewlalis/aos_server_registry/servlet/ServerInfoServlet.java +++ b/server-registry/src/main/java/nl/andrewlalis/aos_server_registry/servlet/ServerInfoServlet.java @@ -7,7 +7,6 @@ import nl.andrewlalis.aos_server_registry.servlet.dto.ServerStatusUpdate; import nl.andrewlalis.aos_server_registry.util.Requests; import nl.andrewlalis.aos_server_registry.util.Responses; -import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -22,7 +21,7 @@ import java.util.Map; public class ServerInfoServlet extends HttpServlet { @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { int page = Requests.getIntParam(req, "page", 0, i -> i >= 0); int size = Requests.getIntParam(req, "size", 20, i -> i >= 5 && i <= 50); String searchQuery = Requests.getStringParam(req, "q", null, s -> !s.isBlank()); @@ -44,7 +43,7 @@ public class ServerInfoServlet extends HttpServlet { } @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { var info = Requests.getBody(req, ServerInfoUpdate.class); try { this.saveNewServer(info); @@ -56,15 +55,9 @@ public class ServerInfoServlet extends HttpServlet { } @Override - protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException { var status = Requests.getBody(req, ServerStatusUpdate.class); - try { - this.updateServerStatus(status); - Responses.ok(resp, Map.of("message", "Server status updated.")); - } catch (SQLException e) { - e.printStackTrace(); - Responses.internalServerError(resp, "Database error."); - } + this.updateServerStatus(status, resp); } private List getData(int size, int page, String searchQuery, String order, String orderDir) throws SQLException { @@ -146,18 +139,26 @@ public class ServerInfoServlet extends HttpServlet { } } - private void updateServerStatus(ServerStatusUpdate status) throws SQLException { - var con = DataManager.getInstance().getConnection(); - PreparedStatement stmt = con.prepareStatement(""" + private void updateServerStatus(ServerStatusUpdate status, HttpServletResponse resp) throws IOException { + try { + var con = DataManager.getInstance().getConnection(); + PreparedStatement stmt = con.prepareStatement(""" UPDATE servers SET current_players = ? WHERE name = ? AND address = ? """); - stmt.setInt(1, status.currentPlayers()); - stmt.setString(2, status.name()); - stmt.setString(3, status.address()); - int rowCount = stmt.executeUpdate(); - stmt.close(); - if (rowCount != 1) throw new SQLException("Could not update server status."); - System.out.println("Updated server status for " + status.name() + " @ " + status.address()); + stmt.setInt(1, status.currentPlayers()); + stmt.setString(2, status.name()); + stmt.setString(3, status.address()); + int rowCount = stmt.executeUpdate(); + stmt.close(); + if (rowCount != 1) { + Responses.notFound(resp); + } else { + Responses.ok(resp); + } + } catch (SQLException e) { + e.printStackTrace(); + Responses.internalServerError(resp, "Database error."); + } } } diff --git a/server-registry/src/main/java/nl/andrewlalis/aos_server_registry/util/Responses.java b/server-registry/src/main/java/nl/andrewlalis/aos_server_registry/util/Responses.java index cda999a..98103b3 100644 --- a/server-registry/src/main/java/nl/andrewlalis/aos_server_registry/util/Responses.java +++ b/server-registry/src/main/java/nl/andrewlalis/aos_server_registry/util/Responses.java @@ -11,6 +11,10 @@ import static nl.andrewlalis.aos_server_registry.ServerRegistry.mapper; * JSON responses. */ public class Responses { + public static void ok(HttpServletResponse resp) { + resp.setStatus(HttpServletResponse.SC_OK); + } + public static void ok(HttpServletResponse resp, Object body) throws IOException { resp.setStatus(HttpServletResponse.SC_OK); resp.setContentType("application/json"); diff --git a/server/src/main/java/module-info.java b/server/src/main/java/module-info.java index 6908209..e0f17bc 100644 --- a/server/src/main/java/module-info.java +++ b/server/src/main/java/module-info.java @@ -2,6 +2,7 @@ module aos_server { requires java.logging; requires aos_core; requires java.desktop; + requires java.net.http; requires com.fasterxml.jackson.databind; requires com.fasterxml.jackson.dataformat.yaml; diff --git a/server/src/main/java/nl/andrewlalis/aos_server/RegistryManager.java b/server/src/main/java/nl/andrewlalis/aos_server/RegistryManager.java new file mode 100644 index 0000000..68669fa --- /dev/null +++ b/server/src/main/java/nl/andrewlalis/aos_server/RegistryManager.java @@ -0,0 +1,97 @@ +package nl.andrewlalis.aos_server; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * The registry manager is responsible for keeping the server registry up to + * date with this server's information, by sending periodic update HTTP messages. + */ +public class RegistryManager { + private final ScheduledExecutorService executorService; + private final Server server; + + private final ObjectMapper mapper; + private final HttpClient httpClient; + + public RegistryManager(Server server) { + this.server = server; + this.mapper = new ObjectMapper(); + this.executorService = Executors.newScheduledThreadPool(3); + this.httpClient = HttpClient.newBuilder() + .executor(this.executorService) + .connectTimeout(Duration.ofSeconds(3)) + .build(); + this.executorService.submit(this::sendInfo); + this.executorService.scheduleAtFixedRate( + this::sendUpdate, + server.getSettings().getRegistrySettings().getUpdateInterval(), + server.getSettings().getRegistrySettings().getUpdateInterval(), + TimeUnit.SECONDS + ); + } + + public void sendInfo() { + try { + Map data = new HashMap<>(); + data.put("name", this.server.getSettings().getRegistrySettings().getName()); + data.put("address", this.server.getSettings().getRegistrySettings().getAddress()); + data.put("description", this.server.getSettings().getRegistrySettings().getDescription()); + data.put("location", this.server.getSettings().getRegistrySettings().getLocation()); + data.put("maxPlayers", this.server.getSettings().getMaxPlayers()); + data.put("currentPlayers", 0); + HttpRequest request = HttpRequest.newBuilder() + .uri(new URI(this.server.getSettings().getRegistrySettings().getRegistryUri() + "/serverInfo")) + .POST(HttpRequest.BodyPublishers.ofByteArray(this.mapper.writeValueAsBytes(data))) + .header("Content-Type", "application/json") + .build(); + this.httpClient.sendAsync(request, HttpResponse.BodyHandlers.discarding()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void sendUpdate() { + try { + Map data = new HashMap<>(); + data.put("name", this.server.getSettings().getRegistrySettings().getName()); + data.put("address", this.server.getSettings().getRegistrySettings().getAddress()); + data.put("currentPlayers", server.getPlayerCount()); + HttpRequest request = HttpRequest.newBuilder() + .uri(new URI(this.server.getSettings().getRegistrySettings().getRegistryUri() + "/serverInfo")) + .PUT(HttpRequest.BodyPublishers.ofByteArray(this.mapper.writeValueAsBytes(data))) + .header("Content-Type", "application/json") + .build(); + this.httpClient.sendAsync(request, responseInfo -> { + if (responseInfo.statusCode() == 404) { + System.out.println("Received 404 when sending registry update. Re-sending registry info..."); + this.sendInfo(); + } + return null; + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void shutdown() { + this.executorService.shutdown(); + try { + while (!this.executorService.awaitTermination(3, TimeUnit.SECONDS)) { + System.out.println("Waiting for scheduler to terminate."); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/server/src/main/java/nl/andrewlalis/aos_server/Server.java b/server/src/main/java/nl/andrewlalis/aos_server/Server.java index 17d66ec..8bff6f8 100644 --- a/server/src/main/java/nl/andrewlalis/aos_server/Server.java +++ b/server/src/main/java/nl/andrewlalis/aos_server/Server.java @@ -25,7 +25,9 @@ import java.net.Socket; import java.net.SocketException; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executors; import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; public class Server { private final ServerSettings settings; @@ -37,6 +39,7 @@ public class Server { private final WorldUpdater worldUpdater; private final ServerCli cli; private final ChatManager chatManager; + private RegistryManager registryManager; private volatile boolean running; @@ -50,6 +53,9 @@ public class Server { this.initWorld(); this.worldUpdater = new WorldUpdater(this, this.world); this.chatManager = new ChatManager(this); + if (settings.getRegistrySettings().isDiscoverable()) { + this.registryManager = new RegistryManager(this); + } } public ServerSettings getSettings() { @@ -230,7 +236,7 @@ public class Server { handler.send(new Message(Type.SERVER_SHUTDOWN)); handler.shutdown(); } - } catch (IOException e) { + } catch (Exception e) { System.err.println("Could not close server socket on shutdown: " + e.getMessage()); } } @@ -252,6 +258,10 @@ public class Server { System.out.println("Stopped CLI interface."); this.dataTransceiver.shutdown(); System.out.println("Stopped data transceiver."); + if (this.registryManager != null) { + this.registryManager.shutdown(); + System.out.println("Stopped registry communications."); + } } diff --git a/server/src/main/java/nl/andrewlalis/aos_server/settings/RegistrySettings.java b/server/src/main/java/nl/andrewlalis/aos_server/settings/RegistrySettings.java index 2a39965..02016bf 100644 --- a/server/src/main/java/nl/andrewlalis/aos_server/settings/RegistrySettings.java +++ b/server/src/main/java/nl/andrewlalis/aos_server/settings/RegistrySettings.java @@ -3,6 +3,7 @@ package nl.andrewlalis.aos_server.settings; public class RegistrySettings { private boolean discoverable; private String registryUri; + private long updateInterval; private String name; private String address; private String description; @@ -16,6 +17,10 @@ public class RegistrySettings { return registryUri; } + public long getUpdateInterval() { + return updateInterval; + } + public String getName() { return name; } diff --git a/server/src/main/java/nl/andrewlalis/aos_server/settings/ServerSettings.java b/server/src/main/java/nl/andrewlalis/aos_server/settings/ServerSettings.java index 9de8795..44c3698 100644 --- a/server/src/main/java/nl/andrewlalis/aos_server/settings/ServerSettings.java +++ b/server/src/main/java/nl/andrewlalis/aos_server/settings/ServerSettings.java @@ -24,6 +24,10 @@ public class ServerSettings { return ticksPerSecond; } + public RegistrySettings getRegistrySettings() { + return registrySettings; + } + public PlayerSettings getPlayerSettings() { return playerSettings; } diff --git a/server/src/main/resources/default_settings.yaml b/server/src/main/resources/default_settings.yaml index 173c7e1..29f6b5d 100644 --- a/server/src/main/resources/default_settings.yaml +++ b/server/src/main/resources/default_settings.yaml @@ -3,9 +3,10 @@ max-players: 32 ticks-per-second: 120 # Information for the public server registry. -registry-data: +registry-settings: discoverable: true registry-uri: "http://localhost:8567" + update-interval: 10 name: "Testing Server" address: "localhost:8035" description: "A simple testing server for development."