2
votes

In my actix-web-server, I'm trying to use reqwest to call an external server, and then return the response back to the user.

use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use serde::{Deserialize, Serialize};
use futures::Future;
use lazy_static::lazy_static;
use reqwest::r#async::Client as HttpClient;
#[macro_use] extern crate serde_json;

#[derive(Debug, Deserialize)]
struct FormData {
    title: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct Response {
    title: String,
}

fn main() {
    HttpServer::new(|| {
        App::new()
            .route("/validate", web::post().to(validator))
    })
    .bind("127.0.0.1:8000")
    .expect("Can not bind to port 8000")
    .run()
    .unwrap();
}

fn validator(form: web::Form<FormData>) -> impl Responder {
    let _resp = validate(form.title.clone());
    HttpResponse::Ok()
}

pub fn validate(title: String) -> impl Future<Item=String, Error=String> {
    let url = "https://jsonplaceholder.typicode.com/posts";
    lazy_static! {
        static ref HTTP_CLIENT: HttpClient = HttpClient::new();
    }
    HTTP_CLIENT.post(url)
        .json(
            &json!({
                "title": title,
            })
        )
        .send()
        .and_then(|mut resp| resp.json())
        .map(|json: Response| {
            println!("{:?}", json);
            json.title
        })
        .map_err(|error| format!("Error: {:?}", error))
}

This has two issues:

  1. println!("{:?}", json); never appears to run, or at least I never see any output.
  2. I get _resp back, which is a Future, and I don't understand how I can wait for that to resolve so I can pass a string back to the Responder

For reference:

$ curl -data "title=x" "https://jsonplaceholder.typicode.com/posts"
{
  "title": "x",
  "id": 101
}
1

1 Answers

1
votes

To make the future block until it is resolved you have to call wait on it, but that's not ideal.

You can make your validator function return a future and in the route call to_async instead of to. The framework will poll and send the response when the future is resolved.

Also you should consider using the http client that comes with actix web and reduce one dependency from your application.

fn main() {
    HttpServer::new(|| {
        App::new()
            .route("/validate", web::post().to_async(validator))
    })
    .bind("127.0.0.1:8000")
    .expect("Can not bind to port 8000")
    .run()
    .unwrap();
}

fn validator(form: web::Form<FormData>) -> impl Future<Item=String, Error=String> {
    let url = "https://jsonplaceholder.typicode.com/posts";
    lazy_static! {
        static ref HTTP_CLIENT: HttpClient = HttpClient::new();
    }
    HTTP_CLIENT.post(url)
        .json(
            &json!({
                "title": form.title.clone(),
            })
        )
        .send()
        .and_then(|mut resp| resp.json())
        .map(|json: Response| {
            println!("{:?}", json);
            HttpResponse::Ok().body(Body::from(json.title))
        })
        .map_err(|error| format!("Error: {:?}", error))
}