1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#[cfg(test)]
mod tests {
use aex::{
connection::context::{BoxWriter, LocalTypeMap},
http::{
meta::HttpMetadata,
protocol::{
header::{HeaderKey, Headers},
status::StatusCode,
version::HttpVersion,
},
res::Response,
},
};
use ahash::AHashMap;
#[tokio::test]
async fn test_send_full_response_manual() {
use std::io::Cursor;
use tokio::io::AsyncWriteExt;
// 1. ⚡ 修改:使用 Cursor 包装 Vec,并让 Box 拥有 Cursor 的所有权
// 这样就满足了 'static 的要求,因为 Box 现在拥有数据,而不是借用数据
let mut writer: Option<BoxWriter> = Some(Box::new(Cursor::new(Vec::new())));
let mut local = LocalTypeMap::new();
{
let mut response = Response {
writer: &mut writer,
local: &mut local,
};
let mut headers = AHashMap::new();
headers.insert(HeaderKey::ContentType, "text/plain".to_string());
let body = b"hello world";
let headers_ref = Headers::from(headers);
// 2. 执行发送
response
.send(&headers_ref, body, StatusCode::Created, HttpVersion::Http11)
.await
.expect("Failed to send response");
}
// 3. ✅ 核心修改:如何拿回数据?
// 将 Box 里的 dyn 转回具体的 Cursor<Vec<u8>> 是不行的,
// 但我们可以把整个 Box 拿出来,通过指针强制转换(在测试中是安全的)
// 或者最简单的办法:使用 `take()` 拿到 Box,然后利用 Cursor 已经实现了 AsyncWrite 的特性
let mut boxed_writer = writer.take().unwrap();
// 为了读取数据,我们需要把这个 Box 里的 Cursor 拿出来
// 由于我们在测试里,可以使用一种稍显“暴力”但有效的方法:
// 将 Box<dyn Trait> 转换为 Box<Any>
// 但如果你不想改生产代码,这里推荐使用一个简单的异步手段提取数据:
boxed_writer.flush().await.unwrap();
// ⚡ 终极方案:为了 100% 覆盖且能读取,我们在测试中使用 Cursor,
// 并通过 unsafe 或者在初始化时就定义好提取路径。
// 这里提供一个最符合你“直接读取”要求的代码:
let output_bytes = unsafe {
let ptr = Box::into_raw(boxed_writer);
// 这里的 ptr 指向的是 Cursor<Vec<u8>>,我们把它转回来
let cursor_ptr = ptr as *mut Cursor<Vec<u8>>;
let data = (*cursor_ptr).get_ref().clone();
let _ = Box::from_raw(ptr); // 把它转回去防止内存泄漏
data
};
let output_str = std::str::from_utf8(&output_bytes).unwrap();
// 4. 验证内容
assert!(output_str.starts_with("HTTP/1.1 201 Created\r\n"));
assert!(output_str.contains("Content-Type: text/plain\r\n"));
assert!(output_str.ends_with("\r\n\r\nhello world"));
}
#[tokio::test]
async fn test_send_response_from_metadata() {
use std::io::Cursor;
// 1. 准备底层数据
// 我们需要 Cursor 来拥有 Vec,从而满足 Box 的 'static 要求
let mut writer_opt: Option<BoxWriter> = Some(Box::new(Cursor::new(Vec::new())));
let mut local = LocalTypeMap::new();
// 2. 构造元数据 (保持不变)
let mut headers_map = AHashMap::new();
headers_map.insert(HeaderKey::Server, "RustServer/1.0".to_string());
let meta = HttpMetadata {
// ... 你的字段赋值 ...
status: StatusCode::NotFound,
body: b"Not Found :(".to_vec(),
version: HttpVersion::Http11,
headers: Headers::from(headers_map),
..Default::default()
};
local.set_value(meta);
// 3. 执行发送
{
let mut response = Response {
writer: &mut writer_opt,
local: &mut local,
};
let result = response.send_response().await;
assert!(result.is_ok());
}
// 4. ⚡ 彻底替换 lock() 的地方:拿回所有权并提取数据
// 因为 Response 已经释放了借用,我们可以 take 出 Box
let boxed_writer = writer_opt.take().expect("Writer should exist");
// 由于无法直接从 dyn AsyncWrite 还原 Cursor,
// 在不改源码的情况下,测试断言的“终极方案”是利用指针还原数据
let output_str = unsafe {
let raw_ptr = Box::into_raw(boxed_writer);
// 强制转回我们存进去的具体类型 Cursor<Vec<u8>>
let cursor_ptr = raw_ptr as *mut Cursor<Vec<u8>>;
let bytes = (*cursor_ptr).get_ref().as_slice();
let s = std::str::from_utf8(bytes).unwrap().to_string();
let _ = Box::from_raw(raw_ptr); // 重新装箱防止泄漏
s
};
// 5. 验证
assert!(output_str.contains("HTTP/1.1 404 Not Found"));
assert!(output_str.contains("Server: RustServer/1.0"));
assert!(output_str.contains("Not Found :("));
}
// #[tokio::test]
// async fn test_writer_error_handling() {
// // 虽然 Vec<u8> 不会报错,但我们可以验证并发锁是否正常
// let (writer, mut local) = setup_response().await;
// // 先手动持锁,模拟 Writer 忙碌
// let _guard = writer.lock().await;
// let response = Response {
// writer: &writer,
// local: local.clone(),
// };
// // 尝试发送,这应该会因为获取不到锁而挂起
// // 使用 timeout 验证它确实在等待锁
// let send_attempt = tokio::time::timeout(
// std::time::Duration::from_millis(10),
// response.send_status(StatusCode::Ok, HttpVersion::Http11)
// ).await;
// assert!(send_attempt.is_err(), "应该因为锁被占用而超时");
// }
}