tower-web icon indicating copy to clipboard operation
tower-web copied to clipboard

What is the recommended way of conditionally adding middleware?

Open shepmaster opened this issue 7 years ago • 6 comments

I'd like to write this code:

    let builder = ServiceBuilder::new()
        .resource(Index::new(config.root.clone()))
        .resource(Assets::new(config.root))
        .resource(SandboxFixme)
        .resource(Meta::default())
        .resource(Gist::new(config.gh_token));

    if config.cors_enabled {
        let cors = CorsBuilder::new()
            .allow_origins(AllowedOrigins::Any { allow_null: true })
            .allow_headers(vec![header::CONTENT_TYPE])
            .allow_methods(vec![Method::GET, Method::POST])
            .allow_credentials(false)
            .max_age(Duration::from_secs(ONE_HOUR_IN_SECONDS as u64))
            .prefer_wildcard(true)
            .build();

        builder = builder.middleware(cors);
    }

But it results in

error[E0308]: mismatched types
   --> src/tower_web_server.rs:373:19
    |
373 |         builder = builder.middleware(cors);
    |                   ^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `tower_web::middleware::Identity`, found struct `tower_web::middleware::Chain`
    |
    = note: expected type `tower_web::ServiceBuilder<_, _, tower_web::middleware::Identity>`
               found type `tower_web::ServiceBuilder<_, _, tower_web::middleware::Chain<tower_web::middleware::Identity, tower_web::middleware::cors::CorsMiddleware>>`

I see a few avenues:

  1. Make each middleware have an internal on/off switch. For CORS, this is "easy" as the restrictive settings should be as good as off. Every middleware would have to remember to add this, all would probably be named differently, it's not as "blatantly correct" as never inserting it, etc.

  2. Implement Middleware for Box<Middleware> and add some type erasure.

  3. Implement Middleware for Either<L, R> and then do Either<Cors, Identity>

shepmaster avatar Aug 22 '18 13:08 shepmaster

I think the best route forward is going to be something like 1. (passing in a type that conditionally enables or disables the middleware).

The problem is that the code you wrote feels natural and the fact that it does not work is frustrating.

carllerche avatar Aug 24 '18 16:08 carllerche

passing in a type that conditionally enables or disables the middleware

Heh, this makes me think I should add MaybeMiddleware(M) so that all middleware can use the same implementation. Essentially a newtype on 3...

shepmaster avatar Aug 24 '18 16:08 shepmaster

passing in a type that conditionally enables or disables the middleware

Actually, why do you think this is the best path? Doesn't doing this force the decision to be a runtime switch instead of potentially optimizing to a compile-time switch?

shepmaster avatar Aug 24 '18 20:08 shepmaster

What would the compile time switch look like?

carllerche avatar Aug 24 '18 20:08 carllerche

What would the compile time switch look like?

I guess I was thinking something like

#[cfg(feature = "cors")]
fn enable_cors() -> impl Middleware { ... } // cors

#[cfg(not(feature = "cors"))]
fn enable_cors() -> impl Middleware { ... } // identity

Which I suppose is still possible...

shepmaster avatar Aug 24 '18 20:08 shepmaster

That can work.

I have the impression that it will be common to want to enable / disable middleware based on a config file or whatever, so there should be some good API for that.

carllerche avatar Aug 24 '18 21:08 carllerche