Request Error Handling in Hyperlane
Project Code:https://github.com/hyperlane-dev/hyperlane
Introduction
Robust error handling is a cornerstone of any production-grade HTTP server. The hyperlane framework provides a comprehensive error handling mechanism that allows developers to gracefully manage request-level errors, respond with appropriate HTTP status codes, and maintain server stability even when things go wrong. This article explores the request error handling system in hyperlane, covering the RequestError type, error middleware, and practical patterns for building resilient applications.
Understanding RequestError
In hyperlane, request errors are represented by the RequestError type. When something goes wrong during request processing—whether it's a malformed request, a timeout, or a business logic failure—you can capture and handle these errors through the framework's error handling pipeline.
The key entry points for accessing request error data are:
-
ctx.try_get_request_error_data()— Attempts to retrieve request error data from the current context. -
#[try_get_request_error_data]— An attribute macro that automatically extracts request error data. -
#[request_error_data]— A convenience attribute for declaring request error data parameters.
These mechanisms allow you to inspect what went wrong and respond accordingly, rather than letting errors propagate as unhandled exceptions.
The Request Error Middleware
Hyperlane provides a dedicated request_error middleware hook that fires when a request error occurs. This middleware receives a RequestError object and lets you implement custom error handling logic.
Registering an Error Middleware
You can register a request error middleware using the #[request_error] attribute macro. This middleware will be invoked whenever a request error is detected during processing.
#[request_error]
async fn handle_request_error(error: RequestError) {
// Log the error details
eprintln!("Request error occurred: {:?}", error);
}
The #[request_error] macro marks a function as a request error handler. When registered with the server, this function will be called automatically whenever a request error is encountered, giving you a centralized place to handle failures.
Error Middleware with Context
You can also access the full server context within your error handling middleware to inspect request details and craft appropriate responses:
#[request_error]
async fn handle_request_error(ctx: &mut ServerContext) {
if let Some(error_data) = ctx.try_get_request_error_data() {
eprintln!("Error data: {:?}", error_data);
}
}
Retrieving Error Data from Context
When processing a request, you may need to check for error data at various stages. Hyperlane provides multiple ways to access this information.
Using try_get_request_error_data
The try_get_request_error_data() method returns an Option type, making it safe to call even when no error has occurred:
async fn process_request(ctx: &mut ServerContext) {
if let Some(error_data) = ctx.try_get_request_error_data() {
// An error was detected — handle it
handle_error(error_data).await;
} else {
// No error — proceed normally
process_valid_request(ctx).await;
}
}
Using the #[try_get_request_error_data] Attribute
For a more declarative approach, you can use the #[try_get_request_error_data] attribute macro to automatically extract error data:
#[try_get_request_error_data]
async fn process_request(ctx: &mut ServerContext, request_error_data: Option<RequestError>) {
match request_error_data {
Some(error) => {
// Handle the error case
eprintln!("Request error: {:?}", error);
}
None => {
// No error — normal processing
}
}
}
Using the #[request_error_data] Attribute
The #[request_error_data] attribute provides a more concise way to declare error data as a parameter:
#[request_error_data]
async fn process_request(ctx: &mut ServerContext, request_error_data: RequestError) {
// This function is only called when request error data exists
eprintln!("Handling error: {:?}", request_error_data);
}
Practical Error Handling Patterns
Pattern 1: Centralized Error Response
One common pattern is to create a centralized error handler that maps error types to appropriate HTTP responses:
#[request_error]
async fn centralized_error_handler(ctx: &mut ServerContext) {
if let Some(error_data) = ctx.try_get_request_error_data() {
let response = ctx.get_mut_response();
response.set_status_code(500);
response.set_body("Internal Server Error".to_string());
}
}
Pattern 2: Error Logging and Monitoring
You can use request error middleware to feed errors into your logging and monitoring systems:
#[request_error]
async fn error_logging_middleware(ctx: &mut ServerContext) {
if let Some(error_data) = ctx.try_get_request_error_data() {
let request = ctx.get_request();
let path = request.get_path();
let method = request.get_method();
eprintln!(
"Error on {} {}: {:?}",
method, path, error_data
);
}
}
Pattern 3: Graceful Degradation
For applications that need to remain available even when certain operations fail, you can implement graceful degradation:
#[request_error]
async fn graceful_degradation_handler(ctx: &mut ServerContext) {
if let Some(error_data) = ctx.try_get_request_error_data() {
let response = ctx.get_mut_response();
// Return a fallback response instead of failing completely
response.set_status_code(200);
response.set_body("Fallback response".to_string());
}
}
Pattern 4: Differentiated Error Responses
Different error types may require different responses. You can inspect the error data and respond accordingly:
#[request_error]
async fn differentiated_error_handler(ctx: &mut ServerContext) {
if let Some(error_data) = ctx.try_get_request_error_data() {
let response = ctx.get_mut_response();
// Set appropriate status code and message based on error type
response.set_status_code(400);
response.set_header("Content-Type", "application/json");
response.set_body(r#"{"error": "Bad Request", "message": "Invalid request data"}"#.to_string());
}
}
Combining Error Handling with Other Middleware
Request error handling in hyperlane works seamlessly alongside other middleware types. You can combine request_error middleware with request_middleware and response_middleware to build a complete request processing pipeline:
// Pre-request validation
#[request_middleware]
async fn validate_request(ctx: &mut ServerContext) {
let request = ctx.get_request();
if request.get_headers().is_empty() {
// This could trigger the error handler
}
}
// Error handling
#[request_error]
async fn handle_errors(ctx: &mut ServerContext) {
if let Some(error_data) = ctx.try_get_request_error_data() {
eprintln!("Caught error: {:?}", error_data);
let response = ctx.get_mut_response();
response.set_status_code(500);
response.set_body("An error occurred".to_string());
}
}
// Post-response processing
#[response_middleware]
async fn add_error_headers(ctx: &mut ServerContext) {
let response = ctx.get_mut_response();
response.add_header("X-Error-Handling", "enabled");
}
Error Handling in Route Handlers
Within individual route handlers, you can also check for request error data and respond appropriately:
#[route("/api/data")]
async fn get_data(ctx: &mut ServerContext) {
// Check for errors before processing
if let Some(error_data) = ctx.try_get_request_error_data() {
let response = ctx.get_mut_response();
response.set_status_code(400);
response.set_body("Invalid request".to_string());
return;
}
// Normal processing
let response = ctx.get_mut_response();
response.set_status_code(200);
response.set_body("{\"data\": \"success\"}".to_string());
}
Best Practices
Always handle errors explicitly: Never let request errors go unhandled. Use
#[request_error]middleware to catch and respond to errors.Log errors for debugging: Use the error middleware to log detailed error information, including request path, method, and error details.
Return appropriate HTTP status codes: Map your errors to the correct HTTP status codes (400 for bad requests, 500 for internal errors, etc.).
Don't expose internal details: In production, avoid exposing stack traces or internal error details in responses. Log them server-side instead.
Use centralized error handling: Create a single error middleware that handles all request errors consistently, rather than scattering error handling across individual routes.
Test your error paths: Ensure your error handling middleware works correctly by testing with malformed requests, timeouts, and other error scenarios.
Conclusion
Hyperlane's request error handling system provides a flexible and powerful way to manage errors in your HTTP applications. By leveraging RequestError, the #[request_error] middleware, and the various error data retrieval methods, you can build applications that gracefully handle failures and provide meaningful feedback to clients. Whether you need simple error logging or complex error recovery strategies, hyperlane gives you the tools to build resilient servers.
Project Code:https://github.com/hyperlane-dev/hyperlane
Top comments (0)