gotham-rs / gotham

A flexible web framework that promotes stability, safety, security and speed.

Home Page:https://gotham.rs

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Changing request in middleware has no effect to routing.

s3rius opened this issue · comments

I want to implement X-HTTP-Method-Override header feature like in this blog post.

The main Idea is that if your client or firewall or something doesn't allow you to send DELETE or PATCH request, you can pass the "X-HTTP-Method-Override" to override method.

So I implemented the following middleware:

use gotham::{
    handler::HandlerFuture,
    hyper::{header::HeaderValue, HeaderMap, Method},
    middleware::Middleware,
    prelude::NewMiddleware,
    state::State,
};
use std::{pin::Pin, str::FromStr};

#[derive(Clone, NewMiddleware)]
pub struct MethodOverrideMiddleware;

impl Middleware for MethodOverrideMiddleware {
    fn call<Chain>(self, mut state: State, chain: Chain) -> Pin<Box<HandlerFuture>>
    where
        Chain: FnOnce(State) -> Pin<Box<HandlerFuture>>,
    {
        // Here we're trying to get http-method-override.
        let new_method: Option<&HeaderValue> = {
            let headers = state.borrow::<HeaderMap>();
            headers.get("X-HTTP-Method-Override")
        };

        // If header was passed.
        if let Some(method) = new_method {
            // If header value is a correct string.
            if let Ok(method_str) = method.to_str() {
                if let Ok(method_obj) = Method::from_str(method_str.to_uppercase().as_str()) {
                    // Actually override request's method.
                    let old_method = state.borrow_mut::<Method>();
                    *old_method = method_obj;
                }
            }
        }
        // Calling other middlewares in current chain.
        chain(state)
    }
}

And after some tests I found out that even if the state was overridden handler will be the same.
For example:

use gotham::{
    helpers::http::response::create_response,
    hyper::{Body, Response, StatusCode},
    mime,
    pipeline::{new_pipeline, single_pipeline},
    prelude::*,
    router::{builder::*, Router},
    state::State,
};
use middlewares::MethodOverrideMiddleware;

mod middlewares;

pub fn get_req(state: State) -> (State, Response<Body>) {
    let resp = create_response(
        &state,
        StatusCode::OK,
        mime::TEXT_PLAIN,
        String::from("GET REQUEST"),
    );

    (state, resp)
}

pub fn post_req(state: State) -> (State, Response<Body>) {
    let resp = create_response(
        &state,
        StatusCode::OK,
        mime::TEXT_PLAIN,
        String::from("POST REQUEST"),
    );

    (state, resp)
}

fn construct_router() -> Router {
    let (chain, pipelines) = single_pipeline(new_pipeline().add(MethodOverrideMiddleware).build());
    build_router(chain, pipelines, |router| {
        router.get("/handler").to(get_req);
        router.post("/handler").to(post_req);
    })
}

fn main() -> Result<(), gotham::StartError> {
    gotham::start("0.0.0.0:8080", construct_router())
}
❯ curl -XGET "http://localhost:8080/handler" -H "X-HTTP-Method-Override: post"
GET REQUEST
❯ curl -XPOST "http://localhost:8080/handler" -H "X-HTTP-Method-Override: GET"
POST REQUEST
❯ curl -XDELETE "http://localhost:8080/handler" -H "X-HTTP-Method-Override: GET"
# Here i got 405 - Method not allowed

I expect this to work like this:

❯ curl -XGET "http://localhost:8080/handler" -H "X-HTTP-Method-Override: post"
POST REQUEST
❯ curl -XPOST "http://localhost:8080/handler" -H "X-HTTP-Method-Override: GET"
GET REQUEST
❯ curl -XDELETE "http://localhost:8080/handler" -H "X-HTTP-Method-Override: GET"
GET REQUEST

Maybe I'm doing something wrong? Can you please help?

commented

Middleware in gotham runs after the router. So at the time your middleware modifies the state, the router has already chosen which handler to call. In fact, the middleware even receives the handler (plus possibly other middleware that hasn't run yet) as part of the chain argument.

Thanks for your answer. But how can I achieve desired result with gotham? Because this framework is really cool and I want to use it really bad. Maybe it's possible to change the way how handler is chosen?

commented

If using a reverse proxy that handles the method overrides for you is not an option, you could try to register one handler for all methods (https://docs.rs/gotham/latest/gotham/prelude/trait.DrawRoutes.html#method.request) and implement a "wrapper" which you use as your handler and which then delegates to your real handlers after reading the header values.

Thanks for answer.