DEV Community

tengxgfyrz67s
tengxgfyrz67s

Posted on

Routing Basics

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");
Enter fullscreen mode Exit fullscreen mode

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}");
Enter fullscreen mode Exit fullscreen mode

In this example, {text} is a dynamic parameter that matches any single path segment. For example:

  • /test/hellotext = "hello"
  • /test/worldtext = "world"
  • /test/123text = "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+}");
Enter fullscreen mode Exit fullscreen mode

In this example, {number:\\d+} only matches one or more digits. For example:

  • /test/123number = "123" (matches)
  • /test/abc — does not match (contains non-digit characters)
  • /test/42number = "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");
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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")]
Enter fullscreen mode Exit fullscreen mode

This route only matches requests where the Host header is example.com.

Reject Host Filter

Block requests from specific hosts:

#[reject_host("blocked.com")]
Enter fullscreen mode Exit fullscreen mode

This route rejects requests where the Host header is blocked.com.

Referer Filter

Match requests based on the Referer header:

#[referer("https://example.com")]
Enter fullscreen mode Exit fullscreen mode

This route only matches requests that come from https://example.com.

Reject Referer Filter

Block requests from specific referers:

#[reject_referer("https://malicious.com")]
Enter fullscreen mode Exit fullscreen mode

This route rejects requests from https://malicious.com.

Method Filter

Filter requests by HTTP method:

#[filter(ctx.get_request().get_method() == &RequestMethod::Get)]
Enter fullscreen mode Exit fullscreen mode

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)]
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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)]
Enter fullscreen mode Exit fullscreen mode

This route only matches when ALL of the following conditions are met:

  1. The host is example.com
  2. The host is NOT blocked.com
  3. The referer is https://example.com
  4. The referer is NOT https://malicious.com
  5. The request method is GET
  6. 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;
}
Enter fullscreen mode Exit fullscreen mode

Route Matching Order

When multiple routes could match a request, Hyperlane uses the following priority:

  1. Static routes take priority over dynamic routes
  2. More specific routes take priority over less specific ones
  3. 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

  1. Use static routes when possible: They're faster and more predictable than dynamic routes.

  2. Use regex constraints for typed parameters: If a parameter should be a number, use {id:\\d+} instead of {id}.

  3. 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.

  4. Use filters for access control: Host and method filters are more efficient than checking these conditions in your handler.

  5. 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)