Request Method Filtering in Hyperlane
Project Code:https://github.com/hyperlane-dev/hyperlane
Introduction
In web applications, it's common to handle different HTTP methods (GET, POST, PUT, DELETE, etc.) differently within the same route. Hyperlane provides powerful mechanisms for filtering requests based on their HTTP method, allowing you to route requests to specific handlers or apply middleware conditionally. This article explores the various ways to filter by HTTP method in Hyperlane.
Understanding HTTP Methods in Hyperlane
Hyperlane supports all standard HTTP methods through the RequestMethod enum. When a request arrives, the method is parsed and made available through the Context:
let method = ctx.get_request().get_method();
You can compare it against enum variants:
ctx.get_request().get_method() == &RequestMethod::Get
The #[is_get_method] Attribute Macro
For simple GET-only handlers, Hyperlane provides the #[is_get_method] attribute macro. When applied to a handler, it automatically checks if the request method is GET and rejects non-GET requests:
#[is_get_method]
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
// This handler only processes GET requests
Status::Continue
}
This macro is ideal for read-only endpoints that should never accept POST, PUT, or other mutating methods.
The #[methods] Attribute Macro
For more flexible method filtering, the #[methods] attribute macro accepts a list of allowed methods:
#[methods("GET", "POST")]
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
// This handler processes both GET and POST requests
Status::Continue
}
This is perfect for endpoints that accept multiple methods but should reject others. For example, an API endpoint that supports both reading (GET) and creating (POST) but not updating or deleting.
Manual Method Checking
For complex method-based logic, you can check the method manually in your handler:
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
let method = ctx.get_request().get_method();
if method == &RequestMethod::Get {
// Handle GET request
let data = ctx.get_mut_response()
.set_status_code(200)
.set_body("GET response")
.build();
stream.try_send(data).await;
} else if method == &RequestMethod::Post {
// Handle POST request
let body = ctx.get_request().get_body_string();
let data = ctx.get_mut_response()
.set_status_code(200)
.set_body(format!("Received: {}", body))
.build();
stream.try_send(data).await;
} else {
// Reject other methods
let data = ctx.get_mut_response()
.set_status_code(405)
.set_body("Method Not Allowed")
.build();
stream.try_send(data).await;
}
Status::Continue
}
Method-Based Route Filtering
Hyperlane's route filtering system supports method-based filtering through the #[filter] attribute:
#[filter(ctx.get_request().get_method() == &RequestMethod::Get)]
This can be combined with route definitions to create method-specific handlers:
#[route("/test/{text}")]
struct Route;
Combined with method filtering:
#[route("/api/resource")]
#[filter(ctx.get_request().get_method() == &RequestMethod::Get)]
struct GetResource;
#[route("/api/resource")]
#[filter(ctx.get_request().get_method() == &RequestMethod::Post)]
struct CreateResource;
Method Filtering in Middleware
You can also use method filtering in middleware to apply logic conditionally:
struct MethodLoggingMiddleware;
impl ServerHook for MethodLoggingMiddleware {
async fn new(_: &mut Stream, _: &mut Context) -> Self {
Self
}
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
let method = ctx.get_request().get_method();
// Log the method for debugging
ctx.set_attribute("request_method", &format!("{:?}", method));
Status::Continue
}
}
server.request_middleware::<MethodLoggingMiddleware>();
Combining Method Filters with Other Filters
Method filtering can be combined with other route filters for fine-grained control:
#[host("example.com")]
#[filter(ctx.get_request().get_method() == &RequestMethod::Get)]
#[reject(ctx.get_request().get_path().len() > 1000)]
This combination ensures the handler only processes GET requests from example.com with paths shorter than 1000 characters.
Practical Example: REST API
Here's a practical example of using method filtering to build a simple REST API:
// GET /items - List all items
#[route("/items")]
#[is_get_method]
struct ListItems;
impl ServerHook for ListItems {
async fn new(_: &mut Stream, _: &mut Context) -> Self {
Self
}
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
ctx.get_mut_response()
.set_version(HttpVersion::Http1_1)
.set_status_code(200)
.set_header(CONTENT_TYPE, APPLICATION_JSON)
.set_body(r#"["item1","item2"]"#);
let data = ctx.get_mut_response().build();
if stream.try_send(data).await.is_err() {
stream.set_closed(true);
return Status::Reject;
}
Status::Continue
}
}
// POST /items - Create a new item
#[route("/items")]
#[methods("POST")]
struct CreateItem;
impl ServerHook for CreateItem {
async fn new(_: &mut Stream, _: &mut Context) -> Self {
Self
}
#[request_body(body)]
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
// Process the request body
ctx.get_mut_response()
.set_version(HttpVersion::Http1_1)
.set_status_code(201)
.set_header(CONTENT_TYPE, APPLICATION_JSON)
.set_body(r#"{"created":true}"#);
let data = ctx.get_mut_response().build();
if stream.try_send(data).await.is_err() {
stream.set_closed(true);
return Status::Reject;
}
Status::Continue
}
}
Method Filtering with WebSocket
Method filtering is also relevant for WebSocket endpoints. WebSocket upgrades always use the GET method, so you can combine #[is_get_method] with #[is_ws_upgrade_type]:
#[route("/ws_upgrade_type")]
#[is_get_method]
struct Websocket;
impl ServerHook for Websocket {
async fn new(_: &mut Stream, _: &mut Context) -> Self {
Self
}
#[is_ws_upgrade_type]
#[try_get_websocket_request(body)]
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
let body_list = WebSocketFrame::create_frame_list(&body);
stream.send_list(body_list).await;
Status::Continue
}
}
Best Practices
-
Use
#[is_get_method]for read-only endpoints — It's the simplest and most readable way to restrict to GET. -
Use
#[methods]for multi-method endpoints — It clearly documents which methods are accepted. - Return 405 for unsupported methods — When a route accepts some methods but not others, return HTTP 405 Method Not Allowed.
-
Combine filters for complex rules — Use
#[filter]with method checks for advanced filtering logic. - Document your method requirements — Whether through attribute macros or comments, make it clear which methods each endpoint supports.
Conclusion
Hyperlane provides multiple mechanisms for filtering requests by HTTP method, from simple attribute macros like #[is_get_method] and #[methods] to manual method checking with #[filter]. These tools allow you to build clean, well-organized APIs where each handler is clearly scoped to the methods it supports. By choosing the right filtering mechanism for each use case, you can keep your code declarative and maintainable.
Project Code:https://github.com/hyperlane-dev/hyperlane
Top comments (0)