Using Actix' middleware

I'm looking at the examples for Actix' middleware, and am seeing a pattern. 90% of the code is almost always the same, the only difference - and actual implementation of your code - happens in the function call.

This is from the examples:

// Struct containing some types/variables/properties/... which are always the same
pub struct SayHi;

// Implementing the Middleware-factory 'Transform' into our custom middleware-thing.
impl<S, B> Transform<S> for SayHi
where
    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Request = ServiceRequest;
    type Response = ServiceResponse<B>;
    type Error = Error;
    type InitError = ();
    type Transform = SayHiMiddleware<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ok(SayHiMiddleware { service }) //new_transform function calls our actual middleware, in this case 'SayHiMiddleware'
    }
}

// The actual middleware
pub struct SayHiMiddleware<S> {
    service: S,
}

// Implementing our actual middleware
impl<S, B> Service for SayHiMiddleware<S>
where
    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Request = ServiceRequest;
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.service.poll_ready(cx)
    }

    fn call(&mut self, req: ServiceRequest) -> Self::Future {
        //THIS IS WHERE TO PUT THE CODE WHICH WILL ACTUALLY GET EXECUTED
    }
}

So actually, I just need to copy/paste the whole block from code the whole time, and in the function call I just write the code that actually needs to be executed.

If I'd like to write a middleware to check if a session is already set (see this question), and if not, already set one; I'd write it like this:

    fn call(&mut self, req: ServiceRequest) -> Self::Future {
        if !session_exists(req) {
            set_session_token(req)
        }

       else {
           //return Http::OK
       }
    }

But what is the return type Self::Future? I actually need to return a HttpResponse with a Set-Cookie in the header. Is it possible to return a HttpResponse from middleware, and still go to another route-function after the middleware?

Any other things I need to know about this middleware?

In the middleware you return a ServiceResponse, because you may an intermediate step to the actual response to the client.

The ServiceResponse contains an HttpResponse.

In the middleware you can of course change the HttpResponse, have a look at the examples project on github:

https://github.com/actix/examples/blob/master/middleware/src/read_response_body.rs

1 Like

Alright cool, I'll try to implement it.

This is my full implementation:

use actix_cors::Cors;
use actix_web::{web, get, middleware, Result, Responder, HttpResponse, HttpServer, App, HttpRequest, Error, dev::ServiceResponse, dev::ServiceRequest, dev::Transform, dev::Service};
use actix_web::http::StatusCode;
use actix_session::{Session, CookieSession};
use serde::Deserialize;
use mysql::prelude::Queryable;
use chrono::{DateTime, NaiveDate, Utc};
use std::{collections::HashMap, sync::Mutex, sync::RwLock, pin::Pin, future::Future, task::Context, task::Poll, future::Ready};
use dashmap::DashMap;


#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate pwhash;

use regex::Regex;

lazy_static! {
    static ref db : mysqldata::MySQLData = mysqldata::MySQLData::init_connection(&String::from("rustsite"), &String::from("root"), &String::from("toor"));
    static ref SESSIONS : RwLock<DashMap<String, Option<u8>>> = RwLock::new(DashMap::new());
}


// Module for handling sessions
pub mod session_handler {
    use actix_web::{HttpRequest, HttpResponse};
    use rand::{thread_rng, Rng};
    use rand::distributions::Alphanumeric;
    use sha2::{Sha256, Digest};
    use uuid::Uuid;
    use crate::*;


    // Generate session_token. Concatenation of sha256(ip_address) and a random string,
    // ip_address is included to avoid collission
    fn generate_session_token() -> String {
        Uuid::new_v4().to_string()
    }


    // Check if a session exists by reading the cookie 'session' in the request-header
    pub fn session_exists(req: HttpRequest) -> bool {
        get_session_token(req).map_or(false, |s| s != "")
    }

    // Retrieves the session_token from the request.
    // if none cookie is found this Option<String> will be None, otherwise
    // it will contain the session_token after unwrapping
    pub fn get_session_token(req: HttpRequest) -> Option<String> {
        Some(req.headers().get("session").and_then(|r| r.to_str().ok()).unwrap().to_string())

    }

