#web-framework #框架 #异步 #Web #HTTP

roa

受koajs启发的异步Web框架,轻量但强大

23次发布

0.6.1 2022年1月17日
0.5.3 2021年3月29日
0.5.2 2020年7月15日
0.5.0-rc.52020年3月31日

343HTTP服务器

Download history 15/week @ 2024-03-14 7/week @ 2024-03-21 44/week @ 2024-03-28 44/week @ 2024-04-04 14/week @ 2024-04-11 20/week @ 2024-04-18 21/week @ 2024-04-25 13/week @ 2024-05-02 18/week @ 2024-05-09 26/week @ 2024-05-16 18/week @ 2024-05-23 19/week @ 2024-05-30 20/week @ 2024-06-06 17/week @ 2024-06-13 16/week @ 2024-06-20 12/week @ 2024-06-27

66次每月下载
6 个Crate中使用

MIT许可

235KB
4.5K SLoC

Build status codecov Rust Docs Crate version Download License: MIT

简介

罗亚是一个受koajs启发的异步Web框架,轻量但强大。

应用

罗亚应用是一个由中间件和端点以堆栈方式组成的结构。

必做的“Hello World”应用

use roa::App;
use roa::preload::*;
use tracing::info;
use std::error::Error as StdError;

#[tokio::main]
async fn main() -> Result<(), Box<dyn StdError>> {
    let app = App::new().end("Hello, World");
    app.listen("127.0.0.1:8000", |addr| {
        info!("Server is listening on {}", addr)
    })?
    .await?;
    Ok(())
}

端点

端点是一个请求处理器。

罗亚内置了一些端点。

  • 函数式端点

    一个普通的函数式端点是一个具有以下签名的异步函数:async fn mut Context) -> Result

    use roa::{App, Context, Result};
    
    async fn endpoint(ctx: &mut Context) -> Result {
        Ok(())
    }
    
    let app = App::new().end(endpoint);
    
  • Ok端点

    是一个总是返回Ok(())的端点

    let app = roa::App::new().end(());
    
  • 状态端点

    Status是一个总是返回Err(Status)的端点

    use roa::{App, status};
    use roa::http::StatusCode;
    let app = App::new().end(status!(StatusCode::BAD_REQUEST));
    
  • 字符串端点

    将字符串写入正文。

    use roa::App;
    
    let app = App::new().end("Hello, world"); // static slice
    let app = App::new().end("Hello, world".to_owned()); // string
    
  • 重定向端点

    重定向到URI。

    use roa::App;
    use roa::http::Uri;
    
    let app = App::new().end("/target".parse::<Uri>().unwrap());
    

级联

像koajs一样,中间件通过调用next.await挂起并传递控制权给“下游”。然后在next.await返回时,控制权流回“上游”。

以下示例响应为“Hello World”,但首先请求通过x-response-time和日志中间件来标记请求开始的时间,然后继续通过端点传递控制权。当一个中间件调用next时,函数挂起并将控制权传递给下一个中间件或端点。端点被调用后,堆栈将展开,每个中间件将被恢复以执行其上游行为。

use roa::{App, Context, Next};
use roa::preload::*;
use tracing::info;
use std::error::Error as StdError;
use std::time::Instant;

#[tokio::main]
async fn main() -> Result<(), Box<dyn StdError>> {
    let app = App::new()
        .gate(logger)
        .gate(x_response_time)
        .end("Hello, World");
    app.listen("127.0.0.1:8000", |addr| {
        info!("Server is listening on {}", addr)
    })?
    .await?;
    Ok(())
}

async fn logger(ctx: &mut Context, next: Next<'_>) -> roa::Result {
    next.await?;
    let rt = ctx.load::<String>("x-response-time").unwrap();
    info!("{} {} - {}", ctx.method(), ctx.uri(), rt.as_str());
    Ok(())
}

async fn x_response_time(ctx: &mut Context, next: Next<'_>) -> roa::Result {
    let start = Instant::now();
    next.await?;
    let ms = start.elapsed().as_millis();
    ctx.store("x-response-time", format!("{}ms", ms));
    Ok(())
}

状态处理

您可以捕获或直接抛出next返回的状态。

use roa::{App, Context, Next, status};
use roa::preload::*;
use roa::http::StatusCode;
use tokio::task::spawn;
use tracing::info;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = App::new()
        .gate(catch)
        .gate(not_catch)
        .end(status!(StatusCode::IM_A_TEAPOT, "I'm a teapot!"));
    app.listen("127.0.0.1:8000", |addr| {
        info!("Server is listening on {}", addr)
    })?
    .await?;
    Ok(())
}

async fn catch(_ctx: &mut Context, next: Next<'_>) -> roa::Result {
    // catch
    if let Err(status) = next.await {
        // teapot is ok
        if status.status_code != StatusCode::IM_A_TEAPOT {
            return Err(status);
        }
    }
    Ok(())
}

async fn not_catch(ctx: &mut Context, next: Next<'_>) -> roa::Result {
    next.await?; // just throw
    unreachable!()
}

status_handler

应用有一个status_handler来处理顶级中间件抛出的状态。这就是status_handler

use roa::{Context, Status};
pub fn status_handler<S>(ctx: &mut Context<S>, status: Status) {
    ctx.resp.status = status.status_code;
    if status.expose {
        ctx.resp.write(status.message);
    } else {
        tracing::error!("{}", status);
    }
}

路由器。

Roa提供了一个可配置且可嵌套的路由器。

use roa::preload::*;
use roa::router::{Router, get};
use roa::{App, Context};
use tokio::task::spawn;
use tracing::info;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let router = Router::new()
        .on("/:id", get(end)); // get dynamic "/:id"
    let app = App::new()
        .end(router.routes("/user")?); // route with prefix "/user"
    app.listen("127.0.0.1:8000", |addr| {
        info!("Server is listening on {}", addr)
    })?
    .await?;
    
    Ok(())
}

async fn end(ctx: &mut Context) -> roa::Result {
    // get "/user/1", then id == 1.
    let id: u64 = ctx.must_param("id")?.parse()?;
    // do something
    Ok(())
}

查询

Roa提供了一个中间件 query_parser

use roa::preload::*;
use roa::query::query_parser;
use roa::{App, Context};
use tokio::task::spawn;
use tracing::info;

async fn must(ctx: &mut Context) -> roa::Result {
    // request "/?id=1", then id == 1.
    let id: u64 = ctx.must_query("id")?.parse()?;
    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = App::new()
        .gate(query_parser)
        .end(must);
    app.listen("127.0.0.1:8080", |addr| {
        info!("Server is listening on {}", addr)
    })?
    .await?;     
    Ok(())
}

其他模块

  • body: 更方便地处理正文。
  • compress: 支持透明的内容压缩。
  • cookie: cookie获取器或设置器。
  • cors: CORS支持。
  • forward: "X-Forwarded-*" 解析器。
  • jwt: JSON Web Token 支持。
  • logger: 日志中间件。
  • tls: HTTPS 支持。
  • websocket: WebSocket 支持。

依赖

~8-25MB
~414K SLoC