试着将前面写的Http服务器相关代码进行封装,便于更好的学会如何写大项目! 关键字:封装,rust [[toc]]
Http 库
有时候我们并不能按照正规的流程对lib项目进行设计,通常对关键部门的代码还需要进行试验和验证,这个时候我是比较推荐在项目内封装的方式开展的。 我们新建一个新文件取名为 http.rs
Server 服务端
// .. 省略 use
pub struct Server {
}
impl Server {
pub fn listener(addr: &str, inaction: impl Fn(TcpStream), ) {
let listener: TcpListener = TcpListener::bind(addr).unwrap();
println!("服务启动成功[{}]", addr);
for stream in listener.incoming() {
let stream = stream.unwrap();
inaction(stream);
}
}
}
从代码是可以看出,Server::listener 除了bind地址端口外,还增加了一个闭包函数(Lambda)作为参数,用于对进入的请求做后续处理。
fn main() {
http::Server::listener("0.0.0.0:80", |mut stream| {
let response = format!("HTTP/1.1 200 OK\r\n\r\nHello World!");
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
});
}
现在我们的“库”还是在项目内的,但我们更希望他独立存在,我们只需要 cargo new --lib http
(当然你可能需要修改一个名字:smile:),然后在 Cargo.toml 文件中引入
http = {path = "http"}
这样一个最简单的Http服务器代码封装就完成了!
Response
现在希望让页面输出内容更智能和简洁,那就照葫芦画瓢再建一个 Response 结构体,除了 stream 再加上 headers
pub struct Response<'a> {
stream: &'a TcpStream,
headers: HashMap<String, String>,
}
impl Response<'_> {
pub fn new(stream: &TcpStream) -> Response {
// println!("Response 初始化完成");
Response {
stream,
headers: HashMap::new(),
}
}
pub fn add_header(&mut self, key: &str, val: &str) {
self.headers.insert(key.to_string(), val.to_string());
}
pub fn output(&mut self, content: &str) {
let mut head = String::new();
let head_len = self.headers.len();
if head_len != 0 {
let head_arr = self
.headers
.iter()
.map(|m| format!("{}:{}", m.0, m.1))
.collect::<Vec<String>>();
head = format!("\r\n{}", head_arr.join("\r\n"));
}
let response = format!("HTTP/1.1 200 OK{}\r\n\r\n{}", head, content);
self.stream.write(response.as_bytes()).unwrap();
self.stream.flush().unwrap();
}
}
pub struct Server<'a> {
pub response: Response<'a>,
pub request: Request<'a>,
}
impl Server<'_> {
fn new(stream: &TcpStream) -> Server {
// println!("新请求进入!");
Server {
response: Response::new(stream),
request: Request::new(stream)
}
}
pub fn listener(addr: &str, inaction: impl Fn(Server), ) {
// ...
}
}
我们只需要略微调整 Server 的结构 main.rs 就变成了如下:
fn main() {
Server::listener("0.0.0.0:80", |mut stream| {
stream.response.output("Hello World!");
});
}
::: tip 上例中出现了生命周期的知识点,这对刚接触 Rust 的朋友来说会有点抽象。这个例子中当 stream 传递给 Server::new 他需要传递给 Response 和 Request,这里就存在关于 stream 什么时候销毁的问题,我们得让计算机明确知道,这就是生命周期的使命! {{{width="auto" height="auto"}}} 现在再看这张图,我们就会觉得这个例子只表达了概念,却没有说明使用逻辑。 :::
多线程
目前我们的服务器还会因为某个操作缓慢导致用户等待的情况,所以必须用多线程来解决。 我们修改 Server::listener 如下:
impl Server {
pub fn listener<F>(addr: &str, inaction: F)
where F: Fn(Server) + Send + Copy + Sync + 'static,
{
let listener: TcpListener = TcpListener::bind(addr).unwrap();
println!("服务启动成功[{}]", addr);
for stream in listener.incoming() {
thread::spawn(move || {
let http = Server::new(&stream);
inaction(http);
});
}
}
}
::: tip Fn(Server):表示 F 是一个接受 Server 类型参数的函数闭包。 Send:表示 F 可以安全地在线程间传递。这对于可能在多线程环境中使用的闭包是必要的,以确保其数据和行为在不同线程之间传递时是安全的。 Copy:意味着 F 实现了 Copy trait,即可以通过简单的按位复制来创建新的实例,而不需要进行复杂的内存管理或资源共享。 Sync:表示 F 可以在多个线程中安全地被共享和访问。 'static:表示 F 不包含任何非 'static 生命周期的引用,即它可以在整个程序的生命周期内存在。 :::
延展思路
上述例子中的 Request 用于接收 stream 发来的数据,此外还可以封装一个 Client 用于实现类似 HttpClient 的方法,这需要读者自己去探索和实现了
::: tip 这距离LinkRust还有很大的差距,所以标题党了一回:smile: :::