    // Make a HttpResponse with the NEW session_token in the header.
    pub fn set_session_token(req: HttpRequest) -> HttpResponse {
        let ip_address = req
            .connection_info()
            .remote_addr()
            .unwrap()
            .to_string();

        let session_token = generate_session_token();
        let header_string = format!("session={}; Secure; HttpOnly", session_token);

        add_session(session_token);

        HttpResponse::Ok()
            .content_type("plain/text")
            .header("Set-Cookie", header_string)
            .body("")
    }

    // Save this session in the session_database
    pub fn add_session(token: String) {
        SESSIONS.read().unwrap().insert(token, None);
    }

    // Delete a session from the session_database
    pub fn delete_session_by_token(token: String) {
        SESSIONS.read().unwrap().remove(&token);
    }
    // Unimplemented...
    pub fn delete_session_by_userid(id: u8) {
        unimplemented!()
    }

    // A user is linked to a session after authenticating himself. This Function
    // can retrieve which user is connected to which session.
    pub fn get_user_by_session(token: String) -> Option<u8> {
        *SESSIONS.read().unwrap().get(&token).unwrap()
    }

    pub fn set_user_by_session(token: String, user_id: u8) {
        if let Some(mut value) = SESSIONS.read().unwrap().get_mut(&token) {
            *value = Some(user_id);
        }
    }

}

// Module for MySQLs
pub mod mysqldata {
    use mysql::{prelude::Queryable, params, chrono::NaiveDate, Row};
    use crate::{RegistrationForm, User};


    pub struct MySQLData {
        pub conn: mysql::Pool
    }



    impl MySQLData {
        //Return MySQLData object with conn field
        pub fn init_connection(database_name : &String, database_user : &String, database_pass : &String) -> MySQLData {
            let conn_str : String = format!("mysql://{user}:{pass}@localhost/{name}", user=database_user, pass=database_pass, name=database_name);
            let conn = mysql::Pool::new(conn_str);

            match conn {
                Ok(_) => {

                    println!("Connection to {} successful!", database_name);

                    MySQLData {
                        conn: conn.unwrap()
                    }
                },
                Err(e) => {
                    eprintln!("Connection to {} failed: {}", database_name, e.to_string());
                    std::process::exit(-1);
                }
            }
        }

        // Initialize all needed tables
        pub fn init_tables(&self) {
            let mut conn = self.conn.get_conn().unwrap();

            conn.query_drop(
                "CREATE TABLE IF NOT EXISTS users (
                    ID BIGINT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
                    username VARCHAR(255) NOT NULL,
                    email VARCHAR(256) NOT NULL,
                    birthdate DATETIME,
                    password VARCHAR(255) NOT NULL
                )"
            );

            println!("Tables initialized...");
        }

        pub fn add_user(&self, data: RegistrationForm) -> bool {
            let mut conn = self.conn.get_conn().unwrap();

            let date = NaiveDate::parse_from_str(&*data.birthdate, "%d-%m-%Y").unwrap();

            //let mut insert_id : i64 = -1;
            let execute_result = conn.exec_drop(r"
            INSERT INTO users (
                username, email, birthdate, password
            ) VALUES (
                :username, :email, :birthdate, :password
            )", params! {
                "username" => &data.username,
                "email" => data.email,
                "birthdate" => date.format("%Y-%m-%d").to_string(),
                "password" => pwhash::bcrypt::hash(data.password).unwrap()
            });

            //insert_id = self.conn.get_conn().unwrap().last_insert_id() as i64;


            match execute_result {
                Ok(_) => {
                    println!("User {} added!", data.username);
                    true
                }
                Err(e) => {
                    eprintln!("Failed to add user {}: {}", data.username, e.to_string());
                    false
                }
            }

