athene 1.1.3

A simple and lightweight rust web framework based on hyper
Documentation

example

Useage

use askama::Template;
use athene::headers::{authorization::Bearer, Authorization};
use athene::prelude::*;
use athene::{html, json, status, text, with};
use serde::{Deserialize, Serialize};
use tera::{Context, Tera};
use tracing::{debug, error, info, warn};

// askama template
pub async fn askama_template(_: Request) -> impl Responder {
    #[derive(Serialize, Template)]
    #[template(path = "favorite.html")]
    pub struct Favorite<'a> {
        title: &'a str,
        time: i32,
    }

    let love = Favorite {
        title: "rust_lang",
        time: 1314,
    };

    html!(love.render()?)
}

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

    let tera = Tera::new("templates/*")?;
    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)?;
    with!(love, "text/html; charset=utf-8")
    // html!(love)
}

struct Logger;

#[async_trait]
impl Middleware for Logger {
    async fn apply(&self, req: Request, next: Next<'_>) -> Result {
        let method = req.method().to_string();
        let uri = req.uri().to_string();
        debug!(%method, %uri, "request");
        let result = next.next(req).await?;
        log_response(method, uri, &result);
        Ok(result)
    }
}

fn log_response(method: String, uri: String, resp: &Response) {
    let status = resp.as_ref().status();
    if status.is_server_error() {
        error!(%method, %uri, %status, "response");
    } else if status.is_client_error() {
        warn!(%method, %uri, %status, "response");
    } else {
        info!(%method, %uri, %status, "response");
    }
}

pub struct AuthCheck;

#[async_trait]
impl Middleware for AuthCheck {
    async fn apply(&self, req: Request, next: Next<'_>) -> Result {
        let auth = req.header::<Authorization<Bearer>>();
        match auth {
            None => return Ok(Response::status(StatusCode::UNAUTHORIZED)),
            Some(bearer) => {
                info!("bearer token: {}", bearer.0.token());
                next.next(req).await
            }
        }
    }
}

pub fn base_router() -> Router {
    let mut app = athene::new();
    app.get("/askama_template", askama_template);
    app.get("/tera_template", tera_template);
    app.get("/param/:name", |req: Request| async move {
            let name = req.param("name").unwrap();
            name.to_string()
        });

    app.get("/query", |req: Request| async move {
            #[derive(Serialize, Deserialize)]
            struct Book {
                title: String,
                is_publish: bool,
            }
            json!(req.query::<Book>()?)
        });

    // Context-Type : application/x-www-form-urlencoded
    app.post("/form-urlencoded", |mut req: Request| async move {
            #[derive(Serialize, Deserialize)]
            struct Rust {
                version: String,
                edition: String,
            }
            json!(req.parse::<Rust>().await?)
        });

    // Context-Type : application/json
    app.post("/json", |mut req: Request| async move {
            #[derive(Serialize, Deserialize)]
            struct User {
                name: String,
                age: i32,
            }
            let user = req.parse::<User>().await?;
            json!(user)
        });

    // Context-Type : application/multipart-formdata
    app.post("/file", |mut req: Request| async move {
            let file = req.file("file").await?;
            text!(file.name().unwrap().to_string())
        });

    // Context-Type : application/multipart-formdata
    app.post("/files", |mut req: Request| async move {
            let files = req.files("files").await?;
            let fist_file_name = files[0].name().unwrap();
            let second_file_name = files[1].name().unwrap();
            let name = format!("fist {}, second {}", fist_file_name, second_file_name);
            text!(name)
        });

    // Content-Disposition: application/octet-stream
    app.get("/download", |_| async {
            let mut res = Response::default();
            res.write_file("templates/author.txt", DispositionType::Attachment)?;
            Ok(res)
        });
    app
}

pub async fn websocket_hello(req: Request,mut tx: WebSocketSender,mut rx: WebSocketReceiver) -> Result<()> {
    info!("uri = {} , method = {}",req.uri().path(),req.method());
    
    while let Some(msg) = rx.receive().await? {
        tx.send(msg).await?;
    }
    Ok(())
}

pub fn websocket_router() -> Router {
    let mut app = athene::new();

    // websocket  ws://127.0.0.1:8888/websocket/ws/admin  http://www.jsons.cn/websocket/
    app.ws("/ws/index", |_req, mut tx, mut rx| async move {
            while let Some(msg) = rx.receive().await? {
                tx.send(msg).await?;
            }
            println!("websocket closed");
            Ok(())
        });

    // websocket  ws://127.0.0.1:8888/websocket/ws/hello  http://www.jsons.cn/websocket/
    app.get("/ws/hello", new_ws(websocket_hello));

    app
}

pub fn api_router(auth: AuthCheck) -> Router {
    let mut api = athene::new();
    api.middleware(auth);

    api.get("check", |req: Request| async move {
        let mut res = status!(StatusCode::OK);
        res.text(req.uri().to_string())?;
        Ok(res)
    });

    api.get("user/:name", |req: Request| async move {
        let mut res = status!(StatusCode::OK);
        let resp = format!("uri = {}\nparams = {:?}\n", req.uri(), req.params());
        res.text(resp)?;
        Ok(res)
    });
    api
}

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

    let mut app = athene::new();
    app.middleware(Logger);
    app.get("/", |_| async { "Hello world!\n\n" });
    app.group("/base", base_router());
    // http://127.0.0.1:8888/base/tera_template
    app.static_files("/base/public/*", "public");
    app.group("/websocket", websocket_router());
    app.group("/api/:version", api_router(AuthCheck));

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

templates/index.html

<title>{{title}}</title>

<ul>
  {% for one in product -%}
  <li><a href="{{ one.url | safe }}">{{ one.name }}</a></li>
  {%- endfor %}

  <img src="public/icon.jpg" alt="A rocket icon." height=200 width=200 />
</ul>

templates/favorite.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Rust is one of my favorite programming languages</title>
</head>

<body>
    <h4>{{title}}</h4>
    <p>{{time}}</p>
</body>

</html>