athene 1.2.8

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

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 = Builder::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())
    });
    let app = app.build();
    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 = Builder::new();
        Ok::<_, Error>(res.json(&arg))
    }
}

#[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
}

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/read_body","/user/body_to_string"],
            )
        })
        .build();
    app.listen("127.0.0.1:7878").await
}