DEV Community

tengxgfyrz67s
tengxgfyrz67s

Posted on

Request-Method-Filtering

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

You can compare it against enum variants:

ctx.get_request().get_method() == &RequestMethod::Get
Enter fullscreen mode Exit fullscreen mode

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

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

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

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

This can be combined with route definitions to create method-specific handlers:

#[route("/test/{text}")]
struct Route;
Enter fullscreen mode Exit fullscreen mode

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

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

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

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

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

Best Practices

  1. Use #[is_get_method] for read-only endpoints — It's the simplest and most readable way to restrict to GET.
  2. Use #[methods] for multi-method endpoints — It clearly documents which methods are accepted.
  3. Return 405 for unsupported methods — When a route accepts some methods but not others, return HTTP 405 Method Not Allowed.
  4. Combine filters for complex rules — Use #[filter] with method checks for advanced filtering logic.
  5. 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)