Added infrastructure for server arena management.

This commit is contained in:
Andrew Lalis 2022-04-03 09:32:11 +02:00
parent 98e177c413
commit c9b617172a
9 changed files with 240 additions and 14 deletions

View File

@ -17,4 +17,12 @@
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
</project>

View File

@ -20,15 +20,6 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>

View File

@ -0,0 +1,31 @@
package nl.andrewl.starship_arena.server;
import nl.andrewl.starship_arena.server.model.Arena;
import java.io.IOException;
import java.net.Socket;
public class ClientManager extends Thread {
private final Arena arena;
private final Socket clientSocket;
public ClientManager(Arena arena, Socket clientSocket) {
this.arena = arena;
this.clientSocket = clientSocket;
}
public void shutdown() {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (clientSocket.isConnected() && !clientSocket.isClosed()) {
}
}
}

View File

@ -1,23 +1,90 @@
package nl.andrewl.starship_arena.server;
import nl.andrewl.starship_arena.server.model.Arena;
import nl.andrewl.starship_arena.server.servlet.ArenasServlet;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
public class StarshipArenaServer {
private static final Logger logger = LoggerFactory.getLogger(StarshipArenaServer.class);
private final Map<UUID, Arena> arenas = new ConcurrentHashMap<>();
private final ServerSocket serverSocket;
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
public static void main(String[] args) throws Exception {
StarshipArenaServer server = new StarshipArenaServer();
server.registerArena(new Arena());
server.registerArena(new Arena("Andrew's Arena"));
Server jettyServer = new Server(8080);
Connector connector = new ServerConnector(jettyServer);
jettyServer.addConnector(connector);
ServletContextHandler servletContext = new ServletContextHandler();
servletContext.setContextPath("/");
servletContext.addServlet(new ServletHolder(new ArenasServlet()), "/arenas");
servletContext.addServlet(new ServletHolder(new ArenasServlet(server)), "/arenas");
jettyServer.setHandler(servletContext);
jettyServer.start();
server.acceptConnections();
}
public StarshipArenaServer() throws IOException {
serverSocket = new ServerSocket();
serverSocket.setReuseAddress(true);
}
public List<Arena> getArenas() {
List<Arena> sortedArenas = new ArrayList<>(this.arenas.values());
sortedArenas.sort(Comparator.comparing(Arena::getCreatedAt));
return sortedArenas;
}
public void registerArena(Arena a) {
arenas.put(a.getId(), a);
}
public void acceptConnections() throws Exception {
serverSocket.bind(new InetSocketAddress("127.0.0.1", 8081));
logger.info("Now accepting TCP connections.");
while (!serverSocket.isClosed()) {
Socket clientSocket = serverSocket.accept();
logger.info("Client connected from {}", clientSocket.getRemoteSocketAddress());
executor.submit(() -> {
try {
processIncomingConnection(clientSocket);
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
private void processIncomingConnection(Socket clientSocket) throws IOException {
var din = new DataInputStream(clientSocket.getInputStream());
UUID arenaId = new UUID(din.readLong(), din.readLong());
Arena arena = arenas.get(arenaId);
if (arena != null) {
var cm = new ClientManager(arenas.get(arenaId), clientSocket);
cm.start();
} else {
clientSocket.close();
}
}
}

View File

@ -1,4 +1,67 @@
package nl.andrewl.starship_arena.server.model;
import nl.andrewl.starship_arena.server.ClientManager;
import java.time.Instant;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class Arena {
private final UUID id;
private final Instant createdAt;
private final String name;
private ArenaStage currentStage = ArenaStage.STAGING;
private final Map<UUID, ClientManager> clients = new ConcurrentHashMap<>();
public Arena(String name) {
this.id = UUID.randomUUID();
this.createdAt = Instant.now();
this.name = name;
}
public Arena() {
this("Unnamed Arena");
}
public UUID getId() {
return id;
}
public Instant getCreatedAt() {
return createdAt;
}
public String getName() {
return name;
}
public ArenaStage getCurrentStage() {
return currentStage;
}
public void registerClient(UUID id, ClientManager clientManager) {
if (clients.containsKey(id)) {
clientManager.shutdown();
} else {
clients.put(id, clientManager);
}
}
@Override
public boolean equals(Object o) {
return o == this || o instanceof Arena a && id.equals(a.id);
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public String toString() {
return id.toString();
}
}

View File

@ -0,0 +1,7 @@
package nl.andrewl.starship_arena.server.model;
public enum ArenaStage {
STAGING,
BATTLE,
ANALYSIS
}

View File

@ -0,0 +1,5 @@
package nl.andrewl.starship_arena.server.servlet;
public class ArenaRequest {
public String name;
}

View File

@ -0,0 +1,19 @@
package nl.andrewl.starship_arena.server.servlet;
import nl.andrewl.starship_arena.server.model.Arena;
public record ArenaResponse(
String id,
String createdAt,
String name,
String currentStage
) {
public ArenaResponse(Arena a) {
this(
a.getId().toString(),
a.getCreatedAt().toString(),
a.getName(),
a.getCurrentStage().name()
);
}
}

View File

@ -1,16 +1,51 @@
package nl.andrewl.starship_arena.server.servlet;
import jakarta.servlet.ServletException;
import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import nl.andrewl.starship_arena.server.StarshipArenaServer;
import nl.andrewl.starship_arena.server.model.Arena;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
public class ArenasServlet extends HttpServlet {
private static final Gson gson = new Gson();
private final StarshipArenaServer server;
public ArenasServlet(StarshipArenaServer server) {
this.server = server;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
// TODO: Return the list of available arenas.
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
List<ArenaResponse> data = server.getArenas().stream()
.map(ArenaResponse::new).toList();
resp.setStatus(HttpServletResponse.SC_OK);
resp.setContentType("application/json");
resp.setCharacterEncoding("UTF-8");
gson.toJson(data, resp.getWriter());
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
if (!req.getContentType().equals("application/json")) resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Only JSON is allowed.");
ArenaRequest arenaRequest;
try {
arenaRequest = gson.fromJson(new InputStreamReader(req.getInputStream()), ArenaRequest.class);
} catch (JsonSyntaxException | JsonIOException e) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid JSON.");
return;
}
Arena arena = arenaRequest.name == null ? new Arena() : new Arena(arenaRequest.name);
server.registerArena(arena);
resp.setStatus(HttpServletResponse.SC_CREATED);
resp.setContentType("application/json");
resp.setCharacterEncoding("UTF-8");
gson.toJson(new ArenaResponse(arena), resp.getWriter());
}
}