athene 1.2.6

A simple and lightweight rust web framework based on Hyper, with routing similar to Java SpringBoot and Go gin
Documentation

example

Usage -- Future

use athene::handler::Handler;
use athene::prelude::*;
use std::future::Future;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Default)]
pub struct User {
    pub name: String,
    pub age: i32,
}

impl Handler for User {
    type Responder = Builder;
    type Future = Box<dyn Future<Output = Self::Responder> + Unpin + Send>;

    fn handle(&self, req: Request) -> Self::Future {
        Box::new(Box::pin(async move {
            let uri = req.uri().path();
            let method = req.method();
            let peer_addr = req.peer_addr();

            let builder = Builder::new();
            builder.text(format!("uri : {}, method: {}, peer_addr: {}",uri,method,peer_addr))
         }))
    }
}

impl User {
    pub fn hello(_: Request) -> impl Future<Output = impl Responder> {
        async { "hello" }
    }

    pub fn get_user(mut req: Request) -> impl Future<Output = impl Responder> {
        async move {
            let user = req.parse::<User>().await?;
            Ok::<_, Error>(json!(&user))
        }
    }

    pub fn basic_router(r: Router) -> Router {
        r.get("/hello", Self::hello)
        .post("/user", Self::default())
        .post("/get_user", Self::get_user)
    }
}

#[tokio::main]
pub async fn main() -> Result<()> {
    let app = athene::new()
        .router(|r| {
            r.get("/",|_: Request| async {
                "Hello World"
            })
        })
        .router(User::basic_router)
        .build();

    app.listen("127.0.0.1:7878").await
}

Usage MacroController

No need to use athene's validate feature

use athene::prelude::*;
use serde::{Deserialize,Serialize};

use validator::Validate;
#[derive(Serialize, Deserialize,Validate,Default)]
pub struct UserController {
    #[validate(email)]
    pub username: String,
    #[validate(range(min = 18, max = 20))]
    pub age: u16,
}

// http://127.0.0.1:7878/api/v1/user
#[controller(prefix = "api", version = 1, name = "user")]
impl UserController {

    #[get("/*/**")]
    pub async fn match_any_route(&self, req: Request) -> impl Responder {
        let uri_path = req.uri().path().to_string();
        let method = req.method().to_string();
        (200, format!("uri : {}, method: {}", uri_path, method))
    }

    #[delete("/{username}/{age}")]
    pub async fn delete_by_param(&self, username: String, age: Option<u16>) -> impl Responder {
        (200, format!("username is : {}, and age is : {:?}", username, age))
    }

    #[get("/get_query_1")]
    pub async fn get_query_1(&self, username: String, age: u16) -> impl Responder {
        (200,json!(&Self {
                username,
                age,
            }))
    }

    #[get("/get_query_2")]
    pub async fn get_query_2(&self, request: Request) -> impl Responder {
        let user = request.query::<Self>()?;
        user.validate()?; // User will be validated
        Ok::<_, Error>((200, json!(&user)))
    }

    // Context-Type : application/json  Or application/x-www-form-urlencoded
    // Context-Type : application/msgpack Or application/cbor
    #[post("/parse_body")]
    #[get("/parse_body")]
    async fn parse_body(&self, mut req: Request) -> impl Responder {
        let user = req.parse::<Self>().await?;
        user.validate()?; // User will be validated
        Ok::<_, Error>((
            200,
            format!("username = {} and age = {}", user.username, user.age),
        ))
    }

    // Context-Type : application/multipart-formdata
    #[post("/files")]
    async fn files(&self, mut req: Request) -> impl Responder {
        let files = req.files("files").await?;
        let fist_file_name = files[0].name().unwrap();
        let second_file_name = files[1].name().unwrap();
        Ok::<_, Error>((
            200,
            format!(
                "fist {}, second {}",
                fist_file_name, second_file_name
            ),
        ))
    }

    // Content-Disposition: application/octet-stream
    #[get("/download")]
    pub async fn download(&self,_req: Request) -> impl Responder {
        let mut res = status!(hyper::StatusCode::OK);
        res.write_file("templates/author.txt", DispositionType::Attachment)?;
        Ok::<_, Error>(res)
    }
}

#[tokio::main]
pub async fn main() -> Result<()> {

    let app = athene::new()
        .router(|r|r.controller(UserController::default()));

    let app = app.build();

    app.listen("127.0.0.1:7878").await
}

Usage BasicController

use athene::prelude::*;
use serde::{Deserialize, Serialize};

#[derive(Default, Deserialize, Serialize)]
pub struct AtheneController {
    pub label: String,
    pub keyword: String,
}

