48次发布

0.3.0-beta.22024年2月14日
0.2.0-beta.182023年10月6日
0.2.0-beta.132023年7月19日
0.2.0-beta.102023年3月28日
0.2.0-alpha.02021年3月29日

#297 in HTTP服务器


用于 apocalypse

MIT 协议

160KB
2.5K SLoC

Cataclysm ⛈. 一个简单的Rust HTTP服务器

工作正在进行中:当前状态下的工作部分可用,但将在完整HTTP/1.1服务器功能实现之前持续更新。当达到这种状态时,此说明将删除。

Cataclysm是一个受actix-web启发的http框架,并在tokio之上构建。以下是一个最小的工作示例

use cataclysm::{Server, Branch, http::{Response, Method}};

async fn hello() -> Response {
    Response::ok().body("hello")
}

#[tokio::main]
async fn main() {
    let server = Server::builder(
        Branch::<()>::new("/hello").with(Method::Get.to(hello))
    ).build().unwrap();

    server.run("localhost:8000").await.unwrap();
}

闭包作为回调

async closures 变得稳定之前,将闭包作为路径处理程序传递的选项是一个返回异步块的闭包

use cataclysm::{Server, Branch, http::{Response, Method}};

#[tokio::main]
async fn main() {
    let server = Server::builder(
        Branch::<()>::new("/data").with(Method::Post.to(|| async {
            Response::ok().body("worked!")
        }))
    ).build().unwrap();

    server.run("localhost:8000").await.unwrap();
}

提取器

可以通过向回调添加具有实现 Extractor 特质的类型的参数来从HTTP请求中检索一些数据。默认实现列表如下

  • String:尝试将正文提取为有效的utf-8字符串。如果操作失败,则返回Bad-Request
  • Vec<u8>:将 http 调用的内容作为字节流返回
  • Request:返回请求,以便在回调中拥有更多控制
  • Path<T>:返回路径参数。T必须是元组。
  • Shared<T>:返回提供给服务器(如果有的话)的共享数据。

将数据共享到服务器函数中

可以通过 ServerBuilder 结构的 share 方法以及 Shared 结构的帮助,在服务器调用之间共享数据。

use cataclysm::{Server, Branch, Shared, http::{Response, Method, Path}};

// Receives a string, and concatenates the shared suffix
async fn index(path: Path<(String,)>, shared: Shared<String>) -> Response {
    let (prefix,) = path.into_inner();
    let suffix = shared.into_inner();
    Response::ok().body(format!("{}{}", prefix, suffix))
}

#[tokio::main]
async fn main() {
    // We create our tree structure
    let branch = Branch::new("/{:value}").with(Method::Get.to(index));
    // We create a server with the given tree structure
    let server = Server::builder(branch).share("!!!".into()).build().unwrap();
    // And we launch it on the following address
    server.run("127.0.0.1:8000").await.unwrap();
}

如果您想共享可变数据,请使用Rust的Mutex结构(因为Shared结构已经提供了Arc包装器)。

SPA和静态文件服务

分支结构同时具有file方法和defaults_to_file,用于创建一个简单的SPA服务器,一个允许在提供的文件夹路径中查找包含扩展名的所有路径,另一个则用于在任何没有扩展名的其他匹配路径中提供服务。

use cataclysm::{Server, Branch, http::{Response, Method}};

async fn salute() -> Response {
    Response::ok().body("api salute endpoint?")
}

#[tokio::main]
async fn main() {
    let branch: Branch<()> = Branch::new("/").files("./static").defaults_to_file("./static/index.html")
        .nest(Branch::new("/api/v1/salute").with(Method::Get.to(salute)));
    let server = Server::builder(branch).build().unwrap();
    server.run("127.0.0.1:8000").await.unwrap();
}

Cataclysm允许处理层,也就是中间件。

use cataclysm::{Server, Branch, Additional, Pipeline, http::{Response, Request, Method}};
use std::sync::Arc;
use futures::future::FutureExt;

#[tokio::main]
async fn main() {
    let branch = Branch::new("/").with(Method::Get.to(|| async {Response::ok()}))
        .layer(|req: Request, pipeline: Box<Pipeline<()>>, ad: Arc<Additional<()>>| async {
            // Example of timing layer
            println!("Time measuring begins");
            let now = std::time::Instant::now();
            let request = pipeline.execute(req, ad).await;
            let elapsed = now.elapsed().as_nanos();
            println!("Process time: {} ns", elapsed);
            request
        }.boxed());
    let server = Server::builder(branch).build().unwrap();
    server.run("localhost:8000").await.unwrap();
}

如示例所示,层函数接收一个Request和一个boxed的Pipeline枚举。该Pipeline枚举包含嵌套的futures结构(层加核心处理器),并提供execute方法以简化事情。此函数必须返回一个Pin<Box<_>>未来,因此您可以使用futures crate中的FutureExt trait的boxed方法,或者手动包装。

完整日志功能

如果激活了full_log功能,将通过DebugTrace级别提供日志函数。这可能对调试很有用,但因为它可能会对性能产生重大影响,所以它被设置为可选。

TODO

  • 分支创建时,使用/会引起正则表达式问题(通过"{", "}"检测的队列实现修复)

lib.rs:

Cataclysm:一个简单的HTTP框架

Cataclysm是一个小型个人项目,是一个受actix-web启发的HTTP框架,并在tokio之上构建。以下是一个最小的工作示例

extern crate cataclysm;

use cataclysm::{Server, Branch, http::{Response, Method}};

async fn index() -> Response {
    Response::ok().body("Hello, World!")
}

#[tokio::main]
async fn main() {
    let server = Server::builder(
        Branch::<()>::new("/").with(Method::Get.to(index))
    ).build().unwrap();

    server.run("localhost:8000").await.unwrap();
}

依赖项

~18–30MB
~553K SLoC