            //insert_id
        }

        pub fn read_user_by_id(&self, id: u8) -> RegistrationForm {
            unimplemented!()
        }

        pub fn read_user_by_email(&self, email: String) -> User {
            let mut conn = self.conn.get_conn().unwrap();

            let stmt = conn.prep("SELECT id, username, email, birthdate, password FROM users WHERE email=:email").unwrap();
            let mut row : Row = conn.exec_first(&stmt, params! {
                "email" => email
            }).unwrap().unwrap();


            User {
                id: row.take("id").unwrap(),
                username: row.take("username").unwrap(),
                email: row.take("email").unwrap(),
                birthdate: row.take("birthdate").unwrap(),
                password_hash: row.take("password").unwrap(),
            }

        }
    }
}

#[derive(Deserialize)]
pub struct RegistrationForm {
    username: String,
    email: String,
    birthdate: String,
    password: String,
    rp_password: String
}
impl RegistrationForm {
    pub fn is_valid(&self) -> bool {
        if self.username.is_empty() ||
        self.email.is_empty() ||
        self.birthdate.is_empty() ||
        self.password.is_empty() ||
        self.rp_password.is_empty() {
            println!("User {} did not fill in all inputs!", self.username);
            return false
        }

        if self.password != self.rp_password {
            println!("User {} did not repeat the password correctly!", self.username);
            return false
        }

        let re_date = Regex::new(r"^\d{2}-\d{2}-\d{4}$").unwrap();
        if !re_date.is_match(&*&self.birthdate) {
            println!("{} for user {} is not a valid date", self.birthdate, self.username);
            return false
        }

        let re_email = Regex::new(r"[\w._%+-]+@[\w.-]+\.[a-zA-Z]{2,3}").unwrap();
        if !re_email.is_match(&*&self.email) {
            println!("{} for user {} is not a valid mail-address", self.email, self.username);
            return false
        }

        true
    }
}

#[derive(Deserialize)]
pub struct LoginForm {
    email: String,
    password: String
}

#[derive(Deserialize)]
pub struct User {
    id: u64,
    username: String,
    email: String,
    birthdate: chrono::NaiveDateTime,
    password_hash: String
}




//Function which gets executed with correct route
async fn register(form: web::Form<RegistrationForm>, session: Session) -> String {

    let registration_form = form.into_inner();

    if !registration_form.is_valid() {
        return String::from("error");
    }

    let add_result = db.add_user(registration_form);

    match add_result {
        true => String::from("ok"),
        false => String::from("error")
    }


}

async fn login(form: web::Form<LoginForm>, req: HttpRequest) -> String {

    let login_form = form.into_inner();

    let user : Option<User> = Some(db.read_user_by_email(login_form.email));

    match user {
        None => String::from("error"),
        Some(user) => {
            if pwhash::bcrypt::verify(login_form.password, &*user.password_hash) {
                String::from("ok")
            } else {
                String::from("error")
            }
        }
    }
}

// middleware
pub struct SendSession;
impl<S, B> Transform<S> for SendSession
where
    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Request =  ServiceRequest;
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = Ready<Result<Self::transform, Self::InitError>>;
    type Transform = SendSessionMiddleware<S>;
    type InitError = ();

    fn new_transform(&self, service: S) -> Self::Future {
        Ok(SendSessionMiddleware { service })
    }


}

pub struct SendSessionMiddleware<S> {
    service: S,
}

impl<S, B> Service for SendSessionMiddleware<S>
where
    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Request = ServiceRequest;
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.service.poll_ready(cx)
    }

    fn call(&mut self, req: ServiceRequest) -> Self::Future {
        println!("Middleware test");
        let fut = self.service.call(req);

        Box::pin(async move {
            let res = fut.await?;

            Ok(res)
        })
    }

}


#[actix_rt::main]
async fn main() -> std::io::Result<()> {

    db.init_tables();

    HttpServer::new(|| App::new()
        .wrap(
            Cors::new()
            .allowed_origin("http://rustsite.local")
            .finish()
        )
        .wrap(
            CookieSession::signed(&[0; 32])
            .secure(false)
        )
        .service(
            web::resource("/register").route(web::post().to(register))
        )
        .service(
            web::resource("/login").route(web::post().to(login))
        )
    )
    .bind("127.0.0.1:8088")?
    .run()
    .await
}

I get the following errors:

