hyperlane-macros
2025年7月6日大约 7 分钟hyperlane-macros
安装
要使用这个 crate,你可以运行以下命令:
cargo add hyperlane-macros
可用宏
服务器实例宏
#[hyperlane(variable_name)]
- 在函数开始时使用指定的变量名创建一个新的服务器实例
HTTP 方法宏
#[methods(method1, method2, ...)]
- 接受多个 HTTP 方法#[get]
- GET 方法处理器#[post]
- POST 方法处理器#[put]
- PUT 方法处理器#[delete]
- DELETE 方法处理器#[patch]
- PATCH 方法处理器#[head]
- HEAD 方法处理器#[options]
- OPTIONS 方法处理器#[connect]
- CONNECT 方法处理器#[trace]
- TRACE 方法处理器
协议检查宏
#[ws]
- WebSocket 检查,确保函数仅对 WebSocket 升级请求执行#[http]
- HTTP 检查,确保函数仅对标准 HTTP 请求执行#[h2c]
- HTTP/2 明文检查,确保函数仅对 HTTP/2 明文请求执行#[http0_9]
- HTTP/0.9 检查,确保函数仅对 HTTP/0.9 协议请求执行#[http1_0]
- HTTP/1.0 检查,确保函数仅对 HTTP/1.0 协议请求执行#[http1_1]
- HTTP/1.1 检查,确保函数仅对 HTTP/1.1 协议请求执行#[http1_1_or_higher]
- HTTP/1.1 或更高版本检查,确保函数仅对 HTTP/1.1 或更新协议版本执行#[http2]
- HTTP/2 检查,确保函数仅对 HTTP/2 协议请求执行#[http3]
- HTTP/3 检查,确保函数仅对 HTTP/3 协议请求执行#[tls]
- TLS 检查,确保函数仅对 TLS 安全连接执行
响应设置宏
#[response_status_code(code)]
- 设置响应状态码(支持字面量和全局常量)#[response_reason_phrase("phrase")]
- 设置响应原因短语(支持字面量和全局常量)#[response_header("key", "value")]
- 设置响应头(支持字面量和全局常量)#[response_body("data")]
- 设置响应体(支持字面量和全局常量)
发送操作宏
#[send]
- 函数执行后发送完整响应(头部和正文)#[send_body]
- 函数执行后仅发送响应体#[send_once]
- 函数执行后精确发送一次完整响应#[send_once_body]
- 函数执行后精确发送一次响应体
刷新宏
#[flush]
- 函数执行后刷新响应流以确保立即传输数据
中止宏
#[aborted]
- 处理中止的请求,为过早终止的连接提供清理逻辑
关闭操作宏
#[closed]
- 处理已关闭的流,为已完成的连接提供清理逻辑
过滤器宏
#[filter_unknown_method]
- 过滤未知 HTTP 方法,处理具有非标准方法的请求#[filter_unknown_upgrade]
- 过滤未知升级请求,处理具有非标准升级协议的请求#[filter_unknown_version]
- 过滤未知 HTTP 版本,处理具有非标准 HTTP 协议版本的请求#[filter_unknown]
- 未知方法、升级和版本的组合过滤器
请求体宏
#[request_body(variable_name)]
- 将原始请求体提取到指定变量中,变量类型为 RequestBody#[request_body_json(variable_name: type)]
- 将请求体解析为 JSON 格式并存储到指定变量和类型中
属性宏
#[attribute(键 => 变量名: 类型)]
- 通过键提取特定属性到类型化变量
属性集宏
#[attributes(变量名)]
- 获取所有属性作为 HashMap 以进行全面的属性访问
路由参数宏
#[route_param(键 => 变量名)]
- 通过键将特定路由参数提取到变量中
路由参数集宏
#[route_params(变量名)]
- 获取所有路由参数作为集合
请求查询宏
#[request_query(键 => 变量名)]
- 从 URL 查询字符串中提取特定查询参数
请求查询集宏
#[request_querys(变量名)]
- 获取所有查询参数作为集合
请求头宏
#[request_header(键 => 变量名)]
- 从请求中提取特定 HTTP 头
请求头集宏
#[request_headers(变量名)]
- 获取所有 HTTP 头作为集合
钩子宏
#[pre_hook(函数名)]
- 在主处理函数之前执行指定函数#[post_hook(函数名)]
- 在主处理函数之后执行指定函数
响应头宏
#[response_header(键 => 值)]
- 使用给定的键和值设置特定的 HTTP 响应头
响应体宏
#[response_body(值)]
- 使用给定的值设置 HTTP 响应体
最佳实践警告
- 请求相关的宏大多是查询函数,而响应相关的宏大多是赋值函数。
- 当使用
pre_hook
或post_hook
宏时,不建议将它们与其他宏(如#[get]
、#[post]
、#[http]
等)在同一个函数上组合使用。这些宏应该放在钩子函数本身中。如果您不清楚宏是如何展开的,组合使用可能会导致有问题的代码行为。
使用示例
use hyperlane::*;
use hyperlane_macros::*;
use serde::{Deserialize, Serialize};
const TEST_ATTRIBUTE_KEY: &str = "test_attribute_key";
const CUSTOM_STATUS_CODE: i32 = 200;
const CUSTOM_REASON: &str = "Accepted";
const CUSTOM_HEADER_NAME: &str = "X-Custom-Header";
const CUSTOM_HEADER_VALUE: &str = "custom-value";
const RESPONSE_DATA: &str = "{\"status\": \"success\"}";
#[derive(Debug, Serialize, Deserialize, Clone)]
struct TestData {
name: String,
age: u32,
}
#[get]
#[http]
async fn ctx_pre_hook(ctx: Context) {}
#[flush]
#[send]
#[response_status_code(200)]
async fn ctx_post_hook(ctx: Context) {}
#[send]
#[response_status_code(201)]
#[response_reason_phrase("Created")]
#[response_header("Content-Type" => "application/json")]
#[response_body("{\"message\": \"Resource created\"}")]
async fn test_new_macros_literals(ctx: Context) {}
#[send]
#[response_status_code(CUSTOM_STATUS_CODE)]
#[response_reason_phrase(CUSTOM_REASON)]
#[response_header(CUSTOM_HEADER_NAME => CUSTOM_HEADER_VALUE)]
#[response_body(RESPONSE_DATA)]
async fn response(ctx: Context) {}
#[connect]
async fn connect(ctx: Context) {
let _ = ctx.set_response_body("connect").await.send().await;
}
#[delete]
async fn delete(ctx: Context) {
let _ = ctx.set_response_body("delete").await.send().await;
}
#[head]
async fn head(ctx: Context) {
let _ = ctx.set_response_body("head").await.send().await;
}
#[options]
async fn options(ctx: Context) {
let _ = ctx.set_response_body("options").await.send().await;
}
#[patch]
async fn patch(ctx: Context) {
let _ = ctx.set_response_body("patch").await.send().await;
}
#[put]
async fn put(ctx: Context) {
let _ = ctx.set_response_body("put").await.send().await;
}
#[trace]
async fn trace(ctx: Context) {
let _ = ctx.set_response_body("trace").await.send().await;
}
#[send]
#[h2c]
async fn h2c(ctx: Context) {
let _ = ctx.set_response_body("h2c").await;
}
#[send]
#[http]
async fn http_only(ctx: Context) {
let _ = ctx.set_response_body("http").await;
}
#[send]
#[http0_9]
async fn http0_9(ctx: Context) {
let _ = ctx.set_response_body("http0.9").await;
}
#[send]
#[http1_0]
async fn http1_0(ctx: Context) {
let _ = ctx.set_response_body("http1.0").await;
}
#[send]
#[http1_1]
async fn http1_1(ctx: Context) {
let _ = ctx.set_response_body("http1.1").await;
}
#[send]
#[http2]
async fn http2(ctx: Context) {
let _ = ctx.set_response_body("http2").await;
}
#[send]
#[http3]
async fn http3(ctx: Context) {
let _ = ctx.set_response_body("http3").await;
}
#[send]
#[tls]
async fn tls(ctx: Context) {
let _ = ctx.set_response_body("tls").await;
}
#[send]
#[http1_1_or_higher]
async fn http1_1_or_higher(ctx: Context) {
let _ = ctx.set_response_body("http1.1+").await;
}
#[send]
#[filter_unknown_method]
async fn unknown_method(ctx: Context) {
let _ = ctx.set_response_body("unknown method").await;
}
#[send]
#[filter_unknown_upgrade]
async fn unknown_upgrade(ctx: Context) {
let _ = ctx.set_response_body("unknown upgrade").await;
}
#[send]
#[filter_unknown_version]
async fn unknown_version(ctx: Context) {
let _ = ctx.set_response_body("unknown version").await;
}
#[send]
#[filter_unknown]
async fn unknown_all(ctx: Context) {
let _ = ctx.set_response_body("unknown all").await;
}
#[send_body]
#[ws]
#[get]
async fn get(ctx: Context) {
let _ = ctx.set_response_body("get").await;
}
#[send_once]
#[post]
async fn post(ctx: Context) {
let _ = ctx.set_response_body("post").await;
}
#[send_once_body]
#[ws]
async fn websocket(ctx: Context) {
let _ = ctx.set_response_body("websocket").await;
}
#[send]
#[pre_hook(ctx_pre_hook)]
#[post_hook(ctx_post_hook)]
async fn ctx_hook(ctx: Context) {
let _ = ctx.set_response_body("Testing hook macro").await;
}
#[closed]
#[send]
#[response_reason_phrase("OK")]
#[response_status_code(200)]
#[methods(get, post)]
#[http]
async fn get_post(ctx: Context) {
let _ = ctx.set_response_body("get_post").await;
}
#[send]
#[attributes(request_attributes)]
async fn attributes(ctx: Context) {
let response: String = format!("{:?}", request_attributes);
let _ = ctx.set_response_body(response).await;
}
#[send]
#[route_params(request_route_params)]
async fn route_params(ctx: Context) {
let response: String = format!("{:?}", request_route_params);
let _ = ctx.set_response_body(response).await;
}
#[send]
#[request_querys(request_querys)]
async fn request_querys(ctx: Context) {
let response: String = format!("{:?}", request_querys);
let _ = ctx.set_response_body(response).await;
}
#[send]
#[request_headers(request_headers)]
async fn request_headers(ctx: Context) {
let response: String = format!("{:?}", request_headers);
let _ = ctx.set_response_body(response).await;
}
#[send]
#[route_param("test" => request_route_param)]
async fn route_param(ctx: Context) {
if let Some(data) = request_route_param {
let _ = ctx.set_response_body(data).await;
}
}
#[send]
#[request_query("test" => request_query_option)]
async fn request_query(ctx: Context) {
if let Some(data) = request_query_option {
let _ = ctx.set_response_body(data).await;
}
}
#[send]
#[request_header(HOST => request_header_option)]
async fn request_header(ctx: Context) {
if let Some(data) = request_header_option {
let _ = ctx.set_response_body(data).await;
}
}
#[send]
#[request_body(raw_body)]
async fn request_body(ctx: Context) {
let response: String = format!("Raw body: {:?}", raw_body);
let _ = ctx.set_response_body(response).await;
}
#[send]
#[attribute(TEST_ATTRIBUTE_KEY => request_attribute_option: TestData)]
async fn attribute(ctx: Context) {
if let Some(data) = request_attribute_option {
let response: String = format!("name={}, age={}", data.name, data.age);
let _ = ctx.set_response_body(response).await;
}
}
#[send]
#[request_body_json(request_data_result: TestData)]
async fn request_body_json(ctx: Context) {
if let Ok(data) = request_data_result {
let response: String = format!("name={}, age={}", data.name, data.age);
let _ = ctx.set_response_body(response).await;
}
}
#[tokio::main]
#[hyperlane(server)]
async fn main() {
server.route("/get", get).await;
server.route("/post", post).await;
server.route("/connect", connect).await;
server.route("/delete", delete).await;
server.route("/head", head).await;
server.route("/options", options).await;
server.route("/patch", patch).await;
server.route("/put", put).await;
server.route("/trace", trace).await;
server.route("/h2c", h2c).await;
server.route("/http", http_only).await;
server.route("/http0_9", http0_9).await;
server.route("/http1_0", http1_0).await;
server.route("/http1_1", http1_1).await;
server.route("/http2", http2).await;
server.route("/http3", http3).await;
server.route("/tls", tls).await;
server.route("/http1_1_or_higher", http1_1_or_higher).await;
server.route("/unknown_method", unknown_method).await;
server.route("/unknown_upgrade", unknown_upgrade).await;
server.route("/unknown_version", unknown_version).await;
server.route("/unknown_all", unknown_all).await;
server.route("/websocket", websocket).await;
server.route("/ctx_hook", ctx_hook).await;
server.route("/get_post", get_post).await;
server.route("/attributes", attributes).await;
server.route("/route_params/:test", route_params).await;
server.route("/request_querys", request_querys).await;
server.route("/request_headers", request_headers).await;
server.route("/route_param/:test", route_param).await;
server.route("/request_query", request_query).await;
server.route("/request_header", request_header).await;
server.route("/request_body", request_body).await;
server.route("/attribute", attribute).await;
server.route("/request_body_json", request_body_json).await;
server
.route("/test_new_macros_literals", test_new_macros_literals)
.await;
server.route("/response", response).await;
let _ = server.run().await;
}
许可证
本项目使用 MIT 协议,详情请参见 LICENSE 文件。
贡献
欢迎贡献代码!请提交 issue 或 pull request。
联系方式
如有任何问题,请联系作者 [email protected]。