tokio::spawn makes axum won't take request in parallel - rust

// src/server.rs
use axum::{
extract::Path,
response::{IntoResponse, Response},
routing::get,
};
pub struct Server {}
impl Server {
pub async fn run() -> Result<(), Box<dyn std::error::Error>> {
let axum_http_make_service = axum::Router::new()
.route("/:sec", get(wait_sec_event))
.into_make_service();
let http_server =
axum::Server::bind(&"0.0.0.0:4000".parse().unwrap()).serve(axum_http_make_service);
let http_handle = tokio::spawn(http_server);
let _ = tokio::try_join!(http_handle)?;
Ok(())
}
}
async fn wait_sec_event(Path(sec): Path<String>) -> Response {
let a = std::time::Duration::from_secs(sec.parse::<u64>().unwrap());
std::thread::sleep(a);
"yo".into_response()
}
// src/app.rs
use std::net::SocketAddr;
use crate::server;
pub struct App {
port: SocketAddr,
}
impl App {
pub fn new(p: SocketAddr) -> Self {
Self { port: p }
}
pub async fn run(self) -> Result<(), Box<dyn std::error::Error>> {
server::Server::run().await
}
}
// src/main.rs
use std::net::SocketAddr;
use app::App;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// build our application with a single route
let app = App::new(SocketAddr::from(([0, 0, 0, 0], 4000)));
app.run().await
}
pub mod app;
pub mod server;
When I tried to implement a axum server I found that if I put axum::Server::bind(&"0.0.0.0:4000".parse().unwrap()).serve(axum_http_make_service); into tokio::spawn instead of just await.unwrap() it
the server just can't accept requests in parallel.
means if I do curl 127.0.0.1:4000/10 then curl 127.0.0.1:4000/3 ,
the later request won't execute until the first one is finished This won't happen if I just await.unwrap() it.
Any idea where I might make a mistake?

You use std::thread::sleep, blocking the thread, which you shouldn't do in an async environment because it can prevent other tasks on the same thread to run like you experienced.
Use tokio::time::sleep instead:
async fn wait_sec_event(Path(sec): Path<String>) -> Response {
let a = std::time::Duration::from_secs(sec.parse::<u64>().unwrap());
tokio::time::sleep(a).await;
"yo".into_response()
}
I believe the difference in behaviour is because more or less by chance the tasks get spawned on different threads in your directly awaiting scenario while they get spawned on the same thread when using tokio::spawn.

Related

rust: Single trait to implement read/write/list files from local and remote sources

