3

I'm trying to write an HTTP endpoint using actix-web 1.0. I've reduced the function so that it's just returning the user that is passed to it, but the compiler still gives an error.

extern crate actix_web;
extern crate chrono;
extern crate futures;
extern crate listenfd;
#[macro_use]
extern crate serde_derive;
extern crate dotenv;
use actix_web::{error, web, App, Error, HttpResponse, HttpServer};
use futures::future::Future;

#[derive(Debug, Deserialize, Serialize)]
pub struct LoginUser {
    pub username: String,
    pub password: String,
}

pub fn login(
    login_user: web::Json<LoginUser>,
) -> impl Future<Item = HttpResponse, Error = error::BlockingError<Error>> {
    web::block(move || {
        let login_user = login_user.into_inner();
        let user = LoginUser {
            username: login_user.username,
            password: login_user.password,
        };
        Ok(HttpResponse::Ok().json(user))
    })
}

pub fn router(cfg: &mut web::ServiceConfig) {
    cfg.service(web::scope("/").service(web::resource("").route(web::get().to(login))));
}

fn main() -> std::io::Result<()> {
    HttpServer::new(move || App::new().configure(router))
        .bind("127.0.0.1:3000")?
        .run()
}

Here is my cargo.toml.

[package]
name = "log"
version = "0.1.0"
authors = ["me@example.com"
edition = "2018"

[dependencies]
actix-files = "~0.1"
actix-web = "~1.0"
chrono = { version = "0.4.6", features = ["serde"] }
listenfd = "0.3"
diesel = {version = "1.4.1", features = ["postgres", "uuid", "r2d2", "chrono"]}
dotenv = "0.13"
failure = "0.1"
futures = "0.1"
scrypt = "0.2.0"
serde_derive="1.0"
serde_json="1.0"
serde="1.0"

I'm getting the compilation error

|     web::block(move || {
|     ^^^^^^^^^^ `(dyn std::any::Any + 'static)` cannot be sent between threads safely

I think it has something to do with the use of login_user in the web::block, but it's hard to tell from the error. What is the preferred way to use request parameters asynchronously in Rust or actix safely?

CallMeNorm
  • 2,299
  • 1
  • 16
  • 23
  • Cool, I'll get it down to a small playground. – CallMeNorm Jun 19 '19 at 18:28
  • 1
    It's possible that the playground *doesn't* have the crates you need (pretty sure this is true for Actix) it which case you should do the same techniques to make a MCVE locally and then post your `main.rs` file. Thanks! – Shepmaster Jun 19 '19 at 18:32

1 Answers1

7

Well, first, HttpResponse does not implement Send. Since web::block() runs the closure on a threadpool, that is a problem. So you need to return a value that is Send from web::block, and then create a HttpResponse from that - using and_then() for example.

Second, in your router you are using web::get().to(login). If you want to call a function that returns a Future, that needs to be web::get().to_async(login).

Third, the closure in web::block needs to return Result. Since you never return an error value, the compiler cannot infer the error type. You need to give the compiler a hint. Usually std::io::Error will do,so return Ok::<_, std::io::Error>(...value...).

Fourth, web::block returns a BlockingError<E>. You can use from_err() to map that to something that you can return.

So, with all that, the relevant part of your code would look like:

pub fn login(
    login_user: web::Json<LoginUser>,
) -> impl Future<Item = HttpResponse, Error = Error> {
    web::block(move || {
        let login_user = login_user.into_inner();
        let user = LoginUser {
            username: login_user.username,
            password: login_user.password,
        };
        Ok::<_, std::io::Error>(user)
    })
        .from_err()
        .and_then(|user| HttpResponse::Ok().json(user))
}

pub fn router(cfg: &mut web::ServiceConfig) {
    cfg.service(web::scope("/").service(web::resource("").route(web::get().to_async(login))));
}
miquels
  • 415
  • 3
  • 4
  • That occured to me as well, but CallMeNorm said "I've reduced the function so that it's just returning the user that is passed to it". So I assume that in the original code there is some actual blocking code. – miquels Jun 25 '19 at 11:39
  • @miquels thanks for your answer. How did you get from the error to get to your answer. Part of the problem for me was that the error 'std::any::Any' cannot be sent between threads didn't leave me much to search for. – CallMeNorm Jun 25 '19 at 14:04
  • 1
    Ah yes. The rust compiler is usually very helpful with error messages, but in some cases - especially with futures - they can be quite convoluted. I know from previous experience that actix-web has a slightly different design than plain tokio. It load-balances requests over multiple threads, but then a request is executed on the one thread it was assigned to - and several actix types are not `Send`. They don't have to be. – miquels Jun 25 '19 at 23:17