DEV Community

tengxgfyrz67s
tengxgfyrz67s

Posted on

Version-and-Protocol-Filtering

Version and Protocol Filtering in Hyperlane

Project Code:https://github.com/hyperlane-dev/hyperlane

Introduction

In modern HTTP servers, the ability to filter requests based on HTTP version, protocol type, or other request characteristics is essential for building secure and well-organized applications. The hyperlane framework provides a rich set of filtering mechanisms—from route-level attribute macros to request inspection methods—that allow you to control which requests reach which handlers. This article explores the various filtering and protocol inspection capabilities available in hyperlane.

HTTP Version Detection

Hyperlane makes it straightforward to detect which HTTP version a client is using. This is useful for implementing version-specific behavior, such as deprecation warnings or protocol-specific optimizations.

Using is_http1_1_version

The #[is_http1_1_version] attribute macro allows you to create routes that only match HTTP/1.1 requests:

#[route("/api/data")]
#[is_http1_1_version]
async fn handle_http11_request(ctx: &mut ServerContext) {
    let response = ctx.get_mut_response();
    response.set_status_code(200);
    response.set_body("This endpoint only accepts HTTP/1.1".to_string());
}
Enter fullscreen mode Exit fullscreen mode

When a client connects using a different HTTP version, this route will not match, and the request will fall through to other handlers or return a 404.

Manual Version Inspection

You can also inspect the HTTP version programmatically within your handlers:

#[route("/api/version-check")]
async fn check_version(ctx: &mut ServerContext) {
    let request = ctx.get_request();
    let response = ctx.get_mut_response();

    // Check the HTTP version and respond accordingly
    if request.is_enable_keep_alive() {
        response.set_header("Connection", "keep-alive");
    }

    response.set_status_code(200);
    response.set_body("Version check complete".to_string());
}
Enter fullscreen mode Exit fullscreen mode

WebSocket Upgrade Detection

One of the most important protocol-level filters in any HTTP server is WebSocket upgrade detection. Hyperlane provides dedicated support for this through the #[is_ws_upgrade_type] attribute and related methods.

Using is_ws_upgrade_type

The #[is_ws_upgrade_type] attribute macro creates routes that only match WebSocket upgrade requests:

#[route("/ws")]
#[is_ws_upgrade_type]
async fn handle_websocket(ctx: &mut ServerContext) {
    let response = ctx.get_mut_response();
    response.set_status_code(101);
    response.set_header("Upgrade", "websocket");
    response.set_header("Connection", "Upgrade");
}
Enter fullscreen mode Exit fullscreen mode

Manual WebSocket Detection

You can also detect WebSocket upgrades programmatically:

#[route("/ws-endpoint")]
async fn websocket_handler(ctx: &mut ServerContext) {
    let request = ctx.get_request();

    if request.is_ws_upgrade_type() {
        // Handle WebSocket upgrade
        let response = ctx.get_mut_response();
        response.set_status_code(101);
        response.set_header("Upgrade", "websocket");
        response.set_header("Connection", "Upgrade");
    } else {
        // Handle as regular HTTP
        let response = ctx.get_mut_response();
        response.set_status_code(200);
        response.set_body("Send a WebSocket upgrade request".to_string());
    }
}
Enter fullscreen mode Exit fullscreen mode

Accessing WebSocket Request Data

When a WebSocket upgrade is detected, you can extract the WebSocket request body using the #[try_get_websocket_request(body)] attribute:

#[route("/ws")]
#[is_ws_upgrade_type]
#[try_get_websocket_request(body)]
async fn handle_websocket(ctx: &mut ServerContext, body: String) {
    let response = ctx.get_mut_response();
    response.set_status_code(101);

    // Process the WebSocket frame data
    let frames = WebSocketFrame::create_frame_list(&body);
}
Enter fullscreen mode Exit fullscreen mode

HTTP Method Filtering

Hyperlane provides fine-grained control over which HTTP methods a route will accept.

Using the methods Attribute

The #[methods("GET", "POST")] attribute restricts a route to specific HTTP methods:

