3
votes

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 = ["[email protected]"
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?

1
Cool, I'll get it down to a small playground.CallMeNorm
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

1 Answers

7
votes

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))));
}