How to create an overload API or function in Actix-web? - rust

Background
I need to create a couple of endpoints for an API service project. These API can accept encrypted and un-encrypted one (for development). Both parameters then are passed into a same function. For example:
/api/movie/get with encrypted parameter (for production)
/dev/movie/get with un-encrypted parameter (for development)
Current implementation in actix_web
I use actix_web and routes (not macro) to provide path routing. Modified from the sample code below
use actix_web::{web, App, HttpServer, Responder};
async fn index() -> impl Responder {
"Hello world!"
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new().service(
// prefixes all resources and routes attached to it...
web::scope("/app")
// ...so this handles requests for `GET /app/index.html`
.route("/index.html", web::get().to(index)),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
For each API endpoint, I had to create 2 functions, such as:
/// for encrypted one
/// PATH: /api/movie/get
pub async fn handle_get_movie(
app_data: Data<AppState>,
Json(payload): Json<EncryptedPayload>,
) -> Result<impl Responder, Error> {
// decrypt the content
// this is the only difference between un-encrypted and encrypted endpoint
let params = payload
.unload::<GetMovieInf>()
.map_err(|_| MainError::Malformatted)?;
// other codes here....
Ok(Json(ReplyInf {ok: true}))
}
/// for un-encrypted one
/// PATH: /dev/movie/get
pub async fn handle_get_movie(
app_data: Data<AppState>,
Json(payload): Json<UnencryptedPayload>,
) -> Result<impl Responder, Error> {
// other codes here. These codes are exactly identical with the above function...
Ok(Json(ReplyInf {ok: true}))
}
Questions
Since both functions are similar, does it possible to combine them both into a single function ? a function "overload" maybe ?
The problem is this line Json(payload): Json<UnencryptedPayload> in the parameter function. I tried to use generics like Json<T>. this doesn't work.
I can use the environment variable to control which should be active (EncryptedPayload or UnencryptedPayload). I can use one path for each endpoint (eg: /api/movie/get) and don't have to write the same functionality twice.

Related

Rust Actix-Web HttpServer Type Definition

I'm currently working on an actix-web application but am having some difficulty while trying to refactor some of the code into smaller functions. When setting up the HttpServer the variable type is just inferred.
main.rs (basic example)
use actix_web;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// prepare the server variable but don't start it
let _server = actix_web::HttpServer::new(|| {
actix_web::App::new().service(nothing)
}).bind(("0.0.0.0", 31)).expect("FAIL");
// redacted other stuff
// ok now handle the server start
return _server.run().await;
}
#[actix_web::get("/")]
async fn nothing() -> actix_web::HttpResponse {
return actix_web::HttpResponse::Ok().body("nothing");
}
When moving it into a separate function though, I need to explicitly put the type definition so I can return it.
main.rs (refactored example)
use actix_web;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// prepare the server variable but don't start it
let _server = get_server();
// redacted other stuff
// ok now handle the server start
return _server.run().await;
}
#[actix_web::get("/")]
async fn nothing() -> actix_web::HttpResponse {
return actix_web::HttpResponse::Ok().body("nothing");
}
fn get_server() -> /* unknown type definition */ {
return actix_web::HttpServer::new(|| {
actix_web::App::new().service(nothing)
}).bind(("0.0.0.0", 31)).expect("FAIL");
}
I was wondering if there is a way to handle the type definition of the actix-web HttpServer when it cannot be inferred. I guess more generally I am wondering if it is possible to return an inferred variable type from a function but more specifically I'm just having difficulty defining the HttpServer type because of how many Generics it seems to need in its definition

What does this # line of code mean in Rust?

