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
}
Related
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>))
my "main" file
mod router;
mod student;
use std::sync::Arc;
use crate::router::init_router;
use crate::router::Memory;
use actix_web::{App, HttpServer};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
dotenv::dotenv().ok();
let repo = Arc::new(Memory::repository());
let host = std::env::var("SERVER.HOST").unwrap_or("127.0.0.1".to_string());
let port = std::env::var("SERVER.PORT").unwrap_or("8080".to_string());
let url = format!("{}:{}", host, port);
println!("url: {}", &url);
HttpServer::new(move || {
App::new()
.data(repo.clone()) // shared
.configure(init_router)
})
.bind(&url)?
.run()
.await
}
my file: "router.rs"
use std::sync::Arc;
use crate::student::Student;
use actix_web::{get, web, HttpResponse, Responder};
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
pub struct Memory {
pub students: Vec<Student>,
}
impl Memory {
fn new() -> Self {
Memory {
students: Vec::new(),
}
}
pub fn repository() -> Self{
Self {
students: vec![
{Student::new("1".to_string(), "Daniel".to_string(), 19)},
{Student::new("2".to_string(), "Lucia".to_string(), 17)},
{Student::new("3".to_string(), "Juan".to_string(), 14)}
]
}
}
}
#[get("/student/list/all")]
async fn list_all(repo: web::Data<Arc<Memory>>) -> impl Responder {
HttpResponse::Ok().json(&***repo)
}
#[get("/student/list/by-id/{id_student}")]
async fn list_by_id(web::Path(id_student): web::Path<String>, repo: web::Data<Arc<Memory>>) -> impl Responder {
HttpResponse::Ok().json(&***repo.students.into_iter().find(|x| *x.id == id_student))
}
pub fn init_router(config: &mut web::ServiceConfig) {
config.service(list_all);
config.service(list_by_id);
}
and my file: "student.rs"
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
pub struct Student{
pub id: String,
pub nombre: String,
pub calificacion: u32,
}
impl Student{
pub fn new(id: String, nombre: String, calificacion: u32) -> Self{
Self{id, nombre, calificacion}
}
}
I want to show a student in the following path: 127.0.0.1:3000/student/list/by-id/1
but i have the following error
error[E0614]: type `std::vec::IntoIter<Student>` cannot be dereferenced
--> src\router.rs:43:33
|
43 | HttpResponse::Ok().json((&***repo.lock().unwrap().students.into_iter()).find(|x| *x.id == id_student))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error
because I get the error, I don't know what is wrong. Please I need help I am new to this programming language.
The dot operating will smartly dereference the pointer, so the following will compile:
&repo.students.iter().find(|x| *x.id == id_student)
the Arc will be dereferenced when accessing students from repo, which will give a reference to the Vec, and .iter() will give you a non-consuming iterator, which you can then map over (.into_iter() will require the Vec to be copied and consumed)
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;
}
What is the simplest way to get json from the HttpRequest into a struct you created. Here is the main.rs
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.data(web::JsonConfig::default().limit(4096))
.data(connect())
.service(web::resource("/insert").route(web::post().to(handlers::tours::insert)))
})
.bind("127.0.0.1:8088")
.unwrap()
.run()
.await
}
And here is the struct in model/tour.rs:
#[derive(Serialize, Deserialize, Insertable)]
pub struct TourForm {
pub name: String,
}
And here is the handler in handlers/tours.rs::
pub async fn insert(
tour_form: web::Json<TourForm>,
pool: web::Data<MysqlPool>,
) -> Result<HttpResponse, HttpResponse> {
Ok(HttpResponse::Ok().json(&tour_form.name))
}
I tried this variation because I thought this would make the code very simple:
pub async fn insert(
tour_form: TourForm,
pool: web::Data<MysqlPool>,
) -> Result<HttpResponse, HttpResponse> {
Ok(HttpResponse::Ok().json(&tour_form.name))
}
But got the error:
^^ the trait `actix_web::extract::FromRequest` is not implemented for `model::tour::TourForm`
Should I implement the FromRequest function into the TourForm struct or is there an easier way?
I was able to get the TourForm object out of the web::Json simply by doing tour_form.0
pub async fn insert(
tour_form: web::Json<TourForm>,
pool: web::Data<MysqlPool>,
) -> Result<HttpResponse, HttpResponse> {
Ok(HttpResponse::Ok().json(&tour_form.0))
}
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;
}