I'm trying to create a web service that can stream files from various sources. I want to declare a Source trait that each source must implement with methods for listing, reading and eventually writing files but I have a hard time finding the right pattern.
In the code below I get problems with Source not being "object safe" due to the generic parameter R.
What would be a good pattern to use to have multiple source types some local, some remote/network ones implement the same Source trait to read/write/list files?
use std::collections::HashMap;
use anyhow::{anyhow, Result};
use async_std::path::PathBuf;
use async_trait::async_trait;
use tokio::{io::{BufReader, AsyncRead}, fs::File};
#[async_trait]
pub trait Source {
// async fn list(&self, path: PathBuf, path_prefix: PathBuf) -> Result<Vec<FileMeta>>;
async fn reader<R: AsyncRead>(&self, path: PathBuf) -> Result<BufReader<R>>;
// async fn writer<W: AsyncWrite>(&self, path: PathBuf) -> Result<BufWriter<W>>;
}
#[derive(Clone)]
pub struct Local {
root: PathBuf
}
impl Local {
pub async fn new(root: PathBuf) -> Result<Self> {
Ok(Self { root: root.canonicalize().await? })
}
fn root(&self) -> PathBuf {
self.root.clone()
}
async fn resolve(&self, path: PathBuf) -> Result<PathBuf> {
let path = path.strip_prefix("/").unwrap_or(&path);
let mut result = self.root();
result.push(path);
result.canonicalize().await?;
if !result.starts_with(self.root()) {
return Err(anyhow!("Requested path is outside source root"));
}
Ok(result)
}
}
#[async_trait]
impl Source for Local {
async fn reader<R: AsyncRead>(&self, path: PathBuf) -> Result<BufReader<R>> {
let file = File::open(self.resolve(path).await?).await?;
let reader = BufReader::new(file);
Ok(reader)
}
/*
async fn writer<W: AsyncWrite>(&self, path: PathBuf) -> Result<BufWriter<W>> {
todo!()
}
*/
}
/*
The idea is to allow other file sources, HTTP, SSH, S3 ect. as long as they implement
the Source trait
#[derive(Clone)]
pub struct RemoteHTTP {
server_url: String
}
#[async_trait]
impl Source for RemoteHTTP {
async fn reader<R: AsyncRead>(&self, path: PathBuf) -> Result<BufReader<R>> {
todo!()
}
async fn writer<W: AsyncWrite>(&self, path: PathBuf) -> Result<BufWriter<W>> {
todo!()
}
}
*/
pub struct Config {
sources: HashMap<String, Box<dyn Source>>,
}
impl Config {
pub async fn load() -> Result<Self> {
let local = Local::new("/tmp/".into()).await?;
// let remote = RemoteHTTP::new("https://example.org".into());
let mut sources: HashMap<String, Box<dyn Source>> = HashMap::new();
sources.insert("local".into(), Box::new(local));
// sources.insert("remote".into(), Box::new(remote));
Ok(Self { sources })
}
pub fn sources(&self) -> HashMap<String, Box<dyn Source>> {
self.sources.clone()
}
}
#[tokio::main]
async fn main() -> Result<()> {
let config = Config::load().await;
// Store various sources into a config map
let local = Local::new("/tmp".into()).await?;
config.sources.insert("local".into(), Box::new(local));
// Create a read stream from one of hhe sources
if let Some(source) = config.sources.get("local".into()) {
let reader = source.reader("a-file".into()).await?;
// stream data with an actix HTTP service using: HttpResponse::Ok().streaming(reader)
}
Ok(())
}
You cannot use generics in methods of traits that are intended to be used dynamically. But even if you could, the signature of Source::reader() wouldn't work because it'd allow the caller to choose which reader type to return, whereas the implementation would surely want to return a concrete type. Thus every concrete implementation would fail to compile with "returned <some concrete type>, generic type R expected". The correct return type would be something like BufReader<impl AsyncRead>, but that wouldn't work because impl in return position is not yet allowed in traits, and because you need your trait to be object-safe.
Instead, Source::reader() should return a boxed AsyncRead. For example:
#[async_trait]
pub trait Source {
async fn reader(&self, path: PathBuf) -> Result<BufReader<Box<dyn AsyncRead + Unpin>>>;
}
The implementation then looks like this:
#[async_trait]
impl Source for Local {
async fn reader(&self, path: PathBuf) -> Result<BufReader<Box<dyn AsyncRead + Unpin>>> {
let file = File::open(self.resolve(path).await?).await?;
let reader = BufReader::new(Box::new(file) as _);
Ok(reader)
}
}
Your example fixed up to compile on the playground.

Rust Actix-Web and Dependency Injection [duplicate]

