Improved registry with more customization.
This commit is contained in:
parent
fa8c553041
commit
4481f1c028
|
@ -10,22 +10,44 @@ import nl.andrewlalis.aos_server_registry.data.ServerDataPruner;
|
|||
import nl.andrewlalis.aos_server_registry.servlet.ServerInfoServlet;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ServerRegistry {
|
||||
public static final int PORT = 8567;
|
||||
public static final String SETTINGS_FILE = "settings.properties";
|
||||
public static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
public static void main(String[] args) throws ServletException {
|
||||
startServer();
|
||||
public static void main(String[] args) throws ServletException, IOException {
|
||||
Properties props = new Properties();
|
||||
props.load(ServerRegistry.class.getResourceAsStream("/nl/andrewlalis/aos_server_registry/defaults.properties"));
|
||||
Path settingsPath = Path.of(SETTINGS_FILE);
|
||||
if (Files.exists(settingsPath)) {
|
||||
props.load(Files.newBufferedReader(settingsPath));
|
||||
} else {
|
||||
System.out.println("Using built-in default settings. Create a settings.properties file to configure.");
|
||||
}
|
||||
|
||||
startServer(Integer.parseInt(props.getProperty("port")));
|
||||
|
||||
// Every few minutes, prune all stale servers from the registry.
|
||||
long pruneDelaySeconds = Long.parseLong(props.getProperty("prune-delay"));
|
||||
long pruneThresholdMinutes = Long.parseLong(props.getProperty("prune-threshold-minutes"));
|
||||
System.out.printf("Will prune servers inactive for more than %d minutes, checking every %d seconds.\n", pruneThresholdMinutes, pruneDelaySeconds);
|
||||
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(3);
|
||||
scheduler.scheduleAtFixedRate(new ServerDataPruner(), 1, 1, TimeUnit.MINUTES);
|
||||
scheduler.scheduleAtFixedRate(new ServerDataPruner(pruneThresholdMinutes), pruneDelaySeconds, pruneDelaySeconds, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private static void startServer() throws ServletException {
|
||||
/**
|
||||
* Starts the Undertow HTTP servlet container.
|
||||
* @param port The port to bind to.
|
||||
* @throws ServletException If the server could not be started.
|
||||
*/
|
||||
private static void startServer(int port) throws ServletException {
|
||||
DeploymentInfo servletBuilder = Servlets.deployment()
|
||||
.setClassLoader(ServerRegistry.class.getClassLoader())
|
||||
.setContextPath("/")
|
||||
|
@ -38,7 +60,7 @@ public class ServerRegistry {
|
|||
manager.deploy();
|
||||
HttpHandler servletHandler = manager.start();
|
||||
Undertow server = Undertow.builder()
|
||||
.addHttpListener(PORT, "localhost")
|
||||
.addHttpListener(port, "localhost")
|
||||
.setHandler(servletHandler)
|
||||
.build();
|
||||
server.start();
|
||||
|
|
|
@ -9,9 +9,14 @@ import java.util.logging.Logger;
|
|||
* registry which have not been updated in a while.
|
||||
*/
|
||||
public class ServerDataPruner implements Runnable {
|
||||
public static final int INTERVAL_MINUTES = 5;
|
||||
private static final Logger log = Logger.getLogger(ServerDataPruner.class.getName());
|
||||
|
||||
private final long intervalMinutes;
|
||||
|
||||
public ServerDataPruner(long intervalMinutes) {
|
||||
this.intervalMinutes = intervalMinutes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
|
@ -21,7 +26,7 @@ public class ServerDataPruner implements Runnable {
|
|||
WHERE DATEDIFF('MINUTE', servers.updated_at, CURRENT_TIMESTAMP(0)) > ?
|
||||
""";
|
||||
PreparedStatement stmt = con.prepareStatement(sql);
|
||||
stmt.setInt(1, INTERVAL_MINUTES);
|
||||
stmt.setLong(1, this.intervalMinutes);
|
||||
int rowCount = stmt.executeUpdate();
|
||||
stmt.close();
|
||||
if (rowCount > 0) {
|
||||
|
|
|
@ -6,6 +6,9 @@ import java.util.function.Function;
|
|||
|
||||
import static nl.andrewlalis.aos_server_registry.ServerRegistry.mapper;
|
||||
|
||||
/**
|
||||
* Helper methods for working with HTTP requests.
|
||||
*/
|
||||
public class Requests {
|
||||
public static <T> T getBody(HttpServletRequest req, Class<T> bodyClass) throws IOException {
|
||||
return mapper.readValue(req.getInputStream(), bodyClass);
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
# Default properties for the AOS Server Registry
|
||||
|
||||
port=8567
|
||||
prune-delay=60
|
||||
prune-threshold-minutes=5
|
|
@ -16,9 +16,7 @@ import java.time.Duration;
|
|||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* The registry manager is responsible for keeping the server registry up to
|
||||
|
@ -33,12 +31,22 @@ public class RegistryManager {
|
|||
public static final long[] RETRY_TIMINGS = new long[]{5, 10, 30, 60, 120, 300};
|
||||
private int retryTimingIndex = 0;
|
||||
|
||||
private static final String ICON_FILE = "icon.png";
|
||||
private static final int ICON_SIZE = 64;
|
||||
|
||||
private final ScheduledExecutorService executorService;
|
||||
private final Server server;
|
||||
|
||||
private final ObjectMapper mapper;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
/**
|
||||
* Future that represents a planned attempt to send this server's info to
|
||||
* the registry. This is tracked so that we can cancel this attempt if we
|
||||
* discover during a more frequent update ping that the registry has been
|
||||
* reset.
|
||||
*/
|
||||
private Future<?> infoSendFuture;
|
||||
|
||||
public RegistryManager(Server server) {
|
||||
this.server = server;
|
||||
this.mapper = new ObjectMapper();
|
||||
|
@ -73,14 +81,14 @@ public class RegistryManager {
|
|||
var response = this.httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (response.statusCode() != 200) {
|
||||
System.err.println("Non-OK status when sending registry info:\n" + response.body() + "\nAttempting to send again in 10 seconds...");
|
||||
this.executorService.schedule(this::sendInfo, 10, TimeUnit.SECONDS);
|
||||
this.infoSendFuture = this.executorService.schedule(this::sendInfo, 10, TimeUnit.SECONDS);
|
||||
} else if (this.retryTimingIndex > 0) {
|
||||
this.retryTimingIndex = 0; // Reset the retry timing index if we successfully sent our server info.
|
||||
}
|
||||
} catch (IOException e) {
|
||||
long retryTiming = RETRY_TIMINGS[this.retryTimingIndex];
|
||||
System.err.println("Could not send info to registry server. Registry may be offline, or this server may not have internet access. Attempting to resend info in " + retryTiming + " seconds...");
|
||||
this.executorService.schedule(this::sendInfo, retryTiming, TimeUnit.SECONDS);
|
||||
this. infoSendFuture = this.executorService.schedule(this::sendInfo, retryTiming, TimeUnit.SECONDS);
|
||||
if (this.retryTimingIndex < RETRY_TIMINGS.length - 1) {
|
||||
this.retryTimingIndex++;
|
||||
}
|
||||
|
@ -103,8 +111,15 @@ public class RegistryManager {
|
|||
.build();
|
||||
try {
|
||||
var response = this.httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (response.statusCode() != 200) {
|
||||
System.err.println("Received non-OK status when sending registry update:\n" + response.body());
|
||||
if (response.statusCode() == 404) {
|
||||
System.out.println("Registry doesn't recognize this server yet. Sending info now...");
|
||||
// Cancel any existing plans to send info in the future, and send it now.
|
||||
if (this.infoSendFuture != null && !this.infoSendFuture.isDone()) {
|
||||
this.infoSendFuture.cancel(false);
|
||||
}
|
||||
this.infoSendFuture = this.executorService.submit(this::sendInfo);
|
||||
} else if (response.statusCode() != 200) {
|
||||
System.err.printf("Received status %d when sending registry update:\n%s\n", response.statusCode(), response.body());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("Error sending update to registry server: " + e);
|
||||
|
@ -114,15 +129,24 @@ public class RegistryManager {
|
|||
}
|
||||
}
|
||||
|
||||
private String getIconData() throws IOException {
|
||||
Path iconFile = Path.of("icon.png");
|
||||
/**
|
||||
* Gets a Base64-URL-encoded string representing the bytes of this server's
|
||||
* icon, if the server has an icon, or null otherwise.
|
||||
* @return The Base64-encoded bytes of the server icon, or null.
|
||||
*/
|
||||
private String getIconData() {
|
||||
Path iconFile = Path.of(ICON_FILE);
|
||||
if (Files.exists(iconFile)) {
|
||||
byte[] imageBytes = Files.readAllBytes(iconFile);
|
||||
BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageBytes));
|
||||
if (image.getWidth() == 64 && image.getHeight() == 64) {
|
||||
return Base64.getUrlEncoder().encodeToString(imageBytes);
|
||||
} else {
|
||||
System.err.println("icon.png must be 64 x 64.");
|
||||
try {
|
||||
byte[] imageBytes = Files.readAllBytes(iconFile);
|
||||
BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageBytes));
|
||||
if (image.getWidth() == ICON_SIZE && image.getHeight() == ICON_SIZE) {
|
||||
return Base64.getUrlEncoder().encodeToString(imageBytes);
|
||||
} else {
|
||||
System.err.printf("%s must be %d x %d.\n", ICON_FILE, ICON_SIZE, ICON_SIZE);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
|
Loading…
Reference in New Issue