Im learning rust and I came across this sample code:
use actix_web::{middleware, web, App, HttpRequest, HttpServer};
async fn index(req: HttpRequest) -> &'static str {
println!("REQ: {req:?}");
"Hello world!"
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
HttpServer::new(|| {
App::new()
// enable logger
.wrap(middleware::Logger::default())
.service(web::resource("/index.html").to(|| async { "Hello world!" }))
.service(web::resource("/").to(index))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
As someone coming from Java, I dont know what this line of code means:
#[actix_web::main]
If I remove that and run the program, the error says 'main function cannot be async' , so it must be important.
Does anyone have link to documentation explaining what it is - the use of pound (#) sign?
This is an attribute. There are various kinds of attributes. In this case, this is an proc macro attribute, actix_web::main. It takes the item annotated with it (async fn main()) and transforms it into some other thing, usually some variation of the original code.
For example, this attribute prepares the Actix runtime and allows you to have an async main (in reality, main() cannot be async, but this attribute transforms it into something like fn main() -> ... { actix_web::rt::System::new().block_on(async move { ... }) }).

Actix-web integration tests: reusing the main thread application

I am using actix-web to write a small service. I'm adding integration tests to assess the functionality and have noticed that on every test I have to repeat the same definitions that in my main App except that it's wrapped by the test service:
let app = test::init_service(App::new().service(health_check)).await;
This can be easily extended if you have simple services but then when middleware and more configuration starts to be added tests start to get bulky, in addition it might be easy to miss something and not be assessing the same specs as the main App.
I've been trying to extract the App from the main thread to be able to reuse it my tests without success.
Specifically what I'd like is to create a "factory" for the App:
pub fn get_app() -> App<????> {
App::new()
.wrap(Logger::default())
.wrap(IdentityService::new(policy))
.service(health_check)
.service(login)
}
So that I can write this in my tests
let app = get_app();
let service = test::init_service(app).await;
But the compiler needs the specific return type which seems to be a chorizo composed of several traits and structs, some private.
Has anyone experience with this?
Thanks!
Define a declarative macro app! that builds the App, but define the routes using the procedural API, not the Actix build-in macros such as #[get("/")].
This example uses a database pool as a state - your application might have different kind of states or none at all.
#[macro_export]
macro_rules! app (
($pool: expr) => ({
App::new()
.wrap(middleware::Logger::default())
.app_data(web::Data::new($pool.clone()))
.route("/health", web::get().to(health_get))
.service(web::resource("/items")
.route(web::get().to(items_get))
.route(web::post().to(items_post))
)
});
);
This can be used in the tests as:
#[cfg(test)]
mod tests {
// more code here for get_test_pool
#[test]
async fn test_health() {
let app = test::init_service(app!(get_test_pool().await)).await;
let req = test::TestRequest::get().uri("/health").to_request();
let resp = test::call_service(&app, req).await;
assert!(resp.status().is_success());
}
}
and in the main app as:
// More code here for get_main_pool
#[actix_web::main]
async fn main() -> Result<(),std::io::Error> {
let pool = get_main_pool().await?;
HttpServer::new(move || app!(pool))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
In this context, get_main_pool must return, say, Result<sqlx::Pool<sqlx::Postgres>, std::io::Error> to be compatible with the signature requirements of actix_web::main. On the other hand, get_test_pool can simply return sqlx::Pool<sqlx::Postgres>.
I was struggling with the same issue using actix-web#4, but I came up with a possible solution. It may not be ideal, but it works for my needs. I needed to bring in actix-service#2.0.2 and actix-http#3.2.2 in Cargo.toml as well.
I created a test.rs file with an initializer that I can use in all my tests. Here is what that file could look like for you:
use actix_web::{test::{self}, App, web, dev::{HttpServiceFactory, ServiceResponse}, Error};
use actix_service::Service;
use actix_http::{Request};
#[cfg(test)]
pub async fn init(service_factory: impl HttpServiceFactory + 'static) -> impl Service<Request, Response = ServiceResponse, Error = Error> {
// connect to your database or other things to pass to AppState
test::init_service(
App::new()
.app_data(web::Data::new(crate::AppState { db }))
.service(service_factory)
).await
}
I use this in my API services to reduce boilerplate in my integration tests. Here is an example:
// ...
#[get("/")]
async fn get_index() -> impl Responder {
HttpResponse::Ok().body("Hello, world!")
}
#[cfg(test)]
mod tests {
use actix_web::{test::TestRequest};
use super::{get_index};
#[actix_web::test]
async fn test_get_index() {
let mut app = crate::test::init(get_index).await;
let resp = TestRequest::get().uri("/").send_request(&mut app).await;
assert!(resp.status().is_success(), "Something went wrong");
}
}
I believe the issue you ran into is trying to create a factory for App (which is a bit of an anti-pattern in Actix) instead of init_service. If you want to create a function that returns App I believe the preferred convention is to use configure instead. See this issue for reference: https://github.com/actix/actix-web/issues/2039.

How can I pass structs from an Actix middleware to the handler?

I'm trying to write an authentication middleware for my Actix application. When validating the request in the middleware, I make a call to a database to retrieve the necessary user data to validate the incoming request. Once the request has been authorised, I want to be able to pass this user data to the handler as this will allow me to avoid having the query for the same data twice.
I can't find a solution for this. The best suggestion I could find so far was to "set a request extension". There doesn't seem to be any examples for this and there is also too little documentation around this to work out what to do here.
You can pass data from middleware (service) to handler via extensions. First of all you have to insert extension (in service).
For ServiceRequest struct is implemented HttpMessage witch hase extensions_mut() function. It must be mutable becouse you will be inserting new extension. It might look something like this:
req.extensions_mut().insert(user);
Then you have to implement FromRequest trait for your data structure.
impl FromRequest for User {
type Error = actix_web::Error;
type Future = futures::future::Ready<Result<Self, Self::Error>>;
type Config = ();
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
match req.extensions().get::<User>() {
Some(user) => return ok(user.clone()),
None => return err(actix_web::error::ErrorBadRequest("ups..."))
};
}
}
Then you're ready to use it in handler.
pub async fn get_user_handler(user: User) {}

How can I return a configured App in Actix-Web?

I'm using actix-web to create a httpserver with state/data embedded in it. But vscode show me that the create_app function has wrong arguments in its return value type definition App<AppState>:
pub struct App<T, B>
wrong number of type arguments: expected 2, found 1
expected 2 type argumentsrustc(E0107)
app.rs:
use crate::api;
use crate::model::DbExecutor;
use actix::prelude::Addr;
use actix_web::{error, http::Method, middleware::Logger, web, App, HttpResponse};
pub struct AppState {
pub db: Addr<DbExecutor>,
}
pub fn create_app(db: Addr<DbExecutor>) -> App<AppState> {
App::new().data(AppState { db }).service(
web::resource("/notes/").route(web::get().to(api::notes))
);
}
main.rs:
fn main() {
HttpServer::new(move || app::create_app(addr.clone()))
.bind("127.0.0.1:3000")
.expect("Can not bind to '127.0.0.1:3000'")
.start();
}
As return type of "service" method is "Self" which is type actix_web::App, I tried modify return type to App (without generic parameter) but still got error, what should I do?
First, App takes two generic type arguments, App<AppEntry, Body>, you've only given one.
Second, AppState is not AppEntry.
Third, instantiating App outside actix-web is hard, if not impossible, as the types you need from actix-web are not public.
Instead, you should use configure to achieve the same, here is a simplified example:
use actix_web::web::{Data, ServiceConfig};
use actix_web::{web, App, HttpResponse, HttpServer};
fn main() {
let db = String::from("simplified example");
HttpServer::new(move || App::new().configure(config_app(db.clone())))
.bind("127.0.0.1:3000")
.expect("Can not bind to '127.0.0.1:3000'")
.run()
.unwrap();
}
fn config_app(db: String) -> Box<Fn(&mut ServiceConfig)> {
Box::new(move |cfg: &mut ServiceConfig| {
cfg.data(db.clone())
.service(web::resource("/notes").route(web::get().to(notes)));
})
}
fn notes(db: Data<String>) -> HttpResponse {
HttpResponse::Ok().body(["notes from ", &db].concat())
}
Read more about ServiceConfig in the api documentation.

Resources