hypers 0.4.2

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 hypers::{prelude::*, Handler};
use std::time::Instant;
use utoipa::{IntoParams, OpenApi, ToSchema};

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

#[derive(Serialize, Deserialize, IntoParams, ToSchema, Debug, Extractor)]
#[into_params(parameter_in = Query)]
#[hypers(extract(source(from = "path"), source(from = "query"), source(from = "body"),))]
pub struct Data {
    pub id: u16,
    pub name: String,
    pub age: u16,
}

// Request Cookies
#[get("/parse_cookies")]
pub async fn parse_cookies(
    hypers: CookieParam<String, true>,
    rust: CookieParam<String, true>,
) -> impl Responder {
    Ok::<_, Error>((
        200,
        format!("first cookie is {}, second cookie is {}", hypers, rust),
    ))
}

// Request Headers
#[utoipa::path(
    get,
    path = "/user/parse_header",
    tag = "parse request headers",
    responses(
        (status = 200, description = "Parse header from request successfully", body = HeaderParams),
        (status = 404, description = "Parse header from request failed")
    )
)]
#[get("/parse_header")]
pub async fn parse_header(req: &mut Request) -> impl Responder {
    let header_params = req.parse_header::<HeaderParams>()?;
    /*
       ....
    */
    Ok::<_, Error>((200, header_params))
}

// Url Path Params
#[utoipa::path(
    delete,
    path = "/user/parse_param/{id}/{name}/{age}",
    tag = "parse request url path params",
    params(
        ("id" = u16, Path, description = "Id of readme to delete"),
        ("age" = u16, Path, description = "Age of readme to delete"),
        ("name" = String, Path, description = "Name of readme to delete"),
    ),
    responses(
        (status = 200, description = "Parse Url Path Params successfully",body = Data),
        (status = 404, description = "Parse Url Path Params failed")
    ),
    security(
        ("api_key" = [])
    )
)]
#[delete("parse_param/:id/:name/:age")]
pub async fn parse_param(
    req: &mut Request,
    id: Path<u16>,
    name: Path<String>,
    age: Path<u16>,
) -> impl Responder {
    let app_state_user = req.get::<Data>("user");
    println!("app_state_user = {:?}", app_state_user);
    (
        200,
        Data {
            id: id.0,
            name: name.0,
            age: age.0,
        },
    )
}

// Url Query Params
#[utoipa::path(
    get,
    path = "/user/parse_query",
    tag = "parse request url query params",
    params(
        ("id" = u16, Query, description = "Id of readme to get"),
        ("age" = u16, Query, description = "Age of readme to get"),
        ("name" = String, Query, description = "Name of readme to get"),
    ),
    responses(
        (status = 200, description = "Parse query successfully", body = QueryParams)
    )
)]
#[get("parse_query")]
pub async fn parse_query(
    id: Query<u16, true>,
    name: Query<String, true>,
    age: Query<u16, true>,
) -> impl Responder {
    /*
       .....
    */
    Ok::<_, Error>((
        200,
        Data {
            id: id.inner(),
            name: name.inner(),
            age: age.inner(),
        },
    ))
}

// Context-Type : application/x-www-form-urlencoded
#[utoipa::path(
    patch,
    path = "/user/parse_form_patch",
    tag = "parse request body",
    request_body(
        content = Data,
        content_type = "application/x-www-form-urlencoded",
    ),
    responses(
        (status = 200, description = "FormParams created successfully", body = Data),
        (status = 409, description = "FormParams already exists")
    )
)]
#[patch("parse_form_patch")]
pub async fn parse_form_patch(user: Data) -> impl Responder {
    //let user = user.0;
    /*
       .....
    */
    Ok::<_, Error>((200, user))
}

// Context-Type : application/json
#[utoipa::path(
    post,
    path = "/user/parse_json",
    tag = "parse request body",
    request_body(
        content = Data,
        content_type = "application/json",
    ),
    responses(
        (status = 201, description = "Data item created successfully", body = Data),
        (status = 409, description = "Data already exists")
    )
)]
#[post("parse_json")]
pub async fn parse_json(user: Json<Data>) -> impl Responder {
    /*
        ......
    */
    Ok::<_, Error>((200, user))
}

// Context-Type : multipart/form-data Form Fields
#[post("multipart_form")]
pub async fn multipart_form(form_data: Data) -> impl Responder {
    /*
       ....
    */
    Ok::<_, Error>((200, form_data))
}

