hypers 0.2.0

A simple rust web framework based on hyper 1.0.0-rc.4
Documentation

HTTP Request URL Parameters Syntax

Pattern Kind Description
:name Normal Matches a path piece, excludes /
:name? Optional Matches an optional path piece, excludes /
/:name?/ /:name? OptionalSegment Matches an optional path segment, excludes /, prefix or suffix should be /
+ :name+ OneOrMore Matches a path piece, includes /
* :name* ZeroOrMore Matches an optional path piece, includes /
/*/ /* /:name*/ /:name* ZeroOrMoreSegment Matches zero or more path segments, prefix or suffix should be /

⚡️ Quick Start

use hypers's full feature

use headers::{authorization::Bearer, Authorization};
use hypers::prelude::*;
use std::time::Instant;
use tracing::info;

#[derive(Serialize, Deserialize)]
pub struct CookieJarParams {
    pub hypers: String,
    pub rust: String,
}

#[derive(Serialize, Deserialize)]
pub struct HeaderParams {
    pub host: Option<String>,
    #[serde(rename(deserialize = "user-agent"))]
    pub user_agent: Option<String>,
    pub accept: Option<String>,
}

#[derive(Debug, Default, Serialize, Deserialize, Validate)]
pub struct UserController {
    pub id: Option<u32>,
    #[validate(email)]
    pub email: Option<String>,
    #[validate(range(min = 18, max = 20))]
    pub age: Option<u16>,
}

// http://127.0.0.1:7878/api/v1/user
#[controller(prefix = "api", version = 1, name = "user")]
impl UserController {
    // Request Cookies
    #[get("parse_cookies")]
    pub async fn parse_cookies(req: Request) -> impl Responder {
        let cookie_jar = req.parse_cookies::<CookieJarParams>()?;
        Ok::<_, Error>((200, Json(cookie_jar)))
    }

    // Request Headers
    #[get("/parse_header")]
    pub async fn parse_header(req: Request) -> impl Responder {
        let header_params = req.parse_header::<HeaderParams>()?;
        Ok::<_, Error>((200, Json(header_params)))
    }

    // Url Path Params
    #[delete("parse_param/:id/:name/:age")]
    pub async fn parse_param(req: Request, id: u32, name: String, age: u16) -> impl Responder {
        let app_state = req.get::<Self>("user");
        (
            200,
            format!(
                "id = {}, name = {},age = {}\n app_state = {:#?}",
                id, name, age, app_state
            ),
        )
    }

    // Url Path Params
    #[get("parse_param2/:id/:email/:age")]
    pub async fn parse_param2(req: Request) -> impl Responder {
        let user = req.parse_param::<UserController>()?;
        user.validate()?; // user will be validate
        Ok::<_, Error>((200, Json(user)))
    }

    // Url Query Params
    #[get("/parse_query")] // user will be validate
    pub async fn parse_query(query_params: Query<Self>) -> impl Responder {
        Ok::<_, Error>((200, Json(query_params.0)))
    }

    // Context-Type : application/x-www-form-urlencoded
    #[get("/parse_form_get")]
    #[patch("/parse_form_patch")]
    #[validator(exclude("user"))] // user will not be validate
    pub async fn parse_form_get(user: Form<UserController>) -> impl Responder {
        (200, user)
    }

    // Context-Type : application/json
    #[post("/parse_json")] // user will be validate
    pub async fn parse_json(user: Json<UserController>) -> impl Responder {
        (200, user)
    }

    // Context-Type : multipart/form-data Form Fields
    #[post("/multipart_form")]
    pub async fn multipart_form(mut req: Request) -> impl Responder {
        let user = req.parse::<Self>().await?;
        user.validate()?; // user will be validate
        Ok::<_, Error>((200, Json(user)))
    }

    // Context-Type : multipart/form-data Files
    #[post("/multipart_file")]
    pub async fn multipart_file(mut req: Request) -> impl Responder {
        let file = req.file("file").await?;
        let file_name = file.name()?;
        let file_name = file_name.to_string();

        let img = req.files("imgs").await?;
        let imgs_name = img
            .iter()
            .map(|m| m.name().unwrap().to_string())
            .collect::<Vec<String>>()
            .join(",");

        Some((
            200,
            format!("file_name = {}, imgs_name = {}", file_name, imgs_name),
        ))
    }
}

struct BasicController;

// http://127.0.0.1:7878/basic
#[controller(name = "basic")]
impl BasicController {
    // Request Headers
    #[get("/header")]
    pub async fn header(req: Request) -> impl Responder {
        let host = req.header::<String>("host")?;
        let user_agent = req.header::<String>("user-agent")?;
        Some((
            200,
            format!("host = {} , user_agent = {}", host, user_agent),
        ))
    }

