Middleware in CabinJ
Middleware functions are a powerful feature in CabinJ that allow you to execute code during the request-response cycle. They can perform a wide variety of tasks, such as logging, authentication, data validation, compression, serving static files, and error handling.
Middleware functions have access to the Request
object, the Response
object, and the next
middleware function in the application’s request-response cycle. The next
middleware function is a function in the MiddlewareChain
which, when invoked, executes the middleware succeeding the current middleware.
Core Concepts
- Interface: Middleware in CabinJ is defined by implementing the
Middleware
interface. apply
Method: This interface has a single method:void apply(Request req, Response res, MiddlewareChain next) throws IOException;
.req
: The current HTTP request.res
: The current HTTP response.next
: AMiddlewareChain
object that, when itsnext(req, res)
method is called, passes control to the next middleware in the stack or to the final route handler.
- Order of Execution: Middleware is executed in the order it is added.
- Control Flow: A middleware can:
- Perform operations on the request or response.
- End the request-response cycle (e.g., by sending a response for authentication failure).
- Call the
next.next(req, res)
method to pass control to the next middleware. - Modify the request or response objects before passing them to the next middleware.
Creating Middleware
To create a middleware, implement the Middleware
interface:
import com.cabin.express.http.Request;
import com.cabin.express.http.Response;
import com.cabin.express.interfaces.Middleware;
import com.cabin.express.middleware.MiddlewareChain;
import java.io.IOException;
public class LoggerMiddleware implements Middleware {
@Override
public void apply(Request req, Response res, MiddlewareChain next) throws IOException {
System.out.println("Request received: " + req.getMethod() + " " + req.getPath());
// Call the next middleware or handler in the chain
next.next(req, res);
System.out.println("Response sent with status: " + res.getStatusCode());
}
}
Using Middleware
Middleware can be applied at different levels: server-level (global for all requests), router-level (global for a specific router), or route-specific.
1. Server-Level Middleware
Applied to every request handled by the CabinServer
instance.
CabinServer server = new ServerBuilder().setPort(8080).build();
// Apply LoggerMiddleware to all incoming requests
server.use(new LoggerMiddleware());
Router mainRouter = new Router();
mainRouter.get("/", (req, res) -> res.send("Hello World!"));
server.use(mainRouter);
server.start();
2. Router-Level Middleware
Applied to every request handled by a specific Router
instance and its sub-routers.
Router apiRouter = new Router();
// This middleware will apply to all routes defined in apiRouter
apiRouter.use((req, res, next) -> {
System.out.println("API Router Middleware: Path " + req.getPath());
// Example: Add a custom header for all API responses
res.setHeader("X-API-Version", "1.0");
next.next(req, res);
});
apiRouter.get("/users", (req, res) -> res.json(Map.of("user", "John Doe")));
apiRouter.get("/products", (req, res) -> res.json(Map.of("product", "Laptop")));
CabinServer server = new ServerBuilder().build();
server.use("/api", apiRouter); // Mount the router with its middleware
server.start();
3. Route-Specific Middleware
Applied only to specific routes.
Router router = new Router();
Middleware authMiddleware = (req, res, next) -> {
String token = req.getHeader("Authorization");
if (token != null && "Bearer valid-token".equals(token)) {
System.out.println("Authentication successful for: " + req.getPath());
next.next(req, res); // Proceed to the handler
} else {
System.out.println("Authentication failed for: " + req.getPath());
res.setStatusCode(401).send("Unauthorized");
// Do not call next.next() to stop processing
}
};
// Apply authMiddleware only to the /secure-data route
router.get("/secure-data", authMiddleware, (req, res) -> {
res.send("This is highly sensitive data.");
});
router.get("/public-data", (req, res) -> {
res.send("This data is public.");
});
CabinServer server = new ServerBuilder().build();
server.use(router);
server.start();
You can also pass a list of middleware:
List<Middleware> securityChecks = List.of(authMiddleware, anotherCheckMiddleware);
router.get("/super-secure", securityChecks, (req, res) -> {
res.send("This is super secure data.");
});
Middleware Chain (MiddlewareChain
)
The MiddlewareChain
class is responsible for managing the execution flow of middleware and the final route handler.
When chain.next(req, res)
is called within a middleware:
- If there are more middleware functions in the chain, the
apply
method of the next middleware is invoked. - If all middleware functions have been executed, the final
routeHandler.handle(req, res)
is invoked.
This mechanism allows for pre-processing of requests and post-processing of responses.
Built-in Middleware: StaticMiddleware
CabinJ includes StaticMiddleware
for serving static files (e.g., HTML, CSS, JavaScript, images).
import com.cabin.express.middleware.StaticMiddleware;
import com.cabin.express.server.CabinServer;
import com.cabin.express.server.ServerBuilder;
import com.cabin.express.router.Router;
// ...
// Assuming you have a directory "public" with static files
// e.g., public/index.html, public/css/style.css
// Serve files from the "public" directory under the "/static" URL prefix
StaticMiddleware staticMiddleware = new StaticMiddleware("public", "/static");
// You can exclude certain paths or routers from being handled by StaticMiddleware
Router apiRouter = new Router();
apiRouter.get("/data", (req, res) -> res.json(Map.of("message", "API Data")));
// Exclude the /api prefix so StaticMiddleware doesn't try to serve files for API calls
staticMiddleware.excludePrefixes("/api");
// Alternatively, you could exclude the router instance if it's mounted at root
// staticMiddleware.excludeRouters(apiRouter);
CabinServer server = new ServerBuilder().setPort(8080).build();
// Add StaticMiddleware first, so it can serve files
server.use(staticMiddleware);
// Then add your API router
server.use("/api", apiRouter);
// Add a root handler as a fallback or for the main page if not served by static
server.get("/", (req, res) -> res.send("Welcome to CabinJ!"));
server.start();
In this example:
- Requests to
/static/index.html
would servepublic/index.html
. - Requests to
/static/css/style.css
would servepublic/css/style.css
. - Requests to
/api/data
would be handled byapiRouter
and notStaticMiddleware
. - Requests to
/
would be handled by the server's root GET handler.
If StaticMiddleware
is configured with a root URL prefix (e.g., /
), it can serve index.html
by default for requests to /
.
// Serve files from "public" directory at the root URL
StaticMiddleware rootStaticMiddleware = new StaticMiddleware("public", "/");
rootStaticMiddleware.excludePrefixes("/api"); // Still exclude API
CabinServer server = new ServerBuilder().setPort(8080).build();
server.use("/api", apiRouter); // Mount API router first if static is at root
server.use(rootStaticMiddleware); // Static middleware handles remaining requests
server.start();
// Now, a request to "/" might serve "public/index.html"
Middleware is a fundamental concept for building robust and modular web applications with CabinJ.