#[route("/api/resource")]
#[methods("GET", "POST")]
async fn handle_resource(ctx: &mut ServerContext) {
    let request = ctx.get_request();
    let method = request.get_method();
    let response = ctx.get_mut_response();

    match method.as_str() {
        "GET" => {
            response.set_status_code(200);
            response.set_body("Resource data".to_string());
        }
        "POST" => {
            response.set_status_code(201);
            response.set_body("Resource created".to_string());
        }
        _ => {
            response.set_status_code(405);
            response.set_body("Method Not Allowed".to_string());
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Using is_get_method

For the common case of GET-only routes, the #[is_get_method] attribute provides a concise shorthand:

#[route("/api/read")]
#[is_get_method]
async fn handle_get_only(ctx: &mut ServerContext) {
    let response = ctx.get_mut_response();
    response.set_status_code(200);
    response.set_body("GET request received".to_string());
}
Enter fullscreen mode Exit fullscreen mode

Host and Referer Filtering

Hyperlane supports filtering requests based on the Host header and the Referer header, which is useful for virtual hosting, anti-hotlinking, and security policies.

Host-Based Filtering

The #[host] attribute restricts a route to requests with a specific Host header:

#[route("/api/data")]
#[host("api.example.com")]
async fn handle_api_request(ctx: &mut ServerContext) {
    let response = ctx.get_mut_response();
    response.set_status_code(200);
    response.set_body("API data for api.example.com".to_string());
}
Enter fullscreen mode Exit fullscreen mode

Rejecting Specific Hosts

The #[reject_host] attribute does the opposite—it rejects requests from a specific host:

#[route("/api/data")]
#[reject_host("blocked.example.com")]
async fn handle_non_blocked(ctx: &mut ServerContext) {
    let response = ctx.get_mut_response();
    response.set_status_code(200);
    response.set_body("Welcome!".to_string());
}
Enter fullscreen mode Exit fullscreen mode

Referer-Based Filtering

The #[referer] attribute restricts a route to requests with a specific Referer header:

#[route("/images/photo.jpg")]
#[referer("https://www.example.com")]
async fn handle_referer(ctx: &mut ServerContext) {
    let response = ctx.get_mut_response();
    response.set_status_code(200);
    response.set_body("Image data".to_string());
}
Enter fullscreen mode Exit fullscreen mode

Rejecting Specific Referers

The #[reject_referer] attribute rejects requests from a specific referer:

#[route("/images/photo.jpg")]
#[reject_referer("https://www.badsite.com")]
async fn handle_non_bad_referer(ctx: &mut ServerContext) {
    let response = ctx.get_mut_response();
    response.set_status_code(200);
    response.set_body("Image data".to_string());
}
Enter fullscreen mode Exit fullscreen mode

Generic Filtering with #[filter] and #[reject]

For more complex filtering logic, Hyperlane provides the #[filter] and #[reject] attributes, which allow you to create custom filter conditions:

#[route("/api/data")]
#[filter]
async fn filtered_handler(ctx: &mut ServerContext) {
    let response = ctx.get_mut_response();
    response.set_status_code(200);
    response.set_body("Filtered request accepted".to_string());
}

#[route("/api/data")]
#[reject]
async fn rejected_handler(ctx: &mut ServerContext) {
    let response = ctx.get_mut_response();
    response.set_status_code(403);
    response.set_body("Request rejected by filter".to_string());
}
Enter fullscreen mode Exit fullscreen mode

Combining Multiple Filters

The real power of hyperlane's filtering system comes from combining multiple filters on a single route. Filters are evaluated in order, and all must pass for the route to match:

#[route("/api/secure")]
#[is_http1_1_version]
#[is_get_method]
#[host("api.example.com")]
#[referer("https://www.example.com")]
async fn highly_filtered_handler(ctx: &mut ServerContext) {
    let response = ctx.get_mut_response();
    response.set_status_code(200);
    response.set_body("Highly filtered endpoint".to_string());
}
Enter fullscreen mode Exit fullscreen mode

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

  • The request uses HTTP/1.1
  • The request method is GET
  • The Host header is api.example.com
  • The Referer header is https://www.example.com

Keep-Alive Connection Management

Hyperlane provides methods to inspect and manage Keep-Alive connections, which is closely related to protocol handling:

#[route("/api/connection")]
async fn connection_handler(ctx: &mut ServerContext) {
    let request = ctx.get_request();
    let response = ctx.get_mut_response();

    if request.is_enable_keep_alive() {
        response.set_header("Connection", "keep-alive");
    } else {
        response.set_header("Connection", "close");
    }

    response.set_status_code(200);
    response.set_body("Connection info".to_string());
}
Enter fullscreen mode Exit fullscreen mode

You can also check the Keep-Alive status on the stream:

async fn stream_handler(stream: &mut Stream) {
    if stream.is_keep_alive() {
        // Keep-Alive connection — can handle more requests
    } else {
        // Connection will close after this response
        stream.set_closed(true);
    }
}
Enter fullscreen mode Exit fullscreen mode

Practical Use Cases

Use Case 1: API Version Routing

Use host-based filtering to route different API versions:

#[route("/api/data")]
#[host("v1.api.example.com")]
async fn api_v1(ctx: &mut ServerContext) {
    let response = ctx.get_mut_response();
    response.set_status_code(200);
    response.set_body("API v1 response".to_string());
}

#[route("/api/data")]
#[host("v2.api.example.com")]
async fn api_v2(ctx: &mut ServerContext) {
    let response = ctx.get_mut_response();
    response.set_status_code(200);
    response.set_body("API v2 response".to_string());
}
Enter fullscreen mode Exit fullscreen mode

Use Case 2: WebSocket and HTTP on the Same Path

Handle both WebSocket upgrades and regular HTTP on the same endpoint:

#[route("/ws")]
#[is_ws_upgrade_type]
async fn websocket_route(ctx: &mut ServerContext) {
    let response = ctx.get_mut_response();
    response.set_status_code(101);
    response.set_header("Upgrade", "websocket");
    response.set_header("Connection", "Upgrade");
}

#[route("/ws")]
async fn ws_info(ctx: &mut ServerContext) {
    let response = ctx.get_mut_response();
    response.set_status_code(200);
    response.set_body("This endpoint supports WebSocket upgrades".to_string());
}
Enter fullscreen mode Exit fullscreen mode

Use Case 3: Anti-Hotlinking Protection

Use referer filtering to prevent other sites from embedding your resources:

#[route("/images/logo.png")]
#[referer("https://www.mysite.com")]
async fn serve_logo(ctx: &mut ServerContext) {
    let response = ctx.get_mut_response();
    response.set_status_code(200);
    response.set_body("Logo image data".to_string());
}

#[route("/images/logo.png")]
async fn reject_hotlink(ctx: &mut ServerContext) {
    let response = ctx.get_mut_response();
    response.set_status_code(403);
    response.set_body("Hotlinking not allowed".to_string());
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  1. Use specific filters over generic ones: Prefer #[is_get_method] over #[methods("GET")] when possible—it's more concise and readable.

  2. Order filters carefully: Filters are evaluated in order. Place the most restrictive or most likely to fail first for better performance.

  3. Combine filters for security: Use host and referer filtering together for sensitive endpoints.

  4. Handle WebSocket upgrades explicitly: Always check for WebSocket upgrades before processing regular HTTP on shared endpoints.

  5. Test filter combinations: Verify that your filter combinations work correctly by testing with various request configurations.

  6. Document your filtering strategy: Keep documentation about which routes have which filters to avoid confusion as your application grows.

Conclusion

Hyperlane's version and protocol filtering capabilities provide a powerful toolkit for controlling request routing and handling. From simple HTTP method filtering to complex multi-condition filters, the framework gives you the tools to build well-organized, secure, and efficient HTTP servers. By combining these filtering mechanisms with hyperlane's other features—middleware, routing, and request handling—you can create sophisticated applications that handle diverse client requirements with ease.


Project Code:https://github.com/hyperlane-dev/hyperlane

Top comments (0)