7 个不稳定版本 (3 个重大更改)

0.4.2 2023年11月11日
0.4.1 2023年7月31日
0.4.0 2023年6月27日
0.3.1 2023年6月10日
0.1.0 2023年3月19日

#287 in HTTP 服务器

每月38次下载
2 个 Crates 中使用(通过 gcal

BSD-3-Clause

70KB
1.5K SLoC

davisjr:一个简单的 HTTP 框架(用于 rust-lang)

davisjr 在其目标上理想化了 sinatra(Ruby)框架的简洁性,并试图成为其他异步 HTTP 框架(如 towerwarpaxumtide)的替代品。

davisjr 通过实现跨越处理器边界的“责任链”模式,试图实现一个承诺,即“任何处理器也可以是中间件”。简而言之,从第一个处理器返回的内容被传递到第二个处理器,然后返回到第三个处理器,直到所有处理器都处理完毕或接收到错误。错误可以返回有效的状态码或 HTTP 500(内部服务器错误)格式的纯文本错误。

davisjr 最初是 ratpack,一个为 ZeroTier,Inc. 设计的框架。据我所知,它已经没有更新,自 2022 年 4 月以来就没有更新。我正在对其进行硬分叉以改进它。

davisjr 不是什么

  • 复杂:davisjr 并非特别设计用于具有大量路由或与 HTTP 协议复杂交互的服务(例如 SSE 或 Websockets),至少到目前为止。 davisjr 非常专注于某些典型的请求/响应周期。
  • 详细模式:davisjr 尽力使自身内部结构和您与之交互都尽可能简单,这是它能做到的最简单的事情。这意味着您需要传递给名为 compose_handler! 的宏,该宏用于路由调用,并且您很可能不需要花费时间实现复杂的、极其冗长的特质,甚至不需要对 futures 和 async 的工作原理有复杂的理解。
  • 专注于一个平台:虽然目前我们只直接支持 tokio,但没有任何东西阻止我们进入 smolasync-std 的领域。《davisjr》的大多数 async 使用都是 futures,这些 futures 最终被 tokio 在非常高级的层面上利用。

示例

以下是一个示例,它将全局 应用程序状态 作为身份验证令牌验证中间件处理程序,然后传递给问候处理程序。问候处理程序也可以在不需要身份验证的不同端点重用,这也在示例中演示。

注意:此示例可在 examples/auth-with-state.rs 找到。也可以用 cargo 运行:cargo run --example auth-with-state

use davisjr::prelude::*;

// We'll use authstate to (optionally) capture information about the token
// being correct. if it is Some(true), the user was authed, if None, there was no
// authentication performed.
#[derive(Clone)]
struct AuthedState {
    authed: Option<bool>,
}

// All transient state structs must have an initial state, which will be
// initialized internally in the router.
impl TransientState for AuthedState {
    fn initial() -> Self {
        Self { authed: None }
    }
}

// our authtoken validator, this queries the app state and the header
// `X-AuthToken` and compares the two. If there are any discrepancies, it
// returns `401 Unauthorized`.
//
// every handler & middleware takes and returns the same params and has the
// same prototype.
//
async fn validate_authtoken(
    req: Request<Body>,
    resp: Option<Response<Body>>,
    _params: Params,
    app: App<State, AuthedState>,
    mut authstate: AuthedState,
) -> HTTPResult<AuthedState> {
    if let (Some(token), Some(state)) = (req.headers().get("X-AuthToken"), app.state().await) {
        authstate.authed = Some(state.clone().lock().await.authtoken == token);
        Ok((req, resp, authstate))
    } else {
        Err(Error::StatusCode(
            StatusCode::UNAUTHORIZED,
            String::default(),
        ))
    }
}

// our `hello` responder; it simply echoes the `name` parameter provided in the
// route.
async fn hello(
    req: Request<Body>,
    _resp: Option<Response<Body>>,
    params: Params,
    _app: App<State, AuthedState>,
    authstate: AuthedState,
) -> HTTPResult<AuthedState> {
    let name = &params["name"];
    let bytes = Body::from(format!("hello, {}!\n", name));

    if let Some(authed) = authstate.authed {
        if authed {
            return Ok((
                req,
                Some(Response::builder().status(200).body(bytes).unwrap()),
                authstate,
            ));
        }
    } else if authstate.authed.is_none() {
        return Ok((
            req,
            Some(Response::builder().status(200).body(bytes).unwrap()),
            authstate,
        ));
    }

    Err(Error::StatusCode(
        StatusCode::UNAUTHORIZED,
        String::default(),
    ))
}

// our `wildcard` responder, which shows how to use wildcard routes
async fn wildcard(
    req: Request<Body>,
    _resp: Option<Response<Body>>,
    params: Params,
    _app: App<State, AuthedState>,
    state: AuthedState,
) -> HTTPResult<AuthedState> {
    let bytes = Body::from(format!("this route is: {}!\n", params["*"]));

    return Ok((
        req,
        Some(Response::builder().status(200).body(bytes).unwrap()),
        state,
    ));
}

// Our global application state; must be `Clone`.
#[derive(Clone)]
struct State {
    authtoken: &'static str,
}

// ServerError is a catch-all for errors returned by serving content through
// davisjr.
#[tokio::main]
async fn main() -> Result<(), ServerError> {
    let mut app = App::with_state(State {
        authtoken: "867-5309",
    });

    app.get("/wildcard/*", compose_handler!(wildcard))?;
    app.get("/auth/:name", compose_handler!(validate_authtoken, hello))?;
    app.get("/:name", compose_handler!(hello))?;

    app.serve("127.0.0.1:3000").await?;

    Ok(())
}

使用 curl 调用此服务会得到预期的结果

% curl localhost:3000/wildcard/frobnik/from/zorbo
this route is: frobnik/from/zorbo!

% curl localhost:3000/erik
hello, erik!

% curl -D- localhost:3000/auth/erik
HTTP/1.1 401 Unauthorized
content-length: 0
date: Fri, 21 Jan 2022 18:29:03 GMT

% curl -D- -H "X-AuthToken: 867-5309" localhost:3000/auth/erik
HTTP/1.1 200 OK
content-length: 13
date: Fri, 21 Jan 2022 18:29:19 GMT

hello, erik!

更多信息 & 文档

更多详细信息,请参阅 文档

作者

Erik Hollensbe [email protected]

许可证

BSD 3-Clause

依赖关系

~7–20MB
~283K SLoC