Improved resilience of registry communication.

This commit is contained in:
Andrew Lalis 2021-06-30 13:06:11 +02:00
parent b0a289e35f
commit b72a5a8b7a
8 changed files with 146 additions and 23 deletions

View File

@ -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.Requests;
import nl.andrewlalis.aos_server_registry.util.Responses; import nl.andrewlalis.aos_server_registry.util.Responses;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -22,7 +21,7 @@ import java.util.Map;
public class ServerInfoServlet extends HttpServlet { public class ServerInfoServlet extends HttpServlet {
@Override @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 page = Requests.getIntParam(req, "page", 0, i -> i >= 0);
int size = Requests.getIntParam(req, "size", 20, i -> i >= 5 && i <= 50); int size = Requests.getIntParam(req, "size", 20, i -> i >= 5 && i <= 50);
String searchQuery = Requests.getStringParam(req, "q", null, s -> !s.isBlank()); String searchQuery = Requests.getStringParam(req, "q", null, s -> !s.isBlank());
@ -44,7 +43,7 @@ public class ServerInfoServlet extends HttpServlet {
} }
@Override @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); var info = Requests.getBody(req, ServerInfoUpdate.class);
try { try {
this.saveNewServer(info); this.saveNewServer(info);
@ -56,15 +55,9 @@ public class ServerInfoServlet extends HttpServlet {
} }
@Override @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); var status = Requests.getBody(req, ServerStatusUpdate.class);
try { this.updateServerStatus(status, resp);
this.updateServerStatus(status);
Responses.ok(resp, Map.of("message", "Server status updated."));
} catch (SQLException e) {
e.printStackTrace();
Responses.internalServerError(resp, "Database error.");
}
} }
private List<ServerInfoResponse> getData(int size, int page, String searchQuery, String order, String orderDir) throws SQLException { private List<ServerInfoResponse> getData(int size, int page, String searchQuery, String order, String orderDir) throws SQLException {
@ -146,7 +139,8 @@ public class ServerInfoServlet extends HttpServlet {
} }
} }
private void updateServerStatus(ServerStatusUpdate status) throws SQLException { private void updateServerStatus(ServerStatusUpdate status, HttpServletResponse resp) throws IOException {
try {
var con = DataManager.getInstance().getConnection(); var con = DataManager.getInstance().getConnection();
PreparedStatement stmt = con.prepareStatement(""" PreparedStatement stmt = con.prepareStatement("""
UPDATE servers SET current_players = ? UPDATE servers SET current_players = ?
@ -157,7 +151,14 @@ public class ServerInfoServlet extends HttpServlet {
stmt.setString(3, status.address()); stmt.setString(3, status.address());
int rowCount = stmt.executeUpdate(); int rowCount = stmt.executeUpdate();
stmt.close(); stmt.close();
if (rowCount != 1) throw new SQLException("Could not update server status."); if (rowCount != 1) {
System.out.println("Updated server status for " + status.name() + " @ " + status.address()); Responses.notFound(resp);
} else {
Responses.ok(resp);
}
} catch (SQLException e) {
e.printStackTrace();
Responses.internalServerError(resp, "Database error.");
}
} }
} }

View File

@ -11,6 +11,10 @@ import static nl.andrewlalis.aos_server_registry.ServerRegistry.mapper;
* JSON responses. * JSON responses.
*/ */
public class 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 { public static void ok(HttpServletResponse resp, Object body) throws IOException {
resp.setStatus(HttpServletResponse.SC_OK); resp.setStatus(HttpServletResponse.SC_OK);
resp.setContentType("application/json"); resp.setContentType("application/json");

View File

@ -2,6 +2,7 @@ module aos_server {
requires java.logging; requires java.logging;
requires aos_core; requires aos_core;
requires java.desktop; requires java.desktop;
requires java.net.http;
requires com.fasterxml.jackson.databind; requires com.fasterxml.jackson.databind;
requires com.fasterxml.jackson.dataformat.yaml; requires com.fasterxml.jackson.dataformat.yaml;

View File

@ -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<String, Object> 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<String, Object> 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();
}
}
}

View File

@ -25,7 +25,9 @@ import java.net.Socket;
import java.net.SocketException; import java.net.SocketException;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
public class Server { public class Server {
private final ServerSettings settings; private final ServerSettings settings;
@ -37,6 +39,7 @@ public class Server {
private final WorldUpdater worldUpdater; private final WorldUpdater worldUpdater;
private final ServerCli cli; private final ServerCli cli;
private final ChatManager chatManager; private final ChatManager chatManager;
private RegistryManager registryManager;
private volatile boolean running; private volatile boolean running;
@ -50,6 +53,9 @@ public class Server {
this.initWorld(); this.initWorld();
this.worldUpdater = new WorldUpdater(this, this.world); this.worldUpdater = new WorldUpdater(this, this.world);
this.chatManager = new ChatManager(this); this.chatManager = new ChatManager(this);
if (settings.getRegistrySettings().isDiscoverable()) {
this.registryManager = new RegistryManager(this);
}
} }
public ServerSettings getSettings() { public ServerSettings getSettings() {
@ -230,7 +236,7 @@ public class Server {
handler.send(new Message(Type.SERVER_SHUTDOWN)); handler.send(new Message(Type.SERVER_SHUTDOWN));
handler.shutdown(); handler.shutdown();
} }
} catch (IOException e) { } catch (Exception e) {
System.err.println("Could not close server socket on shutdown: " + e.getMessage()); 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."); System.out.println("Stopped CLI interface.");
this.dataTransceiver.shutdown(); this.dataTransceiver.shutdown();
System.out.println("Stopped data transceiver."); System.out.println("Stopped data transceiver.");
if (this.registryManager != null) {
this.registryManager.shutdown();
System.out.println("Stopped registry communications.");
}
} }

View File

@ -3,6 +3,7 @@ package nl.andrewlalis.aos_server.settings;
public class RegistrySettings { public class RegistrySettings {
private boolean discoverable; private boolean discoverable;
private String registryUri; private String registryUri;
private long updateInterval;
private String name; private String name;
private String address; private String address;
private String description; private String description;
@ -16,6 +17,10 @@ public class RegistrySettings {
return registryUri; return registryUri;
} }
public long getUpdateInterval() {
return updateInterval;
}
public String getName() { public String getName() {
return name; return name;
} }

View File

@ -24,6 +24,10 @@ public class ServerSettings {
return ticksPerSecond; return ticksPerSecond;
} }
public RegistrySettings getRegistrySettings() {
return registrySettings;
}
public PlayerSettings getPlayerSettings() { public PlayerSettings getPlayerSettings() {
return playerSettings; return playerSettings;
} }

View File

@ -3,9 +3,10 @@ max-players: 32
ticks-per-second: 120 ticks-per-second: 120
# Information for the public server registry. # Information for the public server registry.
registry-data: registry-settings:
discoverable: true discoverable: true
registry-uri: "http://localhost:8567" registry-uri: "http://localhost:8567"
update-interval: 10
name: "Testing Server" name: "Testing Server"
address: "localhost:8035" address: "localhost:8035"
description: "A simple testing server for development." description: "A simple testing server for development."