I want to create a actix-web server where I can provide my Search trait as application data in order to easily swap between multiple implementations or use mock implementation for testing. Whatever I try I can't get it to compile or when I get it to compile I get the following error when visiting the route in the web browser:
App data is not configured, to configure use App::data()
Here is what I have so far
# Cargo.toml
[dependencies]
actix-rt = "1.1.1"
actix-web = "3.3.2"
[dev-dependencies]
tokio = "0.2.22"
//! main.rs
use actix_web::dev::Server;
use actix_web::{get, web, App, HttpServer, Responder};
pub trait Search {
fn search(&self, query: &str) -> String;
}
#[derive(Clone)]
pub struct SearchClient {
base_url: String,
}
impl SearchClient {
pub fn new() -> Self {
Self {
base_url: String::from("/search"),
}
}
}
impl Search for SearchClient {
fn search(&self, query: &str) -> String {
format!("Searching in SearchClient: {}", query)
}
}
#[get("/{query}")]
async fn index(
web::Path(query): web::Path<String>,
search: web::Data<dyn Search>,
) -> impl Responder {
search.into_inner().search(&query)
}
pub fn create_server(
search: impl Search + Send + Sync + 'static + Clone,
) -> Result<Server, std::io::Error> {
let server = HttpServer::new(move || App::new().data(search.clone()).service(index))
.bind("127.0.0.1:8080")?
.run();
Ok(server)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let search_client = SearchClient::new();
create_server(search_client).unwrap().await
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone)]
pub struct TestClient;
impl Search for TestClient {
fn search(&self, query: &str) -> String {
format!("Searching in TestClient: {}", query)
}
}
#[actix_rt::test]
async fn test_search() {
let search_client = TestClient {};
let server = create_server(search_client).unwrap();
tokio::spawn(server);
}
}
When adding the data to your App, you have to specify that you want it to be downcasted as a trait object. Data does not accept unsized types directly, so you have to first create an Arc (which does accept unsized types) and then convert it to a Data. We will use the app_data method to avoid wrapping the searcher in a double arc.
pub fn create_server(
search: impl Search + Send + Sync + 'static,
) -> Result<Server, std::io::Error> {
let search: Data<dyn Search> = Data::from(Arc::new(search));
HttpServer::new(move || {
App::new()
.app_data(search.clone())
})
}
async fn index(
query: Path<String>,
search: Data<dyn Search>,
) -> impl Responder {
search.into_inner().search(&*query)
}
An alternative approach is using generics. Your handler and create_server functions would be generic over a Search implementation:
async fn index<T: Search>(
web::Path(query): web::Path<String>,
search: web::Data<T>,
-> impl Responder {
search.into_inner().search(&query)
}
pub fn create_server<T: Search + Send + Sync + 'static + Clone>(
search: T,
) -> Result<Server, std::io::Error> {
let server = HttpServer::new(move || {
App::new()
.data(search.clone())
.route("/{query}", web::get().to(index::<T>))
})
.bind("127.0.0.1:8080")?
.run();
Ok(server)
}
Now, when you create the server in main, you can use SearchClient:
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let search_client = SearchClient::new();
create_server(search_client).unwrap().await
}
And when you create the server for testing purposes, you could use a TestClient:
#[actix_rt::test]
async fn test_search() {
let search_client = TestClient {};
let server = create_server(search_client).unwrap();
}
The downside to the generics based approach is that you cannot use the #[get("")] macros for routing because you have to specify the handler's generic parameters:
App::new()
.route("/{query}", web::get().to(index::<T>))

Why does my Future implementation get stuck at the start? [duplicate]

This question already has answers here:
How to implement a Future or Stream that polls an async fn?
(2 answers)
Closed 5 months ago.
Although the code compiles, I don't understand why when using await directly it gets stuck in the first request. Why do I need to use the method execute_requests instead of calling it on the Future implementation?
// ...
async fn send(url: &str) {
println!("Sending to URL {}", url);
// Simulate the sending process
sleep(Duration::from_millis(500)).await;
}
type Request = Pin<Box<dyn Future<Output = ()>>>;
struct Proxy;
impl Proxy {
async fn execute_requests(&self) {
let request_1 = async {
send("url 1").await;
};
let request_2 = async {
send("url 2").await;
};
let mut requests: Vec<Request> = vec![];
requests.push(Box::pin(request_2));
requests.push(Box::pin(request_1));
while let Some(request) = requests.pop() {
request.await;
}
}
}
impl Future for Proxy {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut queue_process = Box::pin(self.execute_requests());
queue_process.as_mut().poll(cx)
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let proxy = Proxy;
// Executes both requests
// Ok(proxy.execute_requests().await)
// FIXME: Timeouts on the first request
Ok(proxy.await)
}
Rust Playground
execute_requests is a simplification: it needs access to self to get the requests and other things.
Each time you poll, you create a new execute_requests() future and poll it once. It will never advance to the next poll - next time your poll() is called, a new future will be created and will be polled once, and so on.
Instead, you should store the execute_requests() future within Proxy, and poll the same future:
struct Proxy(Pin<Box<dyn Future<Output = ()>>>);
impl Proxy {
fn new() -> Self {
Self(Box::pin(Self::execute_requests()))
}
async fn execute_requests() {
let request_1 = async {
send("url 1").await;
};
let request_2 = async {
send("url 2").await;
};
let mut requests: Vec<Request> = vec![];
requests.push(Box::pin(request_2));
requests.push(Box::pin(request_1));
while let Some(request) = requests.pop() {
request.await;
}
}
}
impl Future for Proxy {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.0.as_mut().poll(cx)
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let proxy = Proxy::new();
// Executes both requests
// Ok(proxy.execute_requests().await)
// FIXME: Timeouts on the first request
Ok(proxy.await)
}
Playground.

