Platform-agnostic web server abstraction for HTTP APIs
The WebServer Framework provides a clean abstraction layer for serving HTTP APIs from Hytale plugins, following the Argonath Systems architecture principle of Zero Hytale Imports in Business Logic.
This framework enables mods and tools to expose RESTful APIs without coupling to any specific HTTP server implementation. The actual server (Nitrado WebServer Plugin) is provided by the adapter layer.
┌─────────────────────────────────────────┐
│ Mods (06-mod-*) │
│ - HyQuest Integration │
│ - HyPrefab Integration │
│ Uses: WebServerAccessor │
└─────────────────┬───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ 03-framework-webserver │
│ - WebServerAccessor (interface) │
│ - RouteHandler (interface) │
│ - HttpRequest/Response (wrappers) │
│ Platform Agnostic │
└─────────────────┬───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ 02-adapter-hytale │
│ - NitradoWebServerAdapter │
│ Uses: Nitrado WebServer Plugin │
└─────────────────────────────────────────┘
- ✅ Platform-Agnostic: No Hytale/Nitrado imports in business logic
- ✅ Simple API: Easy route registration and handling
- ✅ Type-Safe: Strongly-typed request/response wrappers
- ✅ Authentication: Built-in user principal access
- ✅ Testable: Easy to mock for unit tests
public class MyApiMod {
private final WebServerAccessor webServer;
public void initialize(WebServerAccessor webServer) {
this.webServer = webServer;
// Register GET /api/quests
webServer.registerRoute("/api/quests", this::listQuests);
// Register GET /api/quests/:id
webServer.registerRoute("/api/quests/:id", this::getQuest);
// Register POST /api/quests
webServer.registerRoute("/api/quests", HttpMethod.POST, this::createQuest);
}
private void listQuests(HttpRequest request, HttpResponse response) {
// Check authentication
if (!request.getUser().isPresent()) {
response.setStatus(401);
response.write("{\"error\": \"Authentication required\"}");
return;
}
// Query database/service
List<Quest> quests = questService.findAll();
// Send JSON response
response.setContentType("application/json");
response.write(toJson(quests));
}
private void getQuest(HttpRequest request, HttpResponse response) {
String questId = request.getPathParam("id");
questService.findById(questId).ifPresentOrElse(
quest -> {
response.setContentType("application/json");
response.write(toJson(quest));
},
() -> {
response.setStatus(404);
response.write("{\"error\": \"Quest not found\"}");
}
);
}
}public void shutdown() {
webServer.unregisterAllRoutes(this);
}Main interface for web server operations:
public interface WebServerAccessor {
void registerRoute(String path, RouteHandler handler);
void registerRoute(String path, HttpMethod method, RouteHandler handler);
void unregisterRoute(String path);
void unregisterAllRoutes(Object owner);
boolean isAvailable();
int getPort();
}Functional interface for handling HTTP requests:
@FunctionalInterface
public interface RouteHandler {
void handle(HttpRequest request, HttpResponse response) throws Exception;
}Request wrapper providing:
- HTTP method, path, query parameters
- Headers, body content
- Path parameters (for
/api/items/:id) - User authentication (optional)
Response builder providing:
- Status code setting
- Header management
- Content type setting
- Body writing
public class HyQuestApiController {
private final QuestService questService;
private final WebServerAccessor webServer;
public void register() {
webServer.registerRoute("/api/v1/quests", this::listQuests);
webServer.registerRoute("/api/v1/quests/:id", this::getQuest);
webServer.registerRoute("/api/v1/quests", HttpMethod.POST, this::createQuest);
webServer.registerRoute("/api/v1/quests/:id", HttpMethod.PUT, this::updateQuest);
webServer.registerRoute("/api/v1/quests/:id", HttpMethod.DELETE, this::deleteQuest);
}
}public class HyPrefabApiController {
private final PrefabService prefabService;
private final WebServerAccessor webServer;
public void register() {
webServer.registerRoute("/api/v1/prefabs", this::listPrefabs);
webServer.registerRoute("/api/v1/prefabs/:id", this::getPrefab);
webServer.registerRoute("/api/v1/prefabs", HttpMethod.POST, this::createPrefab);
}
}@Test
void testQuestApi() {
// Mock the web server
WebServerAccessor mockWebServer = mock(WebServerAccessor.class);
// Capture registered routes
ArgumentCaptor<RouteHandler> handlerCaptor = ArgumentCaptor.forClass(RouteHandler.class);
when(mockWebServer.registerRoute(eq("/api/quests"), handlerCaptor.capture()));
// Initialize API
QuestApi api = new QuestApi(mockWebServer, questService);
api.register();
// Test the handler
HttpRequest mockRequest = mock(HttpRequest.class);
HttpResponse mockResponse = mock(HttpResponse.class);
handlerCaptor.getValue().handle(mockRequest, mockResponse);
verify(mockResponse).setContentType("application/json");
}02-framework-core: Core framework utilities02-framework-accessor: Accessor pattern interfaces- JUnit 5 (testing)
- Mockito (testing)
MIT License - See LICENSE for details.