impl Controller for AtheneController {

    const BASE_PATH: &'static str = "/api/v1/athene";

    fn method(&self) -> Vec<ControllerMethod<Self>>
    where
        Self: Sized,
    {
        ControllerBuilder::new()

        .post("/add", Self::add)
        .delete("/{label}/{keyword}", Self::delete)
        .put("/update", Self::update)
        .get("/get", Self::get)
        .build()
    }
}

impl AtheneController {

    // http://127.0.0.1:7878/api/v1/athene/add
    pub async fn add(&self, mut req: Request) -> impl Responder {
        let obj = req.parse::<Self>().await?;
        Ok::<_, Error>((200, json!(obj)))
    }

    // http://127.0.0.1:7878/api/v1/athene/
    pub async fn delete(&self, mut req: Request) -> impl Responder {
        let lable = req.param::<String>("label")?;
        let keyword = req.param::<String>("keyword")?;

        Ok::<_,Error>((200,format!("lable = {},keyword = {}",lable,keyword)))
    }

    // http://127.0.0.1:7878/api/v1/athene/update
    async fn update(&self, mut req: Request) -> impl Responder {
        let obj = req.parse::<Self>().await?;
        Ok::<_, Error>((200, json!(obj)))
    }

    // http://127.0.0.1:7878/api/v1/athene/get
    async fn get(&self, req: Request) -> impl Responder {
        #[derive(Deserialize, Serialize)]
        struct QueryParam<'a> {
            label: &'a str,
            keyword: &'a str,
        }

        let arg = req.query::<QueryParam>()?;
        let res = json!(&arg);
        Ok::<_, Error>(res)
    }
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    let app = athene::new()
    .router(|r|r.controller(AtheneController::default()))
    .build();

    app.listen("127.0.0.1:7878").await
}

Usage Middleware and RouterGroup

use athene::prelude::*;
use headers::{authorization::Bearer, Authorization};
use serde::{Deserialize, Serialize};
use tracing::info;

#[derive(Serialize, Deserialize)]
pub struct User {
    pub username: String,
    pub age: u16,
}

pub async fn user_params(mut req: Request) -> impl Responder {
    let username = req.param("username")?;
    let age = req.param::<u16>("age")?;
    Ok::<_, Error>((200, json!(&User { username, age })))
}

pub async fn login(mut req: Request) -> impl Responder {
    if let Ok(user) = req.parse::<User>().await {
        (
            200,
            format!("username: {} , age: {} ", user.username, user.age),
        )
    } else {
        (400, String::from("Bad user format"))
    }
}

pub async fn sign_up(mut req: Request) -> impl Responder {
    let user = req.parse::<User>().await?;
    Ok::<_, Error>((200, json!(&user)))
}

pub async fn read_body(mut req: Request) -> impl Responder {
    let body = req.body_bytes().await?;
    Ok::<_, Error>((200, body))
}

pub async fn body_to_string(mut req: Request) -> impl Responder {
    let body = req.body_string().await?;
    Ok::<_, Error>((200, body))
}

pub fn user_router(r: Router) -> Router {
    r.group("///user/**/")
        .get("/{username}/{age}", user_params)
        .post("/login", login)
        .post("/sign_up", sign_up)
        .put("/read_body", read_body)
        .post("/body_to_string", body_to_string)
}

//   ============================ Middleware Fuction ============================
pub async fn log_middleware(ctx: Context, next: &'static dyn Next) -> Result<Context, Error> {
    info!(
        "new request on path: {}",
        ctx.state.request_unchecked().uri().path()
    );

    let ctx = next.next(ctx).await?;

    info!(
        "new response with status: {}",
        ctx.state.response_unchecked().status()
    );
    Ok(ctx)
}

// ================================ Middleware  ======================
struct ApiKeyMiddleware {
    api_key: String,
}

#[middleware]
impl ApiKeyMiddleware {
    async fn next(&self, ctx: Context, chain: &dyn Next) -> Result<Context, Error> {
        if let Some(bearer) = ctx
            .state
            .request_unchecked()
            .header::<Authorization<Bearer>>()
        {
            let token = bearer.0.token();
            if token == self.api_key {
                info!(
                    "Handler {} will be used",
                    ctx.metadata.name.unwrap_or("unknown")
                );
                chain.next(ctx).await
            } else {
                info!("Invalid token");
                Ok(ctx)
            }
        } else {
            info!("Not Authenticated, ");
            Ok(ctx)
        }
    }
}

