Appearance
⚡ Handler Default Implementations (HDI)
Handler Default Implementations (HDIs) offer a streamlined approach to standardize common behaviors across request handlers in Flash. By extending a base RequestHandler
(or chaining multiple base handlers), you can centralize tasks like authentication, user data retrieval, and rate limiting while keeping your code modular and maintainable.
HDIs use the Chain of Responsibility pattern to layer reusable logic, ensuring that shared functionality is defined once and inherited by all handlers.
🔗 How HDIs Work
Rather than repeating common logic across different handlers, HDIs allow you to create abstract base handlers that encapsulate shared behaviors. When your individual request handlers extend these bases, they automatically inherit predefined functionality, and you only need to implement request-specific logic.
Key Benefits
Build-Time Optimization:
When your project is compiled, Flash’s router merges the entire HDI chain into a single handler instance. This eliminates extra function calls and runtime lookups, resulting in a leaner execution path compared to traditional middleware stacks.Reduced Complexity:
Unlike frameworks that rely on reflection (e.g., Spring Boot) or a deep middleware stack (e.g., Express.js), HDIs embed inherited behavior directly in the compiled class, minimizing runtime overhead.Type Safety & Clean State Management:
Protected fields in HDIs allow seamless data sharing between handlers without using global variables, callbacks, or type casting. Note: Always declare these fields as instance (non-static) variables to ensure each handler maintains its own state.
🛡️ HDI Design Guidelines
1. Base HDI Class
Define an abstract base class that extends RequestHandler
to encapsulate common logic:
Constructor:
Initialize by passingRequest
andResponse
objects to the superclass.Overridden
handle
Method:
Implement common logic and delegate to an abstract method for custom behavior.java@Override public Object handle() { // Insert common logic here (e.g., logging, header processing) return handleCustom(); }
Abstract Method:
Declare an abstract method that must be implemented by subclasses.javaprotected abstract Object handleCustom();
Protected Fields:
Use protected instance fields to share data between HDI layers.Warning: Do not declare these fields as static. Each handler should manage its own state.
java// Incorrect: static field protected static String data; // Correct: instance field protected String data;
2. Concrete Handler Implementation
Extend the base HDI class in your handler:
Constructor:
Call the super constructor with the necessaryRequest
andResponse
objects.Implement
handleCustom()
:
Write the request-specific logic here. Protected fields from the base class are available within this method.java@Override protected Object handleCustom() { // Custom logic using inherited data return "Response"; }
🛠️ Example: API Key Authentication
This example demonstrates how to build an HDI that validates an API key before processing a request.
Abstract API Key Protected Handler
java
public abstract class APIKeyProtectedHandler extends RequestHandler {
protected String apiKey;
public APIKeyProtectedHandler(Request req, Response res) {
super(req, res);
}
@Override
public Object handle() {
apiKey = req.header("X-API-Key");
if (apiKey == null || !isValidApiKey(apiKey)) {
res.status(403);
res.type("application/json");
return "{\"error\":\"Invalid API Key\"}";
}
return handleAuthorized();
}
protected abstract Object handleAuthorized();
private boolean isValidApiKey(String key) {
// Implement your API key validation logic here
return true;
}
}
Concrete API Handler
Extend the abstract handler to process the request only if the API key is valid:
java
@RouteInfo(endpoint = "/data", method = HttpMethod.GET)
public class GetDataHandler extends APIKeyProtectedHandler {
public GetDataHandler(Request req, Response res) {
super(req, res);
}
@Override
protected Object handleAuthorized() {
res.type("application/json");
return "{\"data\":\"Your API response here\"}";
}
}
🏗️ Chaining HDIs for Modular Logic
HDIs can be layered to build complex flows. For instance, you might first authenticate a request, then fetch user data.
Protected Handler (Authentication)
java
public abstract class ProtectedHandler extends RequestHandler {
protected String authToken;
public ProtectedHandler(Request req, Response res) {
super(req, res);
}
@Override
public Object handle() {
authToken = req.header("Authorization");
if (authToken == null || !isValidToken(authToken)) {
res.status(401);
res.type("application/json");
return "{\"error\":\"Unauthorized\"}";
}
return handleAuthenticated();
}
protected abstract Object handleAuthenticated();
private boolean isValidToken(String token) {
// Validate the token here
return true;
}
}
Authenticated Handler (User Data Retrieval)
Extend the ProtectedHandler
to fetch user details:
java
public abstract class AuthenticatedHandler extends ProtectedHandler {
protected User user;
public AuthenticatedHandler(Request req, Response res) {
super(req, res);
}
@Override
protected Object handleAuthenticated() {
user = getUserFromDatabase(authToken);
if (user == null) {
res.status(403);
res.type("application/json");
return "{\"error\":\"User not found\"}";
}
return handleWithUser();
}
protected abstract Object handleWithUser();
}
Final Handler Implementation
Implement the final handler that uses the authenticated user data:
java
@RouteInfo(endpoint = "/profile", method = HttpMethod.GET)
public class UserProfileHandler extends AuthenticatedHandler {
public UserProfileHandler(Request req, Response res) {
super(req, res);
}
@Override
protected Object handleWithUser() {
res.type("application/json");
return "{\"username\":\"" + user.getUsername() + "\"}";
}
}
For reference, here's a visual representation of how an HDI chain operates: