Project Code:https://github.com/hyperlane-dev/hyperlane
Introduction
Routing is the mechanism by which an HTTP server maps incoming requests to the appropriate handler. In Hyperlane, routing is both flexible and powerful, supporting static routes, dynamic path parameters, regex-based matching, and route filtering. This article covers everything you need to know about setting up routes in Hyperlane.
Static Routes
The simplest form of routing is a static route, which matches an exact URL path:
server.route::<Route>("/test");
This registers a route that only matches requests to the exact path /test. Any other path will not be handled by this route. Static routes are ideal for fixed endpoints like /health, /about, or /api/status.
Dynamic Routes
Dynamic routes include path parameters that capture variable parts of the URL. Parameters are enclosed in curly braces {...}:
server.route::<Route>("/test/{text}");
In this example, {text} is a dynamic parameter that matches any single path segment. For example:
-
/test/hello—text="hello" -
/test/world—text="world" -
/test/123—text="123"
However, /test/hello/world would NOT match because {text} only matches a single segment.
Regex Dynamic Routes
For more precise control over what a parameter matches, you can use regex patterns:
server.route::<Route>("/test/{number:\\d+}");
In this example, {number:\\d+} only matches one or more digits. For example:
-
/test/123—number="123"(matches) -
/test/abc— does not match (contains non-digit characters) -
/test/42—number="42"(matches)
The regex syntax follows Rust's regex crate, giving you powerful pattern matching capabilities for your routes.
Route Parameters
Once a route matches, you can extract the parameter values from the context:
Getting a Single Parameter
// Returns an Option<String> — None if the parameter doesn't exist
let param: Option<String> = ctx.try_get_route_param("text");
// Returns a String directly — panics if the parameter doesn't exist
let param: String = ctx.get_route_param("text");
Use try_get_route_param when you're not sure if a parameter exists, and get_route_param when the parameter is guaranteed to be present (because the route pattern requires it).
Getting All Parameters
let params = ctx.get_route_params();
This returns all route parameters as a collection, which is useful when you have multiple dynamic segments in your route.
Route Filters
Hyperlane provides a powerful route filtering system that allows you to add conditions to your routes. Filters are implemented as attribute macros on the route struct:
Host Filter
Match requests based on the Host header:
#[host("example.com")]
This route only matches requests where the Host header is example.com.
Reject Host Filter
Block requests from specific hosts:
#[reject_host("blocked.com")]
This route rejects requests where the Host header is blocked.com.
Referer Filter
Match requests based on the Referer header:
#[referer("https://example.com")]
This route only matches requests that come from https://example.com.
Reject Referer Filter
Block requests from specific referers:
#[reject_referer("https://malicious.com")]
This route rejects requests from https://malicious.com.
Method Filter
Filter requests by HTTP method:
#[filter(ctx.get_request().get_method() == &RequestMethod::Get)]
This route only matches GET requests. You can use any expression that evaluates to a boolean.
Reject Filter
Reject requests that match a condition:
#[reject(ctx.get_request().get_path().len() > 1000)]
This route rejects requests with paths longer than 1000 characters.
Attribute Macro Routing
Instead of registering routes programmatically, you can use the #[route] attribute macro:
#[route("/test/{text}")]
struct Route;
This declarative approach is cleaner and keeps the route path close to the handler implementation. The macro handles the registration automatically.
Combining Filters
You can combine multiple filters on a single route for fine-grained control:
#[host("example.com")]
#[reject_host("blocked.com")]
#[referer("https://example.com")]
#[reject_referer("https://malicious.com")]
#[filter(ctx.get_request().get_method() == &RequestMethod::Get)]
#[reject(ctx.get_request().get_path().len() > 1000)]
This route only matches when ALL of the following conditions are met:
- The host is
example.com - The host is NOT
blocked.com - The referer is
https://example.com - The referer is NOT
https://malicious.com - The request method is GET
- The path length is not greater than 1000
Complete Routing Example
Here's a complete example that demonstrates various routing patterns:
use hyperlane::*;
use hyperlane_macros::*;
// Static route
#[route("/health")]
struct HealthRoute;
// Dynamic route with a text parameter
#[route("/users/{name}")]
struct UserRoute;
// Dynamic route with a numeric parameter (regex)
#[route("/posts/{id:\\d+}")]
struct PostRoute;
// Route with host filtering
#[route("/admin")]
#[host("admin.example.com")]
struct AdminRoute;
// Route with method filtering
#[route("/api/data")]
#[filter(ctx.get_request().get_method() == &RequestMethod::Get)]
struct ApiDataRoute;
#[tokio::main]
async fn main() {
let mut server: Server = Server::default();
let server_control_hook: ServerControlHook = server.run().await.unwrap_or_default();
server_control_hook.wait().await;
}
Route Matching Order
When multiple routes could match a request, Hyperlane uses the following priority:
- Static routes take priority over dynamic routes
- More specific routes take priority over less specific ones
- Routes with filters are evaluated after the path matches
This means that if you have both /users/{name} and /users/admin, a request to /users/admin will match the static route first.
Best Practices
Use static routes when possible: They're faster and more predictable than dynamic routes.
Use regex constraints for typed parameters: If a parameter should be a number, use
{id:\\d+}instead of{id}.Keep route paths simple: Deeply nested paths like
/api/v1/users/{id}/posts/{post_id}/comments/{comment_id}can be hard to maintain. Consider flattening your URL structure.Use filters for access control: Host and method filters are more efficient than checking these conditions in your handler.
Use the attribute macro syntax: It's more readable and maintainable than programmatic route registration.
Conclusion
Hyperlane's routing system provides a comprehensive set of tools for mapping URLs to handlers. From simple static routes to complex regex patterns with multiple filters, the system is designed to handle any routing requirement. The attribute macro syntax makes route definitions clean and declarative, while the programmatic API gives you full control when needed.
In the next article, we'll explore how to handle incoming requests, including extracting headers, query parameters, and body data.
Project Code:https://github.com/hyperlane-dev/hyperlane
Top comments (0)