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());
}
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());
}
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");
}
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());
}
}
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);
}
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());
}
}
}
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());
}
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());
}
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());
}
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());
}
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());
}
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());
}
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());
}
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());
}
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);
}
}
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());
}
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());
}
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());
}
Best Practices
Use specific filters over generic ones: Prefer
#[is_get_method]over#[methods("GET")]when possible—it's more concise and readable.Order filters carefully: Filters are evaluated in order. Place the most restrictive or most likely to fail first for better performance.
Combine filters for security: Use host and referer filtering together for sensitive endpoints.
Handle WebSocket upgrades explicitly: Always check for WebSocket upgrades before processing regular HTTP on shared endpoints.
Test filter combinations: Verify that your filter combinations work correctly by testing with various request configurations.
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)