#[tokio::main]
pub async fn main() -> Result<()> {
    tracing_subscriber::fmt().compact().init();

    let app = athene::new()
        .router(user_router)
        .middleware(|m| {
            m.apply(log_middleware, vec!["/"], None).apply(
                ApiKeyMiddleware {
                    api_key: "athene".to_string(),
                },
                vec!["/user/login", "/user/sign_up"],
                vec!["/user/read_body","/user/body_to_string"],
            )
        })
        .build();
    app.listen("127.0.0.1:7878").await
}

Support for WebSocket use feature websocket

use athene::prelude::*;

pub async fn websocket_hello(_req: Request,mut tx: WebSocketSender,mut rx: WebSocketReceiver) -> Result<()> {

    while let Some(msg) = rx.receive().await? {
        tx.send(msg).await?;
    }
    Ok(())
}

// ws://127.0.0.1:7878/ws/world  http://www.jsons.cn/websocket/
pub async fn websocket_world(_req: Request,mut tx: WebSocketSender,mut rx: WebSocketReceiver) -> Result<()> {

    while let Some(msg) = rx.receive().await? {
        tx.send(msg).await?;
    }
    Ok(())
}

pub fn websocket_router(r: Router) -> Router {
    r.group("/ws").ws("/world", websocket_world)
}

#[tokio::main]
pub async fn main() -> Result<()> {
    let app = athene::new()
    .router(|r|{
        r

        // ws://127.0.0.1:7878/ws/index  http://www.jsons.cn/websocket/
        .ws("/ws/index", |_req, mut tx, mut rx| async move {

            while let Some(msg) = rx.receive().await? {
                tx.send(msg).await?;
            }
            Ok(())
        })

        // ws://127.0.0.1:7878/ws/hello  http://www.jsons.cn/websocket/
        .post("/ws/hello",new_ws(websocket_hello))
    }).
    router(websocket_router).
    build();

    app.listen("127.0.0.1:7878").await
}

Support loading static files use feature static_file

use athene::prelude::*;
use tera::{Context, Tera};
use serde::Serialize;

pub async fn tera_template(_: Request) -> impl Responder {
    #[derive(Serialize)]
    struct Awesome<'a> {
        url: &'a str,
        name: &'a str,
    }

    let tera = Tera::new("templates/*").unwrap();
    let mut ctx = Context::new();
    ctx.insert("title", "Welcome to rust-lang world!");
    ctx.insert(
        "product",
        &vec![
            Awesome {
                url: "https://github.com/rust-lang",
                name: "rust-lang",
            },
            Awesome {
                url: "https://github.com/lapce/lapce",
                name: "lapce",
            },
        ],
    );

    let love = tera.render("index.html", &ctx).unwrap();
    with!(love, "text/html; charset=utf-8")
    // html!(love)
}

#[tokio::main]
async fn main() -> Result<()> {
    let app = athene::new()
    .router(|r|{
        r.static_files("/template/public/*", "public")
        .get("/template/tera_template", tera_template)
    }).build();

    app.listen("127.0.0.1:7878").await
}

Usage feature athene_body

If you want to validate parameters, you need to use the validate feature of athene


use athene::prelude::*;
use serde::{Deserialize, Serialize};

use validator::Validate;
#[derive(Serialize, Deserialize, Validate, Default)]
pub struct UserController {
    #[validate(email)]
    pub username: String,
    #[validate(range(min = 18, max = 20))]
    pub age: u16,
}

// http://127.0.0.1:7878/api/v1/user
#[controller(prefix = "api", version = 1, name = "user")]
impl UserController {

    // Context-Type : application/x-www-form-urlencoded
    #[post("/body_form")]
    #[get("/body_form")] // User will be validated
    async fn body_form(&self, user: Form<Self>) -> impl Responder {
        (200, user)
    }

    // Context-Type : application/json
    #[post("/body_json")] // User will be validated
    async fn body_json(&self, user: Json<Self>) -> impl Responder {
        (200, user)
    }

    #[post("/body_form2")]
    #[validator(exclude("user"))] // The user parameter will not be validated
    async fn body_form2(&self, user: Form<Self>) -> impl Responder {
        let user = user.0;
        (200, Form(user))
    }

    #[post("/body_json2")]
    #[validator(exclude("user"))] // The user parameter will not be validated
    async fn body_json2(&self, user: Option<Json<Self>>) -> impl Responder {
        match user {
            Some(user) => (200, user),
            None => (400, Json(Self::default())),
        }
    }
}

#[tokio::main]
pub async fn main() -> Result<()> {
    let app = athene::new().router(|r| r.controller(UserController::default()));
    let app = app.build();
    app.listen("127.0.0.1:7878").await
}