XIKEW.COM - 实用教程 - LinkRust?试着封装Http - 实用教程,封装,rust - 试着将前面写的Http服务器相关代码进行封装,便于更好的学会如何写大项目!

LinkRust?试着封装Http
RUST 编程 学习 教程 9/27/2024 10:20:19 PM 阅读:1

试着将前面写的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 他需要传递给 ResponseRequest,这里就存在关于 stream 什么时候销毁的问题,我们得让计算机明确知道,这就是生命周期的使命! Description{{{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: :::