// Context-Type : multipart/form-data Files
#[post("multipart_file")]
pub async fn multipart_file(req: &mut 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),
    ))
}

// Url Query Params
#[utoipa::path(
    get,
    path = "/base/query",
    tag = "url query params",
    params(
        ("name" = Vec<String>, Query, description = "Url Query Params name"),
        ("age" = u16, Query, description = "Url Query Params age"),
    ),
    responses(
        (status = 200, description = "successfully, response = String"),
        (status = 404, description = "failed")
    )
)]
#[get("/query")]
pub async fn query(req: &mut Request) -> impl Responder {
    let name = req.query::<Vec<String>>("name")?;
    let age = req.query::<u16>("age")?;
    let key = req.get::<&str>("key")?;
    println!("app_state = {}", key);
    /*
       .....
    */
    Some((200, format!("name = {:?} , age = {}", name, age)))
}

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

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

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

#[get("/html")]
pub async fn html() -> impl Responder {
    Text::Html("<html><body>hello</body></html>")
}

// Websocket  ws://127.0.0.1:7878/hello/ws  http://www.jsons.cn/websocket/
#[get(":name/ws")]
pub async fn websocket(req: &mut Request) -> impl Responder {
    let name = req.param::<String>("name").unwrap_or_default();
    WebSocketUpgrade::new()
        .upgrade(req, |mut ws| async move {
            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.unwrap();
                        }
                        Message::Close(_) => break,
                        _ => {}
                    }
                }
            }
        })
        .await
}

// Middleware Function
#[handler]
pub async fn stat_time(req: &mut Request, next: &Next) -> Result {
    // Before executing the request processing function
    let start_time = Instant::now();

    // Calling subsequent request processing functions
    let res = next.handle(req, next).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
#[handler]
pub async fn app_state(req: &mut Request, next: &Next) -> Result {
    let user = Data {
        id: 1,
        name: "admin".to_owned(),
        age: 21,
    };
    req.set("user", user);
    req.set("key", "Hello World");
    next.handle(req, next).await
}

#[derive(OpenApi)]
#[openapi(
    info(title = "Base Api", description = "Base Api description"),
    paths(query),
    tags(
        (name = "base", description = " base router")
    )
)]
struct ApiDoc1;

#[derive(OpenApi)]
#[openapi(
    info(title = "User Api", description = "User Api description"),
    paths(parse_header, parse_param, parse_query, parse_form_patch, parse_json),
    components(schemas(HeaderParams,Data,)),
    tags(
        (name = "user", description = " user router ")
    )
)]
struct ApiDoc2;

// Write router like a tree
fn main() -> Result<()> {
    // The Root Router
    let mut root = Router::new("");
    root.get("/*", StaticDir::new("src").listing(true));
    root.handler(websocket);

    // The Sub Router
    let mut base = Router::new("base");
    // Add Middleware  ( Middleware can be added to any routing node )
    base.hook(app_state, vec!["/"], None);
    base.handlers(handlers!(query, set_cookies, html))
        .openapi(ApiDoc1::openapi());

    // The Sub Router
    let mut user = Router::new("user");
    // Add Middleware  ( Middleware can be added to any routing node )
    user.hook(stat_time, vec!["/user"], None);
    user.handlers(handlers!(
        parse_cookies,
        parse_header,
        parse_param,
        parse_query,
        parse_form_patch,
        parse_json,
        multipart_form,
        multipart_file
    ))
    .openapi(ApiDoc2::openapi());

    // Add sub router to the root router
    root.push(base);
    root.push(user);

    // Accessing in a browser  http://127.0.0.1:7878/swagger-ui/
    root.swagger("swagger-ui");

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

    // Start Server
    // Not Use SSL/TLS
    #[cfg(not(any(feature = "rustls", feature = "native_tls")))]
    {
        hypers::run(root, "127.0.0.1:7878")
    }

    // Use SSL/TLS
    #[cfg(feature = "rustls")]
    {
        let tls = RustlsConfig::new()
            .cert(include_bytes!("./certs/cert.pem").to_vec())
            .key(include_bytes!("./certs/key.pem").to_vec());
        hypers::run(root, "127.0.0.1:7878", tls)
    }

    // Use SSL/TLS
    #[cfg(feature = "native_tls")]
    {
        let tls = NativeTls::from_pkcs12(include_bytes!("./certs/identity.p12"), "mypass")?;
        hypers::run(root, "127.0.0.1:7878", tls)
    }
}