Rust Actix-Web and Dependency Injection [duplicate] - rust

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

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.

Rocket - Use state within my guards fails as traits are not implemented

I want to use state within my guards. I want to have routes that required authentication with an api key which I want to define in my Rocket.toml. But running this code I get the following error:
the trait From<(Status, ())> is not implemented for (Status, ApiKeyError)
for this line of code let config_state = try_outcome!(req.guard::<State<'_, Config>>().await);
How do I implement this trait? Or is there even a better solution to manage the api token in Rocket.
I am using the 0.5.0-devversion of Rocket.
#[macro_use] extern crate rocket;
use rocket::http::Status;
use rocket::request::{Outcome, Request, FromRequest};
use rocket::State;
use rocket::fairing::AdHoc;
use serde::Deserialize;
#[derive(Deserialize)]
struct Config {
api_key: String,
}
struct ApiKey<'r>(&'r str);
#[derive(Debug)]
enum ApiKeyError {
Missing,
Invalid,
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for ApiKey<'r> {
type Error = ApiKeyError;
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let config_state = try_outcome!(req.guard::<State<'_, Config>>().await);
/// Returns true if `key` is a valid API key string.
fn is_valid(key: &str, api_key: String) -> bool {
key == api_key
}
match req.headers().get_one("Authorization") {
None => Outcome::Failure((Status::Unauthorized, ApiKeyError::Missing)),
Some(key) if is_valid(key, config_state.api_key) => Outcome::Success(ApiKey(key)),
Some(_) => Outcome::Failure((Status::Unauthorized, ApiKeyError::Invalid)),
}
}
}
#[get("/")]
async fn index(config: State<'_, Config>, key: ApiKey<'_>) -> &'static str {
"Hello, world!"
}
fn rocket() -> rocket::Rocket {
let rocket = rocket::ignite();
let figment = rocket.figment();
let config: Config = figment.extract().expect("config");
rocket
.mount("/", routes![index])
.attach(AdHoc::config::<Config>())
}
#[rocket::main]
async fn main() {
rocket()
.launch()
.await;
}
I already stored the config with AdHoch::config() but to retrieve it within the guard I need to use request.rocket().state::<Config>(). The corrected source code is below:
#[macro_use] extern crate rocket;
use rocket::http::Status;
use rocket::request::{Outcome, Request, FromRequest};
use rocket::State;
use rocket::fairing::AdHoc;
use serde::Deserialize;
#[derive(Deserialize)]
struct Config {
api_key: String,
}
struct ApiKey<'r>(&'r str);
#[derive(Debug)]
enum ApiKeyError {
Missing,
Invalid,
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for ApiKey<'r> {
type Error = ApiKeyError;
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
// Retrieve the config state like this
let config = req.rocket().state::<Config>().unwrap();
/// Returns true if `key` is a valid API key string.
fn is_valid(key: &str, api_key: &str) -> bool {
key == api_key
}
match req.headers().get_one("Authorization") {
None => Outcome::Failure((Status::Unauthorized, ApiKeyError::Missing)),
Some(key) if is_valid(key, &config.api_key) => Outcome::Success(ApiKey(key)),
Some(_) => Outcome::Failure((Status::Unauthorized, ApiKeyError::Invalid)),
}
}
}
#[get("/")]
async fn index(config: State<'_, Config>, key: ApiKey<'_>) -> &'static str {
"Hello, world!"
}
fn rocket() -> rocket::Rocket {
let rocket = rocket::ignite();
let figment = rocket.figment();
let config: Config = figment.extract().expect("config");
rocket
.mount("/", routes![index])
.attach(AdHoc::config::<Config>())
}
#[rocket::main]
async fn main() {
rocket()
.launch()
.await;
}

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
}

Sending a closure (which returns a struct with a trait) to a thread leads to sized error

I'm trying to send a closure which will generate a structure to a thread, however when I try to do it I get a Sized error. I understand the error (the size is indeed not known at compile time), however adding Boxes and other such tricks does not seem to solve it.
I've tried to look into how to implement the Sized trait, however it seems to be quite special and honestly above my understanding.
I've written a minimal reproducible example:
use std::thread;
trait DataProcess {
fn start(&self);
fn run(&self);
fn stop(&self);
}
struct SomeDP {
name: String,
}
impl DataProcess for SomeDP {
fn start(&self) {
println!("Started");
}
fn run(&self) {
println!("Running");
}
fn stop(&self) {
println!("Stopped");
}
}
fn thread_maker(builder: Box<dyn Fn() -> (dyn DataProcess + Send)>) {
let thread_builder = thread::Builder::new();
let handle = thread_builder.spawn(move || {
let dp = builder();
dp.start();
});
}
fn main() {
let dp_builder = || SomeDP {
name: "nice".to_string(),
};
thread_maker(Box::new(dp_builder));
}
Which you can also find on the playground here
This works
use std::thread;
trait DataProcess{
fn start(&self);
fn run(&self);
fn stop(&self);
}
struct SomeDP{
name: String
}
impl DataProcess for SomeDP{
fn start(&self){println!("Started");}
fn run(&self){println!("Running");}
fn stop(&self){println!("Stopped");}
}
fn thread_maker<F>(builder: F)
where
F: Fn() -> Box<dyn DataProcess>,
F: Send + 'static {
let thread_builder = thread::Builder::new();
let handle = thread_builder.spawn(
move ||{
let dp = builder();
dp.start();
}
);
}
fn main(){
let dp_builder = || -> Box<dyn DataProcess> {
Box::new(SomeDP{name: "nice".to_string()})
};
thread_maker(dp_builder);
}

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