error[E0658]: use of unstable library feature 'future_readiness_fns'
 --> src/main.rs:8:113
  |
8 | use std::{collections::HashMap, sync::Mutex, sync::RwLock, pin::Pin, future::Future, task::Context, task::Poll, future::Ready};
  |                                                                                                                 ^^^^^^^^^^^^^
  |
  = note: see issue #70921 <https://github.com/rust-lang/rust/issues/70921> for more information

error[E0658]: use of unstable library feature 'future_readiness_fns'
   --> src/main.rs:319:19
    |
319 |     type Future = Ready<Result<Self::transform, Self::InitError>>;
    |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    = note: see issue #70921 <https://github.com/rust-lang/rust/issues/70921> for more information

error[E0220]: associated type `transform` not found for `Self`
   --> src/main.rs:319:38
    |
319 |     type Future = Ready<Result<Self::transform, Self::InitError>>;
    |                                      ^^^^^^^^^ help: there is an associated type with a similar name: `Transform`

error: aborting due to 3 previous errors; 8 warnings emitted

Some errors have detailed explanations: E0220, E0658.
For more information about an error, try `rustc --explain E0220`.
error: could not compile `rustSite1`.

It says I'm using an unstable library, but I just followed the example.

First you have a typo in your code: line 319 where:

type Future = Ready<Result<Self::transform, Self::InitError>>;

Should be:

type Future = Ready<Result<Self::Transform, Self::InitError>>;

It's Transform with capital T.

I try tobuild your code and after fixed this issue I had other errors, but connected to stuff not actix related.

I added futures v0.3 in my cargo.toml, and maybe the edition should be 2018 in [package] section of the same file.

1 Like

This is the working code:

use actix_cors::Cors;
use actix_web::{web, get, middleware, Result, Responder, HttpResponse, HttpServer, App, HttpRequest, Error, dev::ServiceResponse, dev::ServiceRequest, dev::Transform, dev::Service};
use actix_web::http::StatusCode;
use actix_session::{Session, CookieSession};
use serde::Deserialize;
use mysql::prelude::Queryable;
use chrono::{DateTime, NaiveDate, Utc};
use std::{collections::HashMap, sync::Mutex, sync::RwLock, pin::Pin, task::Context, task::Poll};
use dashmap::DashMap;
use futures::future::{ok, Ready};
use futures::Future;


#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate pwhash;

use regex::Regex;

lazy_static! {
    static ref db : mysqldata::MySQLData = mysqldata::MySQLData::init_connection(&String::from("rustsite"), &String::from("root"), &String::from("toor"));
    static ref SESSIONS : RwLock<DashMap<String, Option<u8>>> = RwLock::new(DashMap::new());
}


// Module for handling sessions
pub mod session_handler {
    use actix_web::{HttpRequest, HttpResponse};
    use rand::{thread_rng, Rng};
    use rand::distributions::Alphanumeric;
    use sha2::{Sha256, Digest};
    use uuid::Uuid;
    use crate::*;


    // Generate session_token. Concatenation of sha256(ip_address) and a random string,
    // ip_address is included to avoid collission
    fn generate_session_token() -> String {
        Uuid::new_v4().to_string()
    }


    // Check if a session exists by reading the cookie 'session' in the request-header
    pub fn session_exists(req: HttpRequest) -> bool {
        get_session_token(req).map_or(false, |s| s != "")
    }

    // Retrieves the session_token from the request.
    // if none cookie is found this Option<String> will be None, otherwise
    // it will contain the session_token after unwrapping
    pub fn get_session_token(req: HttpRequest) -> Option<String> {
        Some(req.headers().get("session").and_then(|r| r.to_str().ok()).unwrap().to_string())

    }

    // Make a HttpResponse with the NEW session_token in the header.
    pub fn set_session_token(req: HttpRequest) -> HttpResponse {
        let ip_address = req
            .connection_info()
            .remote_addr()
            .unwrap()
            .to_string();

        let session_token = generate_session_token();
        let header_string = format!("session={}; Secure; HttpOnly", session_token);

        add_session(session_token);

        HttpResponse::Ok()
            .content_type("plain/text")
            .header("Set-Cookie", header_string)
            .body("")
    }