    // Url Path Params
    #[delete("/param/:name/:age")]
    pub async fn param(req: Request) -> impl Responder {
        let name = req.param::<String>("name")?;
        let age = req.param::<u16>("age")?;
        Some((200, format!("name = {} , age = {}", name, age)))
    }

    // Url Query Params
    #[get("/query")]
    pub async fn query(req: Request) -> impl Responder {
        let name = req.query::<Vec<String>>("name")?;
        let age = req.query::<u16>("age")?;
        let key = req.get::<&str>("key")?;
        Some((
            200,
            format!("name = {:?} , age = {}\n, app_state = {}", name, age, key),
        ))
    }

    #[get("/set_cookies")]
    pub async fn set_cookies(_req: Request) -> impl Responder {
        let mut cookie1 = Cookie::new("hypers", "hypers2023");
        cookie1.set_path("/api/v1/user");

        let mut cookie2 = Cookie::new("rust", "rust2023");
        cookie2.set_path("/api/v1/user");

        let mut cookie_jar = CookieJar::new();
        cookie_jar.add(cookie1);
        cookie_jar.add(cookie2);
        (200, cookie_jar)
    }
}

// Websocket  ws://127.0.0.1:7878/hello/ws  http://www.jsons.cn/websocket/
pub async fn websocket(req: Request, mut ws: WebSocket) -> Result<()> {
    let name = req.param::<String>("name").unwrap_or_default();
    while let Ok(msg) = ws.receive().await {
        if let Some(msg) = msg {
            match msg {
                Message::Text(text) => {
                    let text = format!("{},{}", name, text);
                    ws.send(Message::Text(text)).await?;
                }
                Message::Close(_) => break,
                _ => {}
            }
        }
    }
    Ok(())
}

// Middleware Function
pub async fn stat_time(req: Request, netx: Next) -> Result {
    // Before executing the request processing function
    let start_time = Instant::now();

    // Calling subsequent request processing functions
    let res = netx.next(req).await?;

    // After the execution of the request processing function
    let elapsed_time = start_time.elapsed();

    println!(
        "The current request processing function takes time :{:?}",
        elapsed_time
    );
    Ok(res)
}

// Middleware Function
pub async fn logger(req: Request, next: Next) -> Result {
    // Before executing the request processing function
    let uri = req.uri().path();
    info!("uri = {:?}", uri);
    // Calling subsequent request processing functions
    let res = next.next(req).await;
    // After the execution of the request processing function
    res
}

// Middleware Function
pub async fn app_state(mut req: Request, next: Next) -> Result {
    let user = UserController {
        id: Some(1),
        email: Some("admin@gmail.com".to_owned()),
        age: Some(21),
    };
    req.set("user", user);
    req.set("key", "Hello World");
    next.next(req).await
}

struct ApiKeyMiddleware {
    api_key: String,
}

#[derive(Deserialize, Serialize)]
pub struct MiddlewareError<T> {
    pub code: u32,
    pub msg: String,
    pub data: T,
}

// Middleware Macro
#[middleware]
impl ApiKeyMiddleware {
    async fn hook(&self, req: Request, next: Next) -> Result<Response> {
        if let Some(bearer) = req.header_typed_get::<Authorization<Bearer>>() {
            let token = bearer.0.token();
            if token == self.api_key {
                next.next(req).await
            } else {
                info!("Invalid token");
                let res = MiddlewareError {
                    code: 400,
                    msg: String::from("Invalid token"),
                    data: "Invalid token",
                };
                Err(Error::Response(401, json!(res)))
            }
        } else {
            info!("Not Authenticated");
            Err(Error::Response(401, json!("Not Authenticated")))
        }
    }
}

// Write router like a tree
fn main() -> Result<()> {
    tracing_subscriber::fmt().compact().init();

    // The Root Router
    let mut root = Router::new("/");
    root.get("/*", StaticDir::new("hypers").listing(true))
        .ws(":name/ws", websocket);

    // Add Middleware  ( Middleware can be added to any routing node )
    root.hook(logger, vec!["/basic"], None)
        .hook(
            app_state,
            vec!["/api/v1/user/parse_param/:id/:name/:age", "/basic/query"],
            None,
        )
        .hook(stat_time, vec!["/api/v1/user"], None)
        .hook(
            ApiKeyMiddleware {
                api_key: "hypers".to_owned(),
            },
            vec!["/basic"],
            vec!["/api/v1/user"],
        );

    // Add sub router to the root router
    root.controller(UserController::default());
    root.controller(BasicController);

    println!("root router = {:#?}", root);

    // Registering the root router into the server
    let app = hypers::new(root);

    app.run("127.0.0.1:7878")
}