athene 1.4.2

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

example

This version is built on hyper v0.14 and crate async_trait is used

1、 Usage MacroController

use athene's validate and file feature

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

static INDEX_HTML: &str = r#"<!DOCTYPE html>
<html>
    <head>
        <title>Upload Test</title>
    </head>
    <body>
        <h1>Upload Test</h1>
        <form action="/api/v1/user/upload" method="post" enctype="multipart/form-data">
            <input type="file" name="file" />
            <input type="submit" value="upload" />
        </form>
    </body>
</html>
"#;

#[derive(Serialize, Deserialize, Validate, Default, Debug)]
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 {

    // http://127.0.0.1:7878/api/v1/user/xxx
    #[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();
        (201, format!("uri : {}, method: {}", uri_path, method))
    }

    // http://127.0.0.1:7878/api/v1/user/web/18
    #[delete("/{username}/{age}")] // username and age will not be validated
    pub async fn delete_by_param(&self, username: String, age: Option<u16>) -> impl Responder {
        (
            202,
            format!("username is : {}, and age is : {:?}", username, age),
        )
    }

    // http://127.0.0.1:7878/api/v1/user/get_query_1/?username=admin&age=29
    #[get("/get_query_1")] // username and age will not be validated
    pub async fn get_query_1(&self, username: String, age: u16) -> impl Responder {
        (203, Json(Self { username, age }))
    }

    // http://127.0.0.1:7878/api/v1/user/get_query_2/?username=admin@qq&age=19
    #[get("/get_query_2")] // user will be validated
    pub async fn get_query_2(&self, user: Query<Self>) -> impl Responder {
        (203, Json(user.0))
    }

    // http://127.0.0.1:7878/api/v1/user/parse_json_body
    // Context-Type : application/json
    #[post("/parse_json_body")]
    #[get("/parse_json_body")] // user will be validated
    async fn parse_json_body(&self, user: Json<Self>) -> impl Responder {
        Ok::<_, Error>((206, user))
    }

    // http://127.0.0.1:7878/api/v1/user/parse_form_body
    // Context-Type : application/x-www-form-urlencoded
    #[post("/parse_form_body")]
    #[get("/parse_form_body")] // user will be validated
    async fn parse_form_body(&self, user: Form<Self>) -> impl Responder {
        Ok::<_, Error>((206, user))
    }

    // http://127.0.0.1:7878/api/v1/user/parse_body_vec
    #[post("/parse_body_vec")]
    #[get("/parse_body_vec")]
    async fn parse_body_vec(&self,mut req: Request) -> impl Responder {
        let vector = req.parse_body::<Vec<u8>>().await?;
        Ok::<_, Error>((StatusCode::OK, vector))
    }
    
    // http://127.0.0.1:7878/api/v1/user/parse_body_string
    #[post("/parse_body_string")]
    #[get("/parse_body_string")]
    async fn parse_body_string(&self,mut req: Request) -> impl Responder {
        let vector = req.parse_body::<String>().await?;
        Ok::<_, Error>((StatusCode::OK, vector))
    }

    // http://127.0.0.1:7878/api/v1/user/file
    #[post("/file")]
    pub async fn file(&self, mut req: Request) -> impl Responder {
        let file = req.file("file").await?;
        let file_name = file
            .name()
            .ok_or_else(|| Error::Other("file not found".to_string()))?;
        Ok::<_, Error>((200, file_name.to_string()))
    }

    // http://127.0.0.1:7878/api/v1/user/files
    #[post("/files")]
    pub async fn files(&self, mut req: Request) -> impl Responder {
        let files = req.files("files").await?;
        let fist_file_name = files[0]
            .name()
            .ok_or_else(|| Error::Other("file not found".to_string()))?;
        let second_file_name = files[1]
            .name()
            .ok_or_else(|| Error::Other("file not found".to_string()))?;
        Ok::<_, Error>((
            200,
            format!(
                "fist_file_name: {}, second_file_name: {}",
                fist_file_name, second_file_name
            ),
        ))
    }

    // http://127.0.0.1:7878/api/v1/user/upload
    #[post("/upload")]
    pub async fn upload(&self, mut req: Request) -> impl Responder {
        let res = req.upload("file", "temp").await?;
        if res > 0 {
            Ok::<_, Error>((200, "File uploaded successfully"))
        } else {
            Ok::<_, Error>((400, "File upload failed"))
        }
    }

    // http://127.0.0.1:7878/api/v1/user/uploads
    #[post("/uploads")]
    pub async fn uploads(&self, mut req: Request) -> impl Responder {
        let msg = req.uploads("files", "temp").await?;
        Ok::<_, Error>((200, msg))
    }

    // http://127.0.0.1:7878/api/v1/user/download
    // Content-Disposition: application/octet-stream
    #[get("/download")]
    pub async fn download(&self, _req: Request) -> impl Responder {
        let mut res = Response::new();
        res.write_file("temp/ws.rs", DispositionType::Attachment)?;
        Ok::<_, Error>((200, "Download successful"))
    }
}

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

    let app = athene::new().router(|r| {
        r.get("/api/v1/user/upload", |_: Request| async {
            Html(INDEX_HTML)
        })
        .controller(UserController::default())
    });

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

2、Usage BasicController

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

#[derive(Default, Serialize, Deserialize)]
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>((300, format!("lable = {},keyword = {}", lable, keyword)))
    }

    // http://127.0.0.1:7878/api/v1/athene/update
    // Context-Type : application/json  Or application/x-www-form-urlencoded
    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 = Response::new();
        Ok::<_, Error>(res.json(&arg))
    }
}

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

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

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

3、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,
}

// 127.0.0.1:7878/user/admin/18
pub async fn user_params(mut req: Request) -> impl Responder {
    let username = req.param("username")?;
    let age = req.param::<u16>("age")?;
    Ok::<_, Error>((300, Json(User { username, age })))
}

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

// 127.0.0.1:7878/user/sign_up
pub async fn sign_up(mut req: Request) -> impl Responder {
    let user = req.parse::<User>().await?;
    Ok::<_, Error>((211, Json(user)))
}

// 127.0.0.1:7878/user/body_to_vec_u8
pub async fn body_to_vec_u8(mut req: Request) -> impl Responder {
    let body = req.parse_body::<Vec<u8>>().await?;
    Ok::<_, Error>((500, body))
}

// 127.0.0.1:7878/user/body_to_string
pub async fn body_to_string(mut req: Request) -> impl Responder {
    let body = req.parse_body::<String>().await?;
    Ok::<_, Error>((404, 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("/body_to_vec_u8", body_to_vec_u8)
        .post("/body_to_string", body_to_string)
}

//   ============================ Middleware Fuction ============================
pub async fn log_middleware(ctx: Context, next: &'static dyn Next) -> Result {
    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 {
        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/body_to_vec_u8","/user/body_to_string"],
            )
        });
        
    app.listen("127.0.0.1:7878").await
}

4、Load static files and directories

use athene's static_file feature

use athene::prelude::*;

#[tokio::main]
pub async fn main() -> Result<()> {
    let app = athene::new().router(|r| {
        
        // Using this in a production environment
        // let r = r.static_dir("/**", "athene");

        // If you want to test the functionality, use this
        let r = r.get("/**", StaticDir::new("athene").with_listing(true));
        r
    });
    app.listen("127.0.0.1:7879").await
}