Currently my main function, where the server starts looks like this
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
let address = "0.0.0.0:3000";
HttpServer::new(move || {
let app_state = {...some state};
App::new()
.data(app_state)
.wrap(middleware::Logger::default())
.service(network_api::init_service())
})
.bind(address)?
.run()
.await
}
Once the server starts, I want to run one (async) function,
to make a request to another server, and that server should respond with another request to this server
that just got up and running.
Not sure I see in the documentation anything mentioning a callback function that is run only once on server start.
For example maybe having it in run() function, like this:
run(|| {
// server started now perform some request or whatever
Client::new().post(&url).json(json_stuff).send().await
})
Edit
I think I solved it, will post answer when I can answer my own question
I have solved this by joining 2 async functions,
created another async function
async fn another_func() -> Result<()> {
...
}
and have used future::join() like this
let server = HttpServer::new(move || {
let app_state = {...some state};
App::new()
.data(app_state)
.wrap(middleware::Logger::default())
.service(network_api::init_service())
})
.bind(address)?
.run();
future::join(server, another_func()).await;
Ok(()
Of course if anyone has a better answer please post it
I actually struggled with it a bit and eventually replaced the future::join with tokio join! macro as follows:
tokio::join!(server, utils::open_web_app(server_address));
Ok(())
Related
I am trying to figure out a way to make my tokio_postgres client a variable that I can reuse in different parts of my app. Ideally, I'm trying to achieve something similar to the Prisma ORM in the Node world:
const prisma = new PrismaClient()
...
const user = await prisma.user.create({
data: {
name: 'Alice',
email: 'alice#prisma.io',
},
The code I have so far is:
async fn connect() -> Result<P::Client, PgError> {
// Connect to the database.
let (client, connection) =
tokio_postgres::connect("host=localhost user=postgres", NoTls).await?;
// The connection object performs the actual communication with the database,
// so spawn it off to run on its own.
tokio::spawn(async move {
if let Err(e) = connection.await {
eprintln!("connection error: {}", e);
}
});
// Now we can execute a simple statement that just returns its parameter.
let rows = client
.query("SELECT $1::TEXT", &[&"hello world"])
.await?;
// And then check that we got back the same string we sent over.
let value: &str = rows[0].get(0);
assert_eq!(value, "hello world");
return client;
}
However, I am getting the error:
expected type Result<tokio_postgres::Client, tokio_postgres::Error>
found struct tokio_postgres::Client
Any idea what I could be doing wrong here? I'm new to Rust and maybe I'm just bringing baggage from Node, but I haven't found any documentation on this and figured it would be good to have.
I am using actix-web.
I would like to restrict the size of post request and have the web application capture this error log it and send back an informative error message to the user.
My code is given below.
It seems the middleware gets run whether or not the limit is exceeded and HTTP code 413 raised.
The accept_post is executed only when the limit has not been exceeded.
Is there a way of capturing this error in the middleware?
.service(
web::scope("/hello_someone4")
.service(
web::resource("/there/*")
.app_data(web::PayloadConfig::default().limit(16))
.wrap(simple_middleware::simple_middleware_say_hi::SayHi)
.route(web::post().to(request_handlers2::accept_post))
)
)
The actix_web::middleware::errhandlers middleware provides the solutions I was seeking.
The example code below as obtained from "https://actix.rs/docs/middleware/".
I can now add code to the render_413 function (shown below) to perform the logging.
use actix_web::middleware::errhandlers::{ErrorHandlerResponse, ErrorHandlers};
use actix_web::{dev, http, HttpResponse, Result};
fn render_500<B>(mut res: actix_web::dev::ServiceResponse<B>) -> actix_web::Result<actix_web::middleware::errhandlers::ErrorHandlerResponse<B>> {
res.response_mut()
.headers_mut()
.insert(actix_web::http::header::CONTENT_TYPE, actix_web::http::HeaderValue::from_static("Error"));
Ok(actix_web::middleware::errhandlers::ErrorHandlerResponse::Response(res))
}
fn render_413<B>(mut res: actix_web::dev::ServiceResponse<B>) -> actix_web::Result<actix_web::middleware::errhandlers::ErrorHandlerResponse<B>> {
res.response_mut()
.headers_mut()
.insert(actix_web::http::header::CONTENT_TYPE, actix_web::http::HeaderValue::from_static("413 Error"));
let req = res.request();
let res = res.map_body(|_, _| actix_web::body::ResponseBody::Body(actix_web::dev::Body::from("{\"code\":413,\"error\":\"413 Payload Too Large\",\"message\":\"You've sent more data than expected\"}")).into_body());//alter the the response body see "https://users.rust-lang.org/t/actix-web-using-a-custom-error-handler-to-alter-the-response-body/41068"
Ok(actix_web::middleware::errhandlers::ErrorHandlerResponse::Response(res))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
use actix_web::{web, App, HttpServer};
HttpServer::new(|| {
App::new()
.wrap(
actix_web::middleware::errhandlers::ErrorHandlers::new()
.handler(actix_web::http::StatusCode::INTERNAL_SERVER_ERROR, render_500),
)
.wrap(
actix_web::middleware::errhandlers::ErrorHandlers::new()
.handler(actix_web::http::StatusCode::PAYLOAD_TOO_LARGE, render_413),
)
)
.service(
web::resource("/test")
.route(web::get().to(|| HttpResponse::Ok()))
.route(web::head().to(|| HttpResponse::MethodNotAllowed())),
)
.service(
web::scope("/hello_someone4")
.service(
web::resource("/there/*")
.app_data(web::PayloadConfig::default().limit(16))
.wrap(simple_middleware::simple_middleware_say_hi::SayHi)
.route(web::post().to(request_handlers2::accept_post))
)
)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
My code looks like the following:
let fetches = futures::stream::iter(
hosts.into_iter().map(|url| {
async move {
match reqwest::get(&url).await {
// Ok and Err statements here!
}
But, the problem here is that it gives an error for URLs with invalid or self-signed SSL certificate. So, I tried to do the following:
let fetches = futures::stream::iter(
hosts.into_iter().map(|url| {
async move {
match reqwest::Client::builder().danger_accept_invalid_certs(true).build().unwrap().get(&url).await {
// Ok and Err statements here!
}
When I try to build it with Cargo, it says "error[E0277]: `RequestBuilder` is not a future".
So, how can I make my code accept invalid certificates?
Unlike the top-level get() function, which returns a Response, the Client::get() method which you call in the second snippet, returns a RequestBuilder, which you must send() to actually communicate.
Adding the missing send() allows the code to compile (playgropund):
fn main() {
let hosts: Vec<String> = vec![];
let fetches = futures::stream::iter(hosts.into_iter().map(|url| async move {
match reqwest::Client::builder()
.danger_accept_invalid_certs(true)
.build()
.unwrap()
.get(&url)
.send()
.await
{
Ok(x) => x,
Err(x) => panic!(),
}
}));
}
I have an actix-web server using HttpAuthentication middleware to authenticate all requests. The server runs fine and responds correctly to most requests, but occasionally certain requests trigger the error:
thread 'actix-rt:worker:2' panicked at 'AuthenticationMiddleware was called already
A request to the same endpoint will only trigger the error some of the time, so I am not sure what the root cause is.
My main() function (with only the relevant code included is:
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
// Some configuration code here
HttpServer::new(move || {
App::new()
.wrap(
HttpAuthentication::basic(validator)
)
// other code here
})
.bind(ip)?
.run()
.await
}
The validator function passed as the process_fn argument to HttpAuthentication::basic is:
async fn validator(
req: ServiceRequest,
credentials: BasicAuth,
) -> Result<ServiceRequest, Error> {
let config = req.app_data::<Config>()
.map(|data| data.get_ref().clone())
.unwrap_or_else(Default::default)
.scope("urn:example:channel=HBO&urn:example:rating=G,PG-13");
let username = env::var("USERNAME")
.expect("USERNAME must be set");
let password = env::var("PASSWORD")
.expect("USERNAME must be set");
if credentials.user_id().deref() == username {
match credentials.password() {
Some(pass) => {
if pass.deref() == password {
Ok(req)
} else {
Err(AuthenticationError::from(config).into())
}
}
None => Err(AuthenticationError::from(config).into())
}
} else {
Err(AuthenticationError::from(config).into())
}
}
This function essentially is just checking for the validity of the basic authentication username and password sent in the request. As I understand it, this should be wrapping every endpoint on the server and only allowing authenticated requests through.
What I do not understand is why I am getting this runtime error. Does anyone have any ideas as to why this is happening?
Most recent threads I have read are saying async is the better way to perform lots of I/O bound work such as sending HTTP requests and the like. I have tried to pick up async recently but am struggling with understanding how to send many groups of requests in parallel, for example:
let client = reqwest::Client::new();
let mut requests = 0;
let get = client.get("https://somesite.com").send().await?;
let response = get.text().await?;
if response.contains("some stuff") {
let get = client.get("https://somesite.com/something").send().await?;
let response = get.text().await?;
if response.contains("some new stuff") {
requests += 1;
println!("Got response {}", requests)
This does what I want, but how can I run it in parallel and control the amount of "worker threads" or whatever the equivalent is to a thread pool in async?
I understand it is similar to this question, but mine is strictly talking about the nightly Rust async/await syntax and a more specific use case where groups of requests/tasks need to be done. I also find using combinators for these situations a bit confusing, was hoping the newer style would help make it a bit more readable.
Not sure if this is the fastest way, as I am just experimenting myself, but here is my solution:
let client = reqwest::Client::new();
let links = vec![ // A vec of strings representing links
"example.net/a".to_owned(),
"example.net/b".to_owned(),
"example.net/c".to_owned(),
"example.net/d".to_owned(),
];
let ref_client = &client; // Need this to prevent client from being moved into the first map
futures::stream::iter(links)
.map(async move |link: String| {
let res = ref_client.get(&link).send().await;
// res.map(|res| res.text().await.unwrap().to_vec())
match res { // This is where I would usually use `map`, but not sure how to await for a future inside a result
Ok(res) => Ok(res.text().await.unwrap()),
Err(err) => Err(err),
}
})
.buffer_unordered(10) // Number of connection at the same time
.filter_map(|c| future::ready(c.ok())) // Throw errors out, do your own error handling here
.filter_map(|item| {
if item.contains("abc") {
future::ready(Some(item))
} else {
future::ready(None)
}
})
.map(async move |sec_link| {
let res = ref_client.get(&sec_link).send().await;
match res {
Ok(res) => Ok(res.text().await.unwrap()),
Err(err) => Err(err),
}
})
.buffer_unordered(10) // Number of connections for the secondary requests (so max 20 connections concurrently)
.filter_map(|c| future::ready(c.ok()))
.for_each(|item| {
println!("File received: {}", item);
future::ready(())
})
.await;
This requires the #![feature(async_closure)] feature.