Errors when moving to actix-web 3.0

Better late than never and so I started re-learning Rust and decided to focus on actix and actix-web.
I have these codes running in actix-web 1.0 and it seems not to run in actix-web 3.0:
main.rs
use messages_actix::MessageApp;
fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
let app = MessageApp::new(8081);
app.run() // error here
}
error: "no method named run found for opaque type impl std::future::Future in the current scope method not found in impl std::future::Future
lib.rs
#[macro_use]
extern crate actix_web;
use actix_web::{middleware, web, App, HttpRequest, HttpServer, Result};
use serde::Serialize;
pub struct MessageApp {
pub port: u16,
}
#[derive(Serialize)]
pub struct IndexResponse{
pub message: String,
}
#[get("/")]
pub fn index(req: HttpRequest) -> Result<web::Json<IndexResponse>> { // error here
let hello = req
.headers()
.get("hello")
.and_then(|v| v.to_str().ok())
.unwrap_or_else(|| "world");
Ok(web::Json(IndexResponse {
message: hello.to_owned(),
}))
}
error for index: the trait Factory<_, _, _> is not implemented for fn(HttpRequest) -> std::result::Result<Json<IndexResponse>, actix_web::Error> {<index as HttpServiceFactory>::register::index}
impl MessageApp {
pub fn new(port: u16) -> Self {
MessageApp{ port }
}
pub fn run(&self) -> std::io::Result<()> {
println!("Starting HTTP server at 127.0.0.1:{}", self.port);
HttpServer::new(move || {
App::new()
.wrap(middleware::Logger::default())
.service(index)
})
.bind(("127.0.0.1", self.port))?
.workers(8)
.run() //error here
}
}
error: expected enum std::result::Result, found struct Server
checked the migration doc yet can't find what I'm looking for in relation to the errors listed.
Any help greatly appreciated...Thanks...
Newer versions of actix-web now use the async-await syntax, which was made stable as of Rust 1.39. You have to make your handlers async:
#[get("/")]
pub async fn index(req: HttpRequest) -> Result<web::Json<IndexResponse>> {
// ...
}
Creating an HttpServer is now an async operation:
impl MessageApp {
pub fn run(&self) -> std::io::Result<()>
HttpServer::new(...)
.run()
.await
}
}
And you can use the main macro to use async/await in your main function:
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let app = MessageApp::new(8081);
app.run().await
}

Dependency Injection in Rust Warp

How do I inject dependencies into my route handlers in Warp? A trivial example is as follows. I have a route that I want to serve a static value that is determined at startup time, but the filter is what passes values into the final handler. How do I pass additional data without creating global variables? This would be useful for dependency injection.
pub fn root_route() -> BoxedFilter<()> {
warp::get().and(warp::path::end()).boxed()
}
pub async fn root_handler(git_sha: String) -> Result<impl warp::Reply, warp::Rejection> {
Ok(warp::reply::json(
json!({
"sha": git_sha
})
.as_object()
.unwrap(),
))
}
#[tokio::main]
async fn main() {
let git_sha = "1234567890".to_string();
let api = root_route().and_then(root_handler);
warp::serve(api).run(([0,0,0,0], 8080)).await;
}
Here is a simple example. By using the .and() in conjunction with .map(move ||)
you can add parameters to the tuple that will be passed into the final handler function.
use warp::filters::BoxedFilter;
use warp::Filter;
#[macro_use]
extern crate serde_json;
pub fn root_route() -> BoxedFilter<()> {
warp::get().and(warp::path::end()).boxed()
}
pub async fn root_handler(git_sha: String) -> Result<impl warp::Reply, warp::Rejection> {
Ok(warp::reply::json(
json!({
"sha": git_sha
})
.as_object()
.unwrap(),
))
}
pub fn with_sha(git_sha: String) -> impl Filter<Extract = (String,), Error = std::convert::Infallible> + Clone {
warp::any().map(move || git_sha.clone())
}
#[tokio::main]
async fn main() {
let git_sha = "1234567890".to_string();
let api = root_route().and(with_sha(git_sha)).and_then(root_handler);
warp::serve(api).run(([0,0,0,0], 8080)).await;
}

Resources