    // Save this session in the session_database
    pub fn add_session(token: String) {
        SESSIONS.read().unwrap().insert(token, None);
    }

    // Delete a session from the session_database
    pub fn delete_session_by_token(token: String) {
        SESSIONS.read().unwrap().remove(&token);
    }
    // Unimplemented...
    pub fn delete_session_by_userid(id: u8) {
        unimplemented!()
    }

    // A user is linked to a session after authenticating himself. This Function
    // can retrieve which user is connected to which session.
    pub fn get_user_by_session(token: String) -> Option<u8> {
        *SESSIONS.read().unwrap().get(&token).unwrap()
    }

    pub fn set_user_by_session(token: String, user_id: u8) {
        if let Some(mut value) = SESSIONS.read().unwrap().get_mut(&token) {
            *value = Some(user_id);
        }
    }

}

// Module for MySQLs
pub mod mysqldata {
    use mysql::{prelude::Queryable, params, chrono::NaiveDate, Row};
    use crate::{RegistrationForm, User};


    pub struct MySQLData {
        pub conn: mysql::Pool
    }



    impl MySQLData {
        //Return MySQLData object with conn field
        pub fn init_connection(database_name : &String, database_user : &String, database_pass : &String) -> MySQLData {
            let conn_str : String = format!("mysql://{user}:{pass}@localhost/{name}", user=database_user, pass=database_pass, name=database_name);
            let conn = mysql::Pool::new(conn_str);

            match conn {
                Ok(_) => {

                    println!("Connection to {} successful!", database_name);

                    MySQLData {
                        conn: conn.unwrap()
                    }
                },
                Err(e) => {
                    eprintln!("Connection to {} failed: {}", database_name, e.to_string());
                    std::process::exit(-1);
                }
            }
        }

        // Initialize all needed tables
        pub fn init_tables(&self) {
            let mut conn = self.conn.get_conn().unwrap();

            conn.query_drop(
                "CREATE TABLE IF NOT EXISTS users (
                    ID BIGINT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
                    username VARCHAR(255) NOT NULL,
                    email VARCHAR(256) NOT NULL,
                    birthdate DATETIME,
                    password VARCHAR(255) NOT NULL
                )"
            );

            println!("Tables initialized...");
        }

        pub fn add_user(&self, data: RegistrationForm) -> bool {
            let mut conn = self.conn.get_conn().unwrap();

            let date = NaiveDate::parse_from_str(&*data.birthdate, "%d-%m-%Y").unwrap();

            //let mut insert_id : i64 = -1;
            let execute_result = conn.exec_drop(r"
            INSERT INTO users (
                username, email, birthdate, password
            ) VALUES (
                :username, :email, :birthdate, :password
            )", params! {
                "username" => &data.username,
                "email" => data.email,
                "birthdate" => date.format("%Y-%m-%d").to_string(),
                "password" => pwhash::bcrypt::hash(data.password).unwrap()
            });

            //insert_id = self.conn.get_conn().unwrap().last_insert_id() as i64;


            match execute_result {
                Ok(_) => {
                    println!("User {} added!", data.username);
                    true
                }
                Err(e) => {
                    eprintln!("Failed to add user {}: {}", data.username, e.to_string());
                    false
                }
            }

            //insert_id
        }

        pub fn read_user_by_id(&self, id: u8) -> RegistrationForm {
            unimplemented!()
        }

        pub fn read_user_by_email(&self, email: String) -> User {
            let mut conn = self.conn.get_conn().unwrap();

            let stmt = conn.prep("SELECT id, username, email, birthdate, password FROM users WHERE email=:email").unwrap();
            let mut row : Row = conn.exec_first(&stmt, params! {
                "email" => email
            }).unwrap().unwrap();


            User {
                id: row.take("id").unwrap(),
                username: row.take("username").unwrap(),
                email: row.take("email").unwrap(),
                birthdate: row.take("birthdate").unwrap(),
                password_hash: row.take("password").unwrap(),
            }

        }
    }
}

#[derive(Deserialize)]
pub struct RegistrationForm {
    username: String,
    email: String,
    birthdate: String,
    password: String,
    rp_password: String
}
impl RegistrationForm {
    pub fn is_valid(&self) -> bool {
        if self.username.is_empty() ||
        self.email.is_empty() ||
        self.birthdate.is_empty() ||
        self.password.is_empty() ||
        self.rp_password.is_empty() {
            println!("User {} did not fill in all inputs!", self.username);
            return false
        }

        if self.password != self.rp_password {
            println!("User {} did not repeat the password correctly!", self.username);
            return false
        }

        let re_date = Regex::new(r"^\d{2}-\d{2}-\d{4}$").unwrap();
        if !re_date.is_match(&*&self.birthdate) {
            println!("{} for user {} is not a valid date", self.birthdate, self.username);
            return false
        }

        let re_email = Regex::new(r"[\w._%+-]+@[\w.-]+\.[a-zA-Z]{2,3}").unwrap();
        if !re_email.is_match(&*&self.email) {
            println!("{} for user {} is not a valid mail-address", self.email, self.username);
            return false
        }

        true
    }
}

#[derive(Deserialize)]
pub struct LoginForm {
    email: String,
    password: String
}

#[derive(Deserialize)]
pub struct User {
    id: u64,
    username: String,
    email: String,
    birthdate: chrono::NaiveDateTime,
    password_hash: String
}




//Function which gets executed with correct route
async fn register(form: web::Form<RegistrationForm>, session: Session) -> String {

    let registration_form = form.into_inner();

    if !registration_form.is_valid() {
        return String::from("error");
    }

    let add_result = db.add_user(registration_form);

    match add_result {
        true => String::from("ok"),
        false => String::from("error")
    }


}

async fn login(form: web::Form<LoginForm>, req: HttpRequest) -> String {

    let login_form = form.into_inner();

    let user : Option<User> = Some(db.read_user_by_email(login_form.email));

    match user {
        None => String::from("error"),
        Some(user) => {
            if pwhash::bcrypt::verify(login_form.password, &*user.password_hash) {
                String::from("ok")
            } else {
                String::from("error")
            }
        }
    }
}

// middleware
pub struct SendSession;
impl<S, B> Transform<S> for SendSession
where
    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Request =  ServiceRequest;
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;
    type Transform = SendSessionMiddleware<S>;
    type InitError = ();

    fn new_transform(&self, service: S) -> Self::Future {
        ok(SendSessionMiddleware { service })
    }


}

pub struct SendSessionMiddleware<S> {
    service: S,
}

impl<S, B> Service for SendSessionMiddleware<S>
where
    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Request = ServiceRequest;
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.service.poll_ready(cx)
    }

    fn call(&mut self, req: ServiceRequest) -> Self::Future {
        println!("Middleware test");
        let fut = self.service.call(req);

        Box::pin(async move {
            let res = fut.await?;

            Ok(res)
        })
    }

}


#[actix_rt::main]
async fn main() -> std::io::Result<()> {

    db.init_tables();

    HttpServer::new(|| App::new()
        .wrap(
            Cors::new()
            .allowed_origin("http://rustsite.local")
            .finish()
        )
        .wrap(
            CookieSession::signed(&[0; 32])
            .secure(false)
        )
        .service(
            web::resource("/register").route(web::post().to(register))
        )
        .service(
            web::resource("/login").route(web::post().to(login))
        )
    )
    .bind("127.0.0.1:8088")?
    .run()
    .await
}

Cargo

[package]
name = "rustSite1"
version = "0.1.0"
authors = ""
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix-rt = "1.1.1"
actix-web = "3.0.2"
actix-cors = "0.3.0"
actix-session = "0.4.0"
serde = "1.0.116"
lazy_static = "1.4.0"
regex = "1.3.9"
mysql = "19.0.1"
chrono = "0.4.15"
pwhash = "0.3"
rand = "0.7"
sha2 = "0.9.1"
dashmap = "2.0.2"
uuid = { version = "0.8.1", features = ["serde", "v4"] }
futures = "0.3.5"

Thanks!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.