7 个版本

0.1.6 2024 年 5 月 30 日
0.1.5 2024 年 5 月 30 日

#301HTTP 服务器

MIT 许可证

55KB
1.5K SLoC

Zeke

使用 Rust 编写的一组简单的 HTTP 基本组件,用于构建 Web 服务。

快速入门

安装

在你的 cargo.toml

[dependencies]
zeke = '0.1.3'

创建一个路由器

路由器用于定义和提供我们的 HTTP 端点。

#[tokio::main]
async fn main() {
	let r = Router::new();
}

创建一个处理器

任何返回处理器的函数都可以与端点关联

#[tokio::main]
async fn main() {
	let r = Router::new();
    r.add(Route::new("GET /", hello_world()));
}

async fn hello_world() -> Handler {
    return Handler::new(|request| {
        // enables our handlers to by async
        Box::pin(async move {
            let response = Response::new()
                .status(200);
            return (request, response);
        })
    });
}

提供服务

要提供应用程序,请调用 Router.serve

#[tokio::main]
async fn main() {
    // --snip
	let result = r.serve(&host).await;
	if result.is_err() {
		println!("Error: {:?}", err);
	}
}

上下文键

在中间件、处理器和外部之间共享的任何数据都称为 context

键用于编码和解码上下文。可以使用实现 Contextable 特性的枚举来跟踪这些键

pub enum AppContext {
    Trace,
}

impl Contextable for AppContext {
    fn key(&self) -> &'static str {
        match self {
            AppContext::Trace => {"TRACE"},
        }
    }
}

HttpTrace

HttpTrace 是一个 context(因为它旨在在中间件、处理器和外部之间共享),帮助我们跟踪每个请求周期所需的时间。

对于任何打算用作 context 的数据,必须 derive SerializeDeserialize

#[derive(Debug, Serialize, Deserialize)]
pub struct HttpTrace {
    pub time_stamp: String,
}

impl HttpTrace {
    pub fn get_time_elapsed(&self) -> String {
        if let Ok(time_set) = DateTime::parse_from_rfc3339(&self.time_stamp) {
            let time_set = time_set.with_timezone(&Utc);
            let now = Utc::now();
            let duration = now.signed_duration_since(time_set);
            let micros = duration.num_microseconds();
            match micros {
                Some(micros) => {
                    if micros < 1000 {
                        return format!("{}µ", micros);
                    }
                },
                None => {

                }
            }
            let millis = duration.num_milliseconds();
            return format!("{}ms", millis);
        } else {
            return "failed to parse time_stamp".to_string();
        }
    }
}

中间件

任何返回 Middleware 的函数都可以用作我们应用程序中的中间件。

让我们利用上一节中创建的 HttpTrace 类型。

以下中间件将在调用我们的处理器之前初始化 HttpTrace

pub async fn mw_trace() -> Middleware {
    Middleware::new(|mut request: &mut Request| {
        let trace = HttpTrace {
            time_stamp: chrono::Utc::now().to_rfc3339(),
        };
        let trace_encoded = serde_json::to_string(&trace);
        if trace_encoded.is_err() {
            return Some(Response::new()
                .status(500)
                .body("failed to encode trace")
            );
        }
        let trace_encoded = trace_encoded.unwrap();
        request.set_context(AppContext::Trace, trace_encoded);
        None
    })
}

让我们花点时间注意这里发生的一些关键事情。

  1. 我们初始化我们的跟踪类型,并将其编码为 json
let trace = HttpTrace{
    time_stamp: chrono::Utc::now().to_rfc3339(),
};
let trace_encoded = serde_json::to_string(&trace);
  1. 我们确保跟踪已正确编码
if trace_encoded.is_err() {
    return Some(Response::new()
        .status(500)
        .body("failed to encode trace")
    );
}
  1. 最后(也是最重要的)我们在 Request 类型上调用 set_context,使用我们的 AppContext::Trace 键
let trace_encoded = trace_encoded.unwrap();
request.set_context(AppContext::Trace, trace_encoded);

现在,HttpTrace 类型的 json 数据与 Request 类型相关联,可以在请求周期中稍后使用。

我们可以将中间件附加到Route上,如下所示

#[tokio::main]
async fn main() {
	let r = Router::new();
    r.add(Route::new("GET /", hello_world())
        .middleware(mw_trace())
    );
    let result = r.serve(&host).await;
	if result.is_err() {
		println!("Error: {:?}", err);
	}
}

外部组件

任何返回Middleware的函数都可以作为我们的应用程序中的外部组件。

中间件在调用处理器之前执行。

外部组件在调用处理器之后执行。

我们可以在请求周期结束后创建一个外部组件来解码我们的HttpTrace类型。然后我们可以计算整个请求处理所需的时间并将其打印到终端。

pub async fn mw_trace_log() -> Middleware {
    Middleware::new(|request: &mut Request | {
        let trace = request.get_context(AppContext::Trace);
        if trace.is_empty() {
            return Some(Response::new()
                .status(500)
                .body("failed to get trace")
            );
        }
        let trace: HttpTrace = serde_json::from_str(&trace).unwrap();
        let elapsed_time = trace.get_time_elapsed();
        let log_message = format!("[{:?}][{}][{}]", request.method, request.path, elapsed_time);
        println!("{}", log_message);
        None
    })
}

让我们更详细地看看一些内容。

  1. 我们使用我们的AppContext::Trace键,通过request.get_context获取编码的HttpTrace
let trace = request.get_context(AppContext::Trace);
  1. 我们确保跟踪存在
if trace == "" {
    return Some(Response::new()
        .status(500)
        .body("failed to get trace")
    );
}
  1. 解码HttpTrace
let trace: HttpTrace = serde_json::from_str(&trace).unwrap();
  1. 最后,我们计算经过的时间并将结果记录到终端
let elapsed_time = trace.get_time_elapsed();
let log_message = format!("[{:?}][{}][{}]", request.method, request.path, elapsed_time);
println!("{}", log_message);

我们可以在应用程序中使用此外部组件如下

#[tokio::main]
async fn main() {
	let r = Router::new();
    r.add(Route::new("GET /", hello_world())
        .middleware(mw_trace().await)
        .outerware(mw_trace_log().await)
    );
    let result = r.serve(&host).await;
	if result.is_err() {
		println!("Error: {:?}", err);
	}
}

中间件组

任何返回MiddlewareGroup的函数都可以作为我们的应用程序中的中间件组。

中间件组允许我们将中间件组合在一起。让我们看看是否可以将我们的mw_tracemw_trace_log函数组合在一起

pub fn mw_group_trace() -> MiddlewareGroup {
    return MiddlewareGroup::new(vec![mw_trace().await], vec![mw_trace_log().await]);
}

现在我们可以简单地使用这个组

#[tokio::main]
async fn main() {
	let r = Router::new();
    r.add(Route::new("GET /", hello_world())
        .group(mw_group_trace().await)
    );
    let result = r.serve(&host).await;
	if result.is_err() {
		println!("Error: {:?}", err);
	}
}

依赖关系

~9–21MB
~304K SLoC