Actix/Diesel API not responding to requests from Postman - rust

My code seems to be ok, as it properly compiles and is quite simple. But when I run my app with cargo run, even though the program executes properly and outputs some debug printlns, it won't answer to any request.
This is my main.rs:
use actix_web::{web, App, HttpServer};
use diesel::r2d2::{ConnectionManager, Pool};
use diesel::sqlite::SqliteConnection;
use dotenvy::dotenv;
#[path = "api/books/books_handlers.rs"]
mod books_handlers;
#[path = "api/books_relationships/books_relationships_handlers.rs"]
mod books_relationships_handlers;
mod models;
mod routes;
mod schema;
mod logger;
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
// Load .env file and set initialization variables
dotenv().ok();
std::env::set_var("RUST_LOG", "actix_web=debug");
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
// Create db connection pool with SQLite
let manager = ConnectionManager::<SqliteConnection>::new(database_url);
let pool: Pool<ConnectionManager<SqliteConnection>> = r2d2::Pool::builder()
.build(manager)
.expect("Failed to create pool.");
// Start HTTP server and register routes
println!("Starting server at http://localhost:8080");
HttpServer::new(move || {
App::new()
.app_data(pool.clone())
// Book class
.route("/create_book", web::post().to(books_handlers::create_book_handler))
.route("/list_books", web::get().to(books_handlers::list_books_handler))
.route("/get_book/{id}", web::post().to(books_handlers::read_book_by_id_handler))
.route("/update_book/{id}", web::put().to(books_handlers::update_book_handler))
.route("/delete_book/{id}", web::delete().to(books_handlers::delete_book_handler))
// BookRelationships class
.route("/create_book_relationship", web::post().to(books_relationships_handlers::create_book_relationship_handler))
.route("/list_book_relationships", web::get().to(books_relationships_handlers::list_books_handler))
.route("/get_book_relationship/{id}", web::post().to(books_relationships_handlers::read_book_by_id_handler))
.route("/update_book_relationship/{id}", web::put().to(books_relationships_handlers::update_book_handler))
.route("/delete_book_relationship/{id}", web::delete().to(books_relationships_handlers::delete_book_handler))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
This is the first handler, the one I'm trying with Postman:
pub async fn create_book_handler(book_data: web::Json<Book>, pool: web::Data<DbPool>) -> HttpResponse {
println!("create_book_handler: {:#?}", book_data); // <-- this never gets executed
let result = books_dao::create_book(book_data, pool).await;
match result {
Ok(book) => {
println!("create_book_handler, OK. Book: {:#?}", book);
HttpResponse::Ok()
.content_type(ContentType::json())
.json(&book)
},
Err(err) => {
println!("create_book_handler, ERROR: {:#?}", err);
log(LogType::Error, err.to_string());
HttpResponse::InternalServerError()
.content_type(ContentType::json())
.body("{err: 'Unable to insert book into database'")
}
}
}
Then the code executes this function, calling Diesel and altering the DB:
pub async fn create_book(book: web::Json<Book>, pool: web::Data<DbPool>) -> Result<usize, Error> {
let mut conn = pool
.get()
.expect("Failed to get database connection from pool");
diesel::insert_into(books::table)
.values(book.into_inner())
.execute(&mut conn)
}
But the problem seems to be even before: not even the println! at the beginning of the handler get executed. When I start the app and send a POST request to http://127.0.0.1:8080/create_book, I get the following error in Postman:
Requested application data is not configured correctly. View/enable debug logs for more details.
Am I sending the requests in a wrong way, or is the API malfunctioning?

The DbPool is wrapped incorrectly. It should look like
...
App::new()
.app_data(actix_web::web::Data::new(pool.clone()))
...
This correctly wraps the DB Pool in the smart pointer that the route handlers can then use across your application

Related

Why is actix-session not correctly assigning values?

I am newbie to Rust, and I am developing a Rust HTTP API with Actix Web. In order to maintain the session between requests, I wanted to use the official-provided package Actix Session, currently, this is my implementation on main.rs:
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let session_secret_key = CookieKeyHelper::generate();
let server = HttpServer::new(move || {
App::new()
.wrap(Cors::permissive())
.wrap(
SessionMiddleware::builder(
CookieSessionStore::default(),
session_secret_key.clone(),
)
.cookie_secure(false)
.build(),
)
// [...]
})
And in some places I'm calling it:
// [...]
return match token_result {
Ok(token) => {
println!("Received token: {:?}", token);
println!("Access token: {:?}", token.access_token().secret());
session
.insert::<StandardTokenResponse<EmptyExtraTokenFields, BasicTokenType>>(
"token", token,
)
.unwrap();
session
.insert::<AuthorizationCode>(
"auth_code",
AuthorizationCode::new(query["code"].to_string()),
)
.unwrap();
// [...]
No errros are raised, but then, when I try to read the session values with session.entries(), it is always empty {}.
What am I doing wrong here?
Thanks in advance!! :)

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
}

Actix Web: Requested application data is not configured correctly. View/enable debug logs for more details

I have a simple application with an HTTP endpoint and a connection to a MongoDB database.
use actix_web::{
middleware, post,
web::{self},
App, HttpServer, Responder,
};
use mongodb::{options::ClientOptions, Client};
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct TestBody {
name: String,
age: u8,
}
#[post("/test")]
async fn test(query: web::Json<TestBody>, db: web::Data<Client>) -> impl Responder {
for db_name in db.list_database_names(None, None).await.unwrap() {
println!("{}", db_name);
}
let res = format!("{} {}", query.name, query.age);
res
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let connection_string = "secret-connection-string";
let client_options = ClientOptions::parse(connection_string).await.unwrap();
let client = Client::with_options(client_options).unwrap();
HttpServer::new(move || {
App::new()
.wrap(middleware::Compress::default())
.app_data(client.clone())
.app_data(web::JsonConfig::default())
.service(test)
})
.bind("0.0.0.0:7080")?
.run()
.await
}
It compiles and runs just fine. But when trying to access localhost:7080/test, I get the following response:
Requested application data is not configured correctly. View/enable debug logs for more details.
I don't see any logs in the console. How do I view or enable the Actix Web logs?
To see the logs of Actix Web, add the env_logger dependency to the cargo.toml.
[dependencies]
env_logger = "0.10.0"
You will also have to set the environment variable RUST_LOG to determine the log level. This can be done at runtime using std::env::set_var.
#[actix_web::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "debug");
env_logger::init();
/* ... */
}
This enables debug logging for Rust and Actix Web.
To solve the original issue: You always need to wrap data passed to app_data() with Data::new().
This is how I did it before:
HttpServer::new(move || {
App::new()
/* ... */
.app_data(client.clone())
/* ... */
})
How it should be instead:
HttpServer::new(move || {
App::new()
/* ... */
.app_data(Data::new(client.clone())) // <-- Data::new() here
/* ... */
})

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

How can I stop the hyper HTTP web server and return an error?

The docs for the hyper crate have a simple example to start a web server:
extern crate hyper;
use hyper::service::service_fn_ok;
use hyper::{Body, Response, Server};
fn main() {
// Construct our SocketAddr to listen on...
let addr = ([127, 0, 0, 1], 3000).into();
// And a NewService to handle each connection...
let new_service = || service_fn_ok(|_req| Response::new(Body::from("Hello World")));
// Then bind and serve...
let server = Server::bind(&addr).serve(new_service);
// Finally, spawn `server` onto an Executor...
hyper::rt::run(server.map_err(|e| {
eprintln!("server error: {}", e);
}));
}
I want to embed this web server in a procedure that returns an error:
// Executes a web server. Returns a result object with an error object
// if the server stopped unexpectedly
fn run_and_stop_web_server(addr: std::net::SocketAddr) -> Result<(), Error> {
let server_builder = Server::try_bind(&addr)?;
let server = server_builder.serve(move || {
let handler: Handler = Handler::new();
hyper::service::service_fn(move |req| handler.serve(req))
});
// Run the server
// HERE: I don't know how to handle the error so I can return it from
// the run_and_stop_web_server() function
hyper::rt::run(server.map_err(|e| eprintln!("Ignored Server error: {}", e)));
Result::Ok(())
}
I don't know how I can handle the error. Instead of using eprintln!() I want to return the error from the run_and_stop_web_server function.
I believe I found a solution using the tokio runtime directly... It compiles. Please anyone tell me if I'm wrong here.
The idea is to use the tokio Runtime directly (docs have many examples):
use tokio::runtime::Runtime;
// Executes a web server. Returns a result object with an error object
// if the server stopped unexpectedly
fn run_and_stop_web_server(addr: std::net::SocketAddr) -> Result<(), Error> {
let server_builder = Server::try_bind(&addr)?;
let server = server_builder.serve(move || {
let handler: Handler = Handler::new();
hyper::service::service_fn(move |req| handler.serve(req))
});
let mut rt = Runtime::new()?;
rt.block_on(server)?;
rt.shutdown_now();
Result::Ok(())
}
Maybe you can store the error into a local variable and return that:
let mut result = Result::Ok(());
hyper::rt::run(server.map_err(|e| { result = Result::Error (e); });
result

Resources