0

I have some actix_web stuff wrapped in a module and pulled in by my REST api functions, except for too much kick-starting boilerplate:

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new()
            .wrap(Logger::default())
            .service((api1, api2, api3)))
        .bind(("127.0.0.1", 8081))?
        .run()
        .await
}

I'd like to get the low-level parts out of sight and have only the business logic stuff (bind-address & rest-apis) visible. I.e. simplify main() to one of:

    web::init((api1, api2, api3))
        .bind(("127.0.0.1", 8081))?
        .run()
        .await

    web::init(|app| app.service((api1, api2, api3)))
        .bind(("127.0.0.1", 8081))?
        .run()
        .await

    web::run(("127.0.0.1", 8081), (api1, api2, api3))?

But just refactoring this into a separate function changes everything. Whatever I try, I always stumble across many suddenly needed generic parameters, moving the tuple into the closure is suddenly considered to cross thread boundaries, etc. How to write such an init or run function?

Daniel
  • 521
  • 1
  • 4
  • 13
  • I don't understand what you want – Stargateur Apr 28 '23 at 19:11
  • 1
    If you want to encapsulate calls like `.app_data()`, `.wrap()`, and `.service()` in a separate function. You should use `.configure()` as demonstrated in [this answer](https://stackoverflow.com/questions/57457202/how-can-i-return-a-configured-app-in-actix-web). I would not recommend trying to return a `App` or `HttpServer`. – kmdreko Apr 28 '23 at 19:16
  • @Stargateur How to write such an init or run function? One that can be called as in my 2nd box (or similar.) – Daniel Apr 28 '23 at 19:17
  • @Daniel My link is correct (not sure where you see anything logging related). Using `.configure()` is the way Actix-web has designed its APIs, which puts the business logic in a separate function. The opposite starts to get messy. The best I'd suggest (using your third desired syntax) would be [this](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c93b0545cd29a28957244df96b492fb3). – kmdreko Apr 28 '23 at 19:38
  • Or I guess you can build around how `.configure()` works and make a function based on that like [this](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=200009abedf8a310cdc9f1880d45f26e). – kmdreko Apr 28 '23 at 19:49
  • @kmdreko Sorry about the wrong url, my bad. Your code-snippet rocks, this is wonderful, thank you! Actually the closure can be just `|| (api1, api2, api3)`. If you provide it as an answer, I'll accept it. – Daniel Apr 28 '23 at 20:05

1 Answers1

0

In case the playground by @kmdreko ever disappears, here's what I turned it into. Not sure why the where clause has to be split into two generics, but it only works like that.

/// If we ever need more services than it can take from a tuple, we need to add services2... and .service(services2())...
pub async fn run<F, S>(host: &str, port: u16, services: F) -> std::io::Result<()>
where
    F: Fn() -> S + Clone + Send + 'static,
    S: HttpServiceFactory + 'static,
{
    HttpServer::new(move || {
        App::new()
            .wrap(Logger::new("%bb %Ts %s %r").log_target("http"))
            .service(services())
    })
    .bind((host, port))?
    .run()
    .await
}

For some reason cargo +nightly fmt (I compile with stable, but use latest formatter) wraps the caller's tuple into braces (which it doesn't do for other single-expression closures.)

run("127.0.0.1", 8081, || (api1, api2, api3)).await
Daniel
  • 521
  • 1
  • 4
  • 13