Calling async reqwest from with actix-web - rust

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:
println!("{:?}", json); never appears to run, or at least I never see any output.
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
}

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

Related

Pass redis connection object to actix web get route in rust

I am using redis-rs library to read json from RedisJSON. The program works fine when i open and create connection inside read_db_demo function. But that is not an ideal way. So i opened and created the connection inside main function. Now how should i pass the connection variable to read_db_demo function. Until now, i tried adding
App::new()
.app_data(web::Data::new(connection.clone()))
.route("/", web::get().to(read_db_demo))
})
which didn't work.
My code -
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
use redis::Client;
use redis::JsonCommands;
use redis::RedisResult;
use serde_json::Value;
const TEST_KEY: &str = "results";
#[get("/")]
async fn read_db_demo() -> impl Responder {
let json_response: RedisResult<String> = connection.json_get(TEST_KEY, "$");
match json_response {
Ok(json_string) => {
let json: Value = serde_json::from_str(&json_string).unwrap();
HttpResponse::Ok().json(json)
}
Err(_) => HttpResponse::InternalServerError().body("Error reading from Redis"),
}
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let client = Client::open("redis://:xx").unwrap();
let mut connection = client.get_connection().unwrap(); // how to pass this connection to read_db_demo
HttpServer::new(|| {
App::new()
.service(read_db_demo)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}

keep the output from a post request inside a string variable

Hi I explain the problem
I'm actually sending a request to the OpenApi and the reply is an url that is basically an image generated by OpenAI.
I would like to keep this url inside a variable after sending a request rather than get this url in the terminal with the println!("{:?}", res);
I need to use this url in an other file after. it's why it's more interesting for me to keep this url in a string variable after the request.
use exitfailure::ExitFailure;
use reqwest::{
header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE},
Url,
};
use serde_derive::{Deserialize, Serialize};
use std::collections::hash_map::*;
// use std::env;
#[derive(Serialize, Deserialize, Debug)]
struct GenerationImage {}
#[derive(Serialize, Deserialize, Debug)]
struct Data {
url: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct Response {
data: [Data; 1],
}
impl GenerationImage {
async fn post(api_key: &str) -> Result<(), ExitFailure> {
let key = format!("Bearer {}", api_key);
let prompt = "a cat behind a tree".to_string();
let url = "https://api.openai.com/v1/images/generations";
let mut map = HashMap::new();
map.insert("prompt", prompt);
let endpoint = Url::parse(url)?;
let client = reqwest::Client::new();
let res = client
.post(endpoint)
.header(AUTHORIZATION, key)
.header(CONTENT_TYPE, "application/json")
.header(ACCEPT, "application/json")
.json(&map)
.send()
.await?
.json::<Response>()
.await;
println!("{:?}", res);
Ok(())
}
}
fn main() {
let api_key = "API";
GenerationImage::post(&api_key);
}
To convert an item that implements Debug into the corresponding String you can do this:
let s = format!("{:?}", res);

How to use routes attributes macros for multiple methods in Actix-Web

In the Actix Web Framework, how does one use the route attributes macros (#[http_method("route")]) to bind multiple http methods to one function?
For example, I have this trivial endpoint:
/// Returns a UUID4.
#[get("/uuid")]
async fn uuid_v4() -> impl Responder {
HttpResponse::Ok().json(Uuid {
uuid: uuid::Uuid::new_v4(),
})
}
I would like to have the same endpoint handle HEAD requests, how do I do this?
My initial approach was to just stack up the macros:
/// Returns a UUID4.
#[get("/uuid")]
#[head("/uuid")]
async fn uuid_v4() -> impl Responder {
HttpResponse::Ok().json(Uuid {
uuid: uuid::Uuid::new_v4(),
})
}
But I do get a compilation error:
|
249 | async fn uuid_v4() -> impl Responder {
| ^^^^^^^ the trait `actix_web::handler::Factory<_, _, _>` is not implemented for `<uuid_v4 as actix_web::service::HttpServiceFactory>::register::uuid_v4`
I have gone through the actix-web and actix-web-codegen docs and didn't find anything addressing this
you can do
#[route("/", method="GET", method="POST", method="PUT")]
async fn index() -> impl Responder {
HttpResponse::Ok().body("Hello world!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
App::new()
.service(index)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
An example with multiple path and multiple methods for one resource
async fn index() -> impl Responder {
HttpResponse::Ok().body("Hello world!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
App::new()
.service(
actix_web::web::resource(vec!["/", "/index"])
.route(actix_web::web::get().to(index))
.route(actix_web::web::post().to(index))
)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
I assume you are using actix-web: 2.0.0 with actix-rt: 1.0.0 and this handler you are passing to App.service method like below
HttpServer::new(move || {
App::new()
.wrap(middleware::Logger::default())
.service(index)
})
.bind(("127.0.0.1", self.port))?
.workers(8)
.run()
.await
then you will need to write handler like this ->
/// Returns a UUID4.
#[get("/uuid")]
async fn uuid_v4(req: HttpRequest) -> Result<web::Json<IndexResponse>> {
let uuid_header = req
.headers()
.get("uuid")
.and_then(|v| v.to_str().ok())
.unwrap_or_else(|| "some-id");
//curl -H "uuid: username" localhost:8080
println!("make use of {}", uuid_header);
Ok(web::Json(Uuid {
uuid: uuid::Uuid::new_v4(),
}))
}

How can I fix "AsyncFactory<_, _> is not implemented" error in Actix-Web?

This is my code. I'm trying to get posts from the Rust subreddit. I would be using Actix's built in client, but it's not working on Windows, hence the use of reqwest instead:
[dependencies]
actix-web = "1.0.8"
futures = "0.1.29"
reqwest = "0.9.21"
use actix_web::{self, middleware, web, App, HttpRequest, HttpServer};
use futures::future::Future;
use reqwest::{self, r#async::Client};
fn get_rust_posts(
_req: HttpRequest,
client: web::Data<Client>,
) -> impl Future<Item = String, Error = reqwest::Error> {
client
.get("http://www.reddit.com/r/rust.json")
.send()
.and_then(|mut resp| resp.text())
.map_err(|err| {
println!("Error in get rust posts: {}", err);
err
})
}
fn main() {
let mut server = HttpServer::new(|| {
App::new()
.data(Client::new())
.wrap(middleware::Logger::default())
.service(web::resource("/get/rust/posts").route(web::get().to_async(get_rust_posts)))
});
server.bind(("0.0.0.0", 8000)).unwrap().run().unwrap();
}
This is the error:
error[E0277]: the trait bound `fn(actix_web::request::HttpRequest, actix_web::data::Data<reqwest::async_impl::client::Client>) -> impl futures::future::Future
{get_rust_posts}: actix_web::handler::AsyncFactory<_, _>` is not satisfied
--> src\main.rs:29:72
|
29 | .service(web::resource("/get/rust/posts").route(web::get().to_async(get_rust_posts)))
| ^^^^^^^^ the trait `actix_web::handler::AsyncFactory<_, _>` is not implemented for
`fn(actix_web::request::HttpRequest, actix_web::data::Data<reqwest::async_impl::client::Client>) -> impl futures::future::Future {get_rust_posts}
to_async requires a handler function that implements the trait AsyncFactory. Changing the Future's Item to actix_web::HttpResponse and putting the reqwest request inside actix_web::web::block satisfies that constraint.
I also tested this with slow responses, and despite the word "block" in actix_web::web::block, it seemed to handle concurrent requests, and I think that's because the handler itself is async.
use actix_web::{middleware, web, App, HttpResponse, HttpServer};
use futures::Future;
use reqwest;
use reqwest::Client;
static REDDIT: &str = "http://www.reddit.com/r/rust.json";
fn get_rust_posts(
_req: actix_web::HttpRequest,
client: web::Data<Client>,
) -> impl Future<Item = HttpResponse, Error = actix_web::Error> {
let builder = client.get(REDDIT);
actix_web::web::block(move || builder.send())
.from_err()
.and_then(|mut res| match res.text() {
Ok(body) => HttpResponse::Ok()
.content_type("application/json")
.body(body),
Err(error) => {
println!("get_request error: {}", error);
HttpResponse::InternalServerError()
.content_type("application/json")
.body(format!("{{\"error\": \"Error getting response text.\"}}"))
}
})
}
fn main() {
HttpServer::new(|| {
App::new()
.data(Client::new())
.wrap(middleware::Logger::default())
.service(web::resource("/get/rust/posts").route(web::get().to_async(get_rust_posts)))
})
.bind("127.0.0.1:5000")
.unwrap()
.run()
.unwrap();
}

How do I pass App data to service route handler function in actix-web when using function decorations?

I found in the docs an example of how to create global state, protected by Mutex, shared among processing threads that is made available to all your route handlers. Perfect! However, I prefer to use attributes attached to my functions to wire up my route handlers. I do not know the syntax (if permitted) to use attributed functions and also pass in the global state.
Here is the example from the actix-web docs, from https://docs.rs/actix-web/1.0.2/actix_web/web/struct.Data.html
use std::sync::Mutex;
use actix_web::{web, App};
struct MyData {
counter: usize,
}
/// Use `Data<T>` extractor to access data in handler.
fn index(data: web::Data<Mutex<MyData>>) {
let mut data = data.lock().unwrap();
data.counter += 1;
}
fn main() {
let data = web::Data::new(Mutex::new(MyData{ counter: 0 }));
let app = App::new()
// Store `MyData` in application storage.
.register_data(data.clone())
.service(
web::resource("/index.html").route(
web::get().to(index)));
}
Notice how the route handler named index is being passed the web::Data.
Now here are some snippets of my code.
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
pub mod request;
pub mod routes;
const SERVICE_NAME : &str = "Shy Rules Engine";
const SERVICE_VERSION : &str = "0.1";
#[get("/")]
fn index() -> impl Responder {
HttpResponse::Ok().body(format!("{} version {}", SERVICE_NAME, SERVICE_VERSION))
}
mod expression_execute {
#[post("/expression/execute")]
fn route(req: web::Json<ExpressionExecuteRequest>) -> HttpResponse {
// ... lots of code omitted ...
if response.has_error() {
HttpResponse::Ok().json(response)
}
else {
HttpResponse::BadRequest().json(response)
}
}
}
pub fn shy_service(ip : &str, port : &str) {
HttpServer::new(|| {
App::new()
.service(index)
.service(expression_execute::route)
})
.bind(format!("{}:{}", ip, port))
.unwrap()
.run()
.unwrap();
}
Notice how I am calling method App::service to wire up my route handlers.
Also notice how my route handler does not receive global state (because I have not yet added it to my app). If I used a similar pattern as the docs using register_data to create global App data, what changes do I make to my method signature, the get and post attributes and anything else so that I can pass that global state to the handler?
Or is it not possible using get and post attributes to gain access to global state?
The two cases you listed really don't have much difference:
//# actix-web = "1.0.8"
use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
use std::sync::Mutex;
const SERVICE_NAME : &str = "Shy Rules Engine";
const SERVICE_VERSION : &str = "0.1";
struct MyData {
counter: usize,
}
#[get("/")]
fn index(data: web::Data<Mutex<MyData>>) -> impl Responder {
let mut data = data.lock().unwrap();
data.counter += 1;
println!("Endpoint visited: {}", data.counter);
HttpResponse::Ok().body(format!("{} version {}", SERVICE_NAME, SERVICE_VERSION))
}
pub fn shy_service(ip : &str, port : &str) {
let data = web::Data::new(Mutex::new(MyData{ counter: 0 }));
HttpServer::new(move || {
App::new()
.register_data(data.clone())
.service(index)
})
.bind(format!("{}:{}", ip, port))
.unwrap()
.run()
.unwrap();
}
fn main() {
shy_service("127.0.0.1", "8080");
}
You can verify that it works by simply curl the http endpoint. For multiple extractors, you'll have to use tuple:
#[post("/expression/execute")]
fn route((req, data): (web::Json<ExpressionExecuteRequest>, web::Data<Mutex<MyData>>)) -> HttpResponse {
unimplemented!()
}

Resources