# NexusWeb A lightweight, high-performance HTTP server library built on top of Netty. NexusWeb provides a fluent API for routing, middleware, CORS handling, and rate limiting — all running on Java virtual threads for maximum concurrency. ## Features - **Netty-based** — uses epoll/kqueue/NIO automatically based on the platform - **Virtual thread dispatch** — each request is handled on a Java 21 virtual thread - **Trie-based router** — supports static paths, path parameters (`{id}`), and wildcards (`*`) - **Annotation-based controllers** — define routes declaratively with `@Controller`, `@GET`, `@POST`, etc. - **Middleware chain** — attach cross-cutting logic to all routes - **CORS support** — configurable origins, methods, headers, credentials, and preflight caching - **Rate limiting** — four algorithm implementations with per-IP, per-token, or custom key strategies - **JSON I/O** — built-in Jackson integration for request parsing and response serialization --- ## Quick Start ```java Router router = new Router(); router.get("/hello", (req, res) -> { res.status(200).json("{\"message\":\"Hello, World!\"}"); }); HttpServer.builder(8080, router).start(); ``` --- ## Routing Routes are registered with `GET`, `POST`, `PUT`, `DELETE`, or the generic `register` method. ```java Router router = new Router(); // Static path router.get("/users", (req, res) -> { ... }); // Path parameter router.get("/users/{id}", (req, res) -> { String id = req.pathParam("id"); res.json("{\"id\":\"" + id + "\"}"); }); // Wildcard router.get("/files/*", (req, res) -> { ... }); // POST with JSON body router.post("/users", (req, res) -> { MyDto dto = req.jsonAs(MyDto.class); res.status(201).json(dto); }); ``` ### Router resolution | Outcome | HTTP Status | |---|---| | Path and method match | Handler is called | | Path exists, method not registered | `405 Method Not Allowed` + `Allow` header | | Path not found | `404 Not Found` | ### Annotation-based Controllers Instead of registering lambdas on the `Router` directly, you can declare routes as methods on a class and register the whole object at once via `AnnotationScanner`. **Annotations** | Annotation | Target | Description | |---|---|---| | `@Controller("prefix")` | class | Optional path prefix applied to all methods in the class | | `@GET("/path")` | method | Maps a `GET` route | | `@POST("/path")` | method | Maps a `POST` route | | `@PUT("/path")` | method | Maps a `PUT` route | | `@DELETE("/path")` | method | Maps a `DELETE` route | | `@PATCH("/path")` | method | Maps a `PATCH` route | | `@Route(method="…", path="…")` | method | Generic — any standard HTTP method by name | | `@CUSTOM(method="…", value="…")` | method | Custom/non-standard HTTP methods | Every annotated method **must** have the exact signature `(Request req, Response res)` and return `void`. ```java @Controller("/users") public class UserController { @GET("") public void list(Request req, Response res) { res.json("[{\"id\":1}]"); } @GET("/{id}") public void get(Request req, Response res) { res.json("{\"id\":\"" + req.pathParam("id") + "\"}"); } @POST("") public void create(Request req, Response res) { UserDto dto = req.jsonAs(UserDto.class); // ... persist ... res.status(201).json(dto); } @PUT("/{id}") public void update(Request req, Response res) { // ... } @DELETE("/{id}") public void delete(Request req, Response res) { res.status(204); } @PATCH("/{id}") public void patch(Request req, Response res) { // ... } } ``` Register the controller with: ```java Router router = new Router(); AnnotationScanner.register(router, new UserController()); HttpServer.builder(8080, router).start(); ``` `AnnotationScanner.register` prints each registered route to stdout: ``` Registered: GET /users -> UserController.list Registered: GET /users/{id} -> UserController.get Registered: POST /users -> UserController.create ``` Multiple controllers can be registered on the same router: ```java AnnotationScanner.register(router, new UserController()); AnnotationScanner.register(router, new OrderController()); ``` --- ### Middleware Middleware runs before the matched handler on every request. ```java router.use((req, res) -> { res.header("X-Request-Id", UUID.randomUUID().toString()); }); ``` --- ## Request API ```java req.pathParam("id") // path parameter, e.g. from /users/{id} req.queryParam("search") // first value of ?search= req.queryParams("tag") // all values of ?tag= as List req.header("Authorization") // raw header value req.body() // raw body as UTF-8 String req.json() // body parsed as Jackson JsonNode req.jsonAs(MyDto.class) // body deserialized into a POJO req.method() // HttpMethod req.path() // decoded path without query string ``` `json()` and `jsonAs()` throw `BadRequestException` (→ `400`) on malformed JSON. --- ## Response API ```java res.status(201) .header("X-Custom", "value") .json("{\"ok\":true}"); // sets Content-Type: application/json res.text("plain text"); // sets Content-Type: text/plain res.json(someObject); // serializes POJO via Jackson ``` --- ## CORS ```java CorsConfig config = CorsConfig.builder() .allowedOrigins("https://app.example.com") .allowedMethods(HttpMethod.GET, HttpMethod.POST) .allowedHeaders("Content-Type", "Authorization") .allowCredentials(true) .maxAgeSeconds(3600) .build(); CorsHandler cors = new CorsHandler(config); HttpServer.builder(8080, router) .withCorsHandler(cors) .start(); ``` Use `CorsConfig.permissive()` for a development preset that allows any origin with common methods and headers (incompatible with `allowCredentials`). **Preflight requests** (`OPTIONS` + `Access-Control-Request-Method`) are handled automatically and short-circuit before the router. --- ## Rate Limiting ### Algorithms | Class | Description | |---|---| | `TokenBucketLimiter` | Smooth bursts up to a configurable capacity, refills at a steady rate | | `FixedWindowLimiter` | Hard limit per fixed time window | | `SlidingWindowLimiter` | Weighted sliding window — reduces boundary spikes vs. fixed window | | `LeakyBucketLimiter` | Constant outflow rate; excess requests are rejected immediately | ### Configuration ```java RateLimitConfig config = RateLimitConfig.builder() // Global rule for all routes — keyed by client IP .global(new TokenBucketLimiter(100, 200), KeyResolver.clientIp()) // Stricter rule for a specific path .forPath("/login", new FixedWindowLimiter(5, 60_000), KeyResolver.clientIp()) // Rule for an entire path prefix .forPrefix("/api/", new SlidingWindowLimiter(1000, 1000), KeyResolver.userOrIp()) .build(); RateLimitGate gate = new RateLimitGate(config); HttpServer.builder(8080, router) .withRateLimitGate(gate) .start(); ``` When the limit is exceeded the server responds with `429 Too Many Requests` and a `Retry-After` header. ### Response headers Every response automatically includes: | Header | Description | |---|---| | `X-RateLimit-Limit` | Configured limit for the matched rule | | `X-RateLimit-Remaining` | Remaining requests in the current window | | `Retry-After` | Seconds until the client may retry (only on `429`) | ### Key resolvers | Factory | Behaviour | |---|---| | `KeyResolver.clientIp()` | Uses `X-Forwarded-For` if present, otherwise the remote IP | | `KeyResolver.userOrIp()` | Uses the Bearer token if present (`u:`), otherwise the client IP (`ip:`) | | Custom lambda | `(req, remoteAddr) -> myKey(req)` | --- ## Full Example ```java // Controller class @Controller("/users") public class UserController { @GET("") public void list(Request req, Response res) { res.json("[{\"id\":1}]"); } @GET("/{id}") public void get(Request req, Response res) { res.json("{\"id\":\"" + req.pathParam("id") + "\"}"); } @POST("") public void create(Request req, Response res) { UserDto dto = req.jsonAs(UserDto.class); res.status(201).json(dto); } } // Server setup Router router = new Router(); // Middleware — add request ID to every response router.use((req, res) -> res.header("X-Request-Id", UUID.randomUUID().toString())); AnnotationScanner.register(router, new UserController()); CorsHandler cors = new CorsHandler(CorsConfig.permissive()); RateLimitConfig rlConfig = RateLimitConfig.builder() .global(new TokenBucketLimiter(200, 400), KeyResolver.clientIp()) .build(); RateLimitGate gate = new RateLimitGate(rlConfig); HttpServer.builder(8080, router) .withCorsHandler(cors) .withRateLimitGate(gate) .start(); ``` --- ## Requirements - Java 26+ - Netty 4.x - Jackson (tools.jackson)