Request-and-Response-Deep-Dive[20260804184107]

0 阅读1分钟

请求与响应深入

项目代码:github.com/hyperlane-d…

每个 HTTP 服务器的核心都在于处理传入的请求和生成传出的响应。Hyperlane 为这两个方面提供了全面且符合人体工程学的 API。本文将深入探讨 hyperlane 如何处理请求和响应,涵盖从基本头访问到 JSON 解析、Cookie 以及属性宏的所有内容。

理解请求生命周期

当客户端向 hyperlane 服务器发送 HTTP 请求时,会发生以下序列:

  1. 接受 TCP 连接并创建 Stream
  2. 将请求从流解析为结构化的 Request 对象。
  3. 请求通过所有已注册的请求中间件
  4. 请求与已注册的路由进行匹配。
  5. 调用匹配路由的 handle 方法。
  6. 响应通过所有已注册的响应中间件
  7. 响应被序列化并发送回客户端。

理解这个生命周期对于编写有效的中间件和路由处理程序至关重要。

访问请求数据

Hyperlane 提供了多种访问请求数据的方式 —— 通过方法调用和通过属性宏。

基于方法的访问

Context 对象让你可以访问传入请求的所有方面:

let method = ctx.get_request().get_method();
let path = ctx.get_request().get_path();
let host = ctx.get_request().get_host();
let headers = ctx.get_request().get_headers();
let body = ctx.get_request().get_body_string();
let json_body: T = ctx.get_request().get_body_json::<T>();
let query = ctx.get_request().try_get_query("key");

让我们逐一了解:

  • get_method() —— 返回 HTTP 方法(GET、POST、PUT、DELETE 等)。
  • get_path() —— 返回请求的 URL 路径部分。
  • get_host() —— 返回 Host 头的值。
  • get_headers() —— 返回所有请求头。
  • get_body_string() —— 返回原始请求体字符串。
  • get_body_json::<T>() —— 将请求体作为 JSON 反序列化为类型 T。这要求 T 实现 serde::Deserialize
  • try_get_query("key") —— 尝试按名称提取查询参数。

属性宏访问

Hyperlane 还提供了属性宏,可以自动提取请求数据并将其绑定到变量:

#[request_body(body)]
#[request_body_json(body: TestData)]
#[request_header(HOST => host_value)]
#[try_get_request_header(HOST => host_value)]
#[request_path(path)]
#[request_query("key" => query_value)]
#[is_get_method]
#[methods("GET", "POST")]
#[is_http1_1_version]
#[is_ws_upgrade_type]

这些宏应用于路由处理程序的 handle 方法。例如:

#[route("/test/{text}")]
struct Route;
impl ServerHook for Route {
    async fn new(_: &mut Stream, _: &mut Context) -> Self { Self }

    #[request_body_json(body: TestData)]
    #[request_query("key" => query_value)]
    async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
        // `body` 现在是从请求体反序列化的 TestData 实例
        // `query_value` 是 "key" 查询参数的值
        Status::Continue
    }
}

属性宏支持:

  • #[request_body(body)] —— 将原始请求体字符串绑定到 body
  • #[request_body_json(body: TestData)] —— 将 JSON 体反序列化为 TestData 结构体。
  • #[request_header(HOST => host_value)] —— 将 HOST 头提取到 host_value 中。
  • #[try_get_request_header(HOST => host_value)] —— 安全地尝试提取头。
  • #[request_path(path)] —— 将请求路径绑定到 path
  • #[request_query("key" => query_value)] —— 提取查询参数。
  • #[is_get_method] —— 检查请求方法是否为 GET。
  • #[methods("GET", "POST")] —— 检查请求方法是否匹配指定的方法之一。
  • #[is_http1_1_version] —— 检查请求是否使用 HTTP/1.1。
  • #[is_ws_upgrade_type] —— 检查请求是否为 WebSocket 升级请求。

路由参数

动态路由参数是任何 Web 框架的核心特性。Hyperlane 在路由路径中使用 {param_name} 语法:

server.route::<Route>("/test/{text}");
server.route::<Route>("/test/{number:\\d+}");

路由参数可以通过 Context 访问:

let param: Option<String> = ctx.try_get_route_param("text");
let param: String = ctx.get_route_param("text");
let params = ctx.get_route_params();
  • try_get_route_param("text") —— 返回 Option<String>,对可选参数安全。
  • get_route_param("text") —— 返回 String,如果参数不存在会 panic。
  • get_route_params() —— 返回所有路由参数的集合。

对于正则表达式约束的参数,如 {number:\\d+},仅当 URL 段满足正则表达式模式时,路由才会匹配。

路由过滤器

Hyperlane 通过属性宏支持路由级别的过滤:

#[host("example.com")]
#[reject_host("blocked.com")]
#[referer("https://example.com")]
#[reject_referer("https://malicious.com")]
#[filter(ctx.get_request().get_method() == &RequestMethod::Get)]
#[reject(ctx.get_request().get_path().len() > 1000)]

这些过滤器允许你:

  • #[host("example.com")] —— 仅匹配到特定主机的请求。
  • #[reject_host("blocked.com")] —— 拒绝到特定主机的请求。
  • #[referer("https://example.com")] —— 仅匹配来自特定来源的请求。
  • #[reject_referer("https://malicious.com")] —— 拒绝来自特定来源的请求。
  • #[filter(...)] —— 应用自定义过滤表达式。
  • #[reject(...)] —— 应用自定义拒绝表达式。

构建响应

Hyperlane 的 Context 提供了对响应对象的可变访问:

ctx.get_mut_response().set_version(HttpVersion::Http1_1).set_status_code(200);
ctx.get_mut_response().set_body("Hello World");
ctx.get_mut_response().set_header(CONTENT_TYPE, APPLICATION_JSON);
ctx.get_mut_response().add_header(SERVER, "hyperlane");

关键响应方法:

  • set_version(HttpVersion::Http1_1) —— 设置 HTTP 版本。
  • set_status_code(200) —— 设置 HTTP 状态码。
  • set_body("Hello World") —— 设置响应体。
  • set_header(CONTENT_TYPE, APPLICATION_JSON) —— 设置响应头(替换现有值)。
  • add_header(SERVER, "hyperlane") —— 添加响应头(允许多个值)。

属性宏响应

你也可以使用属性宏来配置响应:

#[response_status_code(200)]
#[response_version(HttpVersion::Http1_1)]
#[response_body("Hello World")]
#[response_header(SERVER => HYPERLANE)]
#[response_header(SET_COOKIE, "session_id=abc123")]
#[clear_response_headers]

这些宏应用于 handle 方法:

#[response_status_code(200)]
#[response_body("Hello World")]
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
    // 响应自动配置了状态码 200 和体 "Hello World"
    Status::Continue
}

使用 Cookie

Cookie 对于会话管理和有状态 Web 应用至关重要。Hyperlane 提供了全面的 Cookie 支持:

读取 Cookie

let cookies = ctx.get_request().try_get_cookies();
let cookie = ctx.get_request().try_get_cookie("session_id");
  • try_get_cookies() —— 返回请求中的所有 Cookie。
  • try_get_cookie("session_id") —— 按名称返回特定的 Cookie。

使用 CookieBuilder 设置 Cookie

Hyperlane 提供了 CookieBuilder 来构建具有各种属性的 Cookie:

let cookie = CookieBuilder::new("session_id", "abc123").set_path("/").http_only().build();
ctx.get_mut_response().set_header(SET_COOKIE, &cookie);

CookieBuilder 支持:

let cookie = CookieBuilder::new("session", "token123")
    .set_expires("Wed, 21 Oct 2025 07:28:00 GMT")
    .set_domain("example.com")
    .set_same_site("Strict")
    .set_max_age(3600)
    .set_path("/")
    .secure()
    .http_only()
    .build();

可用的构建器方法:

  • set_expires(...) —— 设置 Cookie 过期日期。
  • set_domain(...) —— 设置 Cookie 域。
  • set_same_site(...) —— 设置 SameSite 属性(Strict、Lax、None)。
  • set_max_age(...) —— 设置 Cookie 最大存活时间(秒)。
  • set_path(...) —— 设置 Cookie 路径。
  • secure() —— 将 Cookie 标记为 Secure(仅 HTTPS)。
  • http_only() —— 将 Cookie 标记为 HttpOnly(JavaScript 无法访问)。

清除 Cookie

要清除 Cookie,创建一个值为空且 max age 为 0 的 Cookie:

let clear_cookie = CookieBuilder::new("session", "").set_max_age(0).build();

发送响应

构建响应后,你需要将其发送给客户端:

let data = ctx.get_mut_response().build();
stream.try_send(data).await;
stream.send(data).await;
stream.try_send_list(&frame_list).await;
stream.try_flush().await;
  • build() —— 将响应序列化为字节。
  • stream.try_send(data).await —— 尝试发送数据,如果流已关闭则返回错误。
  • stream.send(data).await —— 发送数据,阻塞直到完成。
  • stream.try_send_list(&frame_list).await —— 一次发送多个帧。
  • stream.try_flush().await —— 刷新所有缓冲数据。

发送属性宏

#[try_send]
#[send]
#[try_flush]
#[flush]
#[closed]

这些宏自动处理发送/刷新操作:

#[try_send]
async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
    // 响应自动构建并发送
    Status::Continue
}

常见模式

JSON API 响应

ctx.get_mut_response()
    .set_header(CONTENT_TYPE, APPLICATION_JSON)
    .set_body(json_string);
let data = ctx.get_mut_response().build();
stream.try_send(data).await;

身份验证中间件

impl ServerHook for AuthMiddleware {
    async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status {
        let auth_str = ctx.get_request().try_get_header_back(AUTHORIZATION).unwrap_or_default();
        if auth_str.is_empty() {
            let data = ctx.get_mut_response().set_status_code(401).set_body("Unauthorized").build();
            if stream.try_send(data).await.is_err() {
                stream.set_closed(true);
            }
            return Status::Reject;
        }
        Status::Continue
    }
}

CORS 中间件

ctx.get_mut_response()
    .set_header(ACCESS_CONTROL_ALLOW_ORIGIN, WILDCARD_ANY)
    .set_header(ACCESS_CONTROL_ALLOW_METHODS, ALL_METHODS)
    .set_header(ACCESS_CONTROL_ALLOW_HEADERS, WILDCARD_ANY);

总结

Hyperlane 为处理请求和响应提供了丰富的双模式 API。无论你更喜欢显式的方法调用风格还是简洁的属性宏风格,你都可以完全控制 HTTP 请求/响应周期的每个方面。路由参数、过滤器、Cookie 管理和灵活的响应构建的组合,使 hyperlane 成为构建任何复杂度的 Web 应用的强大工具。


项目代码:github.com/hyperlane-d…