7 个版本
0.1.6 | 2024 年 5 月 30 日 |
---|---|
0.1.5 | 2024 年 5 月 30 日 |
#301 在 HTTP 服务器
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 Serialize
和 Deserialize
。
#[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
})
}
让我们花点时间注意这里发生的一些关键事情。
- 我们初始化我们的跟踪类型,并将其编码为 json
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")
);
}
- 最后(也是最重要的)我们在
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
})
}
让我们更详细地看看一些内容。
- 我们使用我们的
AppContext::Trace
键,通过request.get_context
获取编码的HttpTrace
。
let trace = request.get_context(AppContext::Trace);
- 我们确保跟踪存在
if trace == "" {
return Some(Response::new()
.status(500)
.body("failed to get trace")
);
}
- 解码
HttpTrace
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);
我们可以在应用程序中使用此外部组件如下
#[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_trace
和mw_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