当前位置:   article > 正文

Rust Web入门(一):TCP 和 HTTP Server_rust tcp

rust tcp

本教程笔记来自 杨旭老师的 rust web 全栈教程,链接如下:

https://www.bilibili.com/video/BV1RP4y1G7KF?p=1&vd_source=8595fbbf160cc11a0cc07cadacf22951

学习 Rust Web 需要学习 rust 的前置知识可以学习杨旭老师的另一门教程

https://www.bilibili.com/video/BV1hp4y1k7SV/?spm_id_from=333.999.0.0&vd_source=8595fbbf160cc11a0cc07cadacf22951

项目的源代码可以查看 git:(注意作者使用的是 mysql 数据库而不是原教程的数据库)

https://github.com/aiai0603/rust_web_mysql

下面正式开始 Rust Web 全栈开发教程的学习:

TCP Server

在编写 web 之前,我们首先尝试编写一个能够相互通信的服务器和客户端程序,之后基于这个架构来编写我们的前后端交互的 web 应用。我们的通信程序基于 TCP 协议,这里使用了 rust 的 std::net 这个模块:

首先我们创建一个工作空间 s1 ,然后创建两个项目,再将我们的两个项目都包含进来

cargo new s1 
cd s1
cargo new tcpserver
cargo new tcpclient
  • 1
  • 2
  • 3
  • 4

s1 / Cargo.toml

[workspace]
members = ["tcpserver", "tcpclient"]
  • 1
  • 2

服务端

之后进入 tcpserver 项目的 main.rs,我们需要导入 TcpListener 这个包,它绑定一个端口,之后监听这个端口的信息,使用 incoming 方法,它返回一个迭代器,返回一个 Result ,我们使用 unwrap 方法可以拿到我们的数据

use std::io::{Read, Write};
use std::net::TcpListener;
fn main() {
    let listener = TcpListener::bind("127.0.0.1:3000").unwrap();
    println!("Listening on port 3000");
    for stream in listener.incoming() {
        let mut _stream = stream.unwrap();
        println!("connection established.");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

现在我们可以在命令行根目录运行这个服务

cargo run -p tcpserver
  • 1

客户端

之后我们编写我们的客户端,客户端需要在 127.0.0.1:3000 这个端口建立连接,我们使用

TcpStream::connect 这个函数。

use std::{
    io::{Read, Write},
    net::TcpStream,
    str,
};
fn main() {
    let mut _stream = TcpStream::connect("127.0.0.1:3000").unwrap();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

现在我们可以启动我们的客户端,可以看到它建立了连接

cargo run -p tcpclient
  • 1

收发数据

现在我们有了一个相互连接的客户端和服务端,之后我们希望他们相互可以发送消息,因为我们的服务器通信使用的 stream 它实现了 io 的 Write 和Read,所以我们可以使用相关的操作来接收和传递我们的消息。

在 tcpserver 里,我们使用新建一个 buff 缓存区来接收我们的数据,之后再使用 write 把它发送回去

use std::io::{Read, Write};
use std::net::TcpListener;
fn main() {
    let listener = TcpListener::bind("127.0.0.1:3000").unwrap();
    println!("Listening on port 3000");
    for stream in listener.incoming() {
        let mut _stream = stream.unwrap();
        println!("connection established.");
        let mut buffer = [0; 1024];
        _stream.read(&mut buffer).unwrap();
        _stream.write(&mut buffer).unwrap();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在 client 中,我们也是同样的操作,我们先写入一个 “hello” 字符串,要注意,传输的时候要传递原始的字节,所以要使用 as_bytes 进行转化,之后我们接收服务器返回的消息,根据刚才的代码,我们会收到 hello 的信息,我们将它输出

use std::{
    io::{Read, Write},
    net::TcpStream,
    str,
};
fn main() {
    let mut _stream = TcpStream::connect("127.0.0.1:3000").unwrap();
    _stream.write("hello".as_bytes()).unwrap();
    let mut buf = [0; 5];
    _stream.read(&mut buf).unwrap();
    println!("{}", str::from_utf8(&buf).unwrap());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

现在我们重新启动这两个程序,注意是先启动 server 再启动 client 可以看到在 client 打印出了 hello ,那么你的服务器就完成了。

HTTP Server

请添加图片描述

这是一个 web server 的消息流动图,当我们收到一个来自客户端浏览器的请求的时候,我们的服务器将请求放到路由器上,路由器决定我们调用哪个方法来提供一个响应返回客户端。

在 rust 中没有内置的HTTP 支持,所以我们需要的是监听 TCP 字节流,将它进行处理,分析出 HTTP 请求的信息,然后通过路由器决定调用的方法,所以这个项目的架构和之前的 TCP Server 服务器一致。我们创建一个 http 项目,然后在其中新建一个 httprequest.rs 和 一个 httpresponse.rs 作为我们服务器的收发工具

解析 HTTP

首先我们需要在之前的服务器的基础上增加解析 HTTP 请求的内容,我们需要三个数据结构来处理我们的请求, Method 和 Version 枚举来处理我们的收到请求的方法和版本,HttpRequest 结构体来处理我们一个请求的全部内容,他们需要实现三个Trait:

  • From<&str> 用于把传进来的字符串切片转化成处理完毕的信息,
  • Debug用于打印调试信息,
  • PartialEq 用于解析和自动化测试脚本里做比较

我们先编写枚举类,可以编写一个测试来实验一下我们的类是不是编写成功

use std::collections::HashMap;

#[derive(Debug, PartialEq)]
pub enum Method {
    Get,
    Post,
    Uninitialized,
}

impl From<&str> for Method {
    fn from(s: &str) -> Self {
        match s {
            "GET" => Method::Get,
            "POST" => Method::Post,
            _ => Method::Uninitialized,
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum Version {
    V1_1,
    V2_0,
    Uninitialized,
}
impl From<&str> for Version {
    fn from(s: &str) -> Version {
        match s {
            "HTTP/1.1" => Version::V1_1,
            _ => Version::Uninitialized,
        }
    }
}


mod tests {
    use super::*;

    #[test]
    fn test_method_into() {
        let m: Method = "GET".into();
        assert_eq!(m, Method::Get);
    }
    #[test]
    fn test_version_into() {
        let v: Version = "HTTP/1.1".into();
        assert_eq!(v, Version::V1_1);
    }

}

  • 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

之后我们编写我们的 HttpRequest 的解析,按照 HTTP 的规范,分别获取方法、版本、路径、请求头和参数,其中请求头中的参数我们按照 key-value 对的方式来存储,所以我们使用了 HashMap 这样的结构

//请求路径是一个String ,我们通过枚举来包装它
#[derive(Debug, PartialEq)]
pub enum Resource {
    Path(String),
}

#[derive(Debug)]
pub struct HttpRequest {
    pub method: Method,
    pub version: Version,
    pub resource: Resource,
    pub headers: HashMap<String, String>,
    pub msd_body: String,
}

impl From<String> for HttpRequest {
    fn from(req: String) -> Self {
        let mut parsed_method = Method::Uninitialized;
        let mut parsed_version = Version::V1_1;
        let mut parsed_resource = Resource::Path("".to_string());
        let mut parsed_headers = HashMap::new();
        let mut parsed_msg_body = "";

        for line in req.lines() {
            //判定是不是一个 HTTP 请求的开始
            if line.contains("HTTP") {
                let (method, resource, version) = process_req_line(line);
                parsed_method = method;
                parsed_resource = resource;
                parsed_version = version;
            } else if line.contains(":") {
                //头部是 key:value 形式的,所以可以这样判定是不是进入到头部
                let (key, value) = process_header_line(line);
                parsed_headers.insert(key, value);
            } else {
                //剩余部分是参数,用于post方法
                parsed_msg_body = line;
            }
        }
		//封装成一个 HttpRequest
        HttpRequest {
            method: parsed_method,
            version: parsed_version,
            resource: parsed_resource,
            headers: parsed_headers,
            msd_body: parsed_msg_body.to_string(),
        }
    }
}

fn process_req_line(s: &str) -> (Method, Resource, Version) {
    //用空白字符分割成数组,之后一个一个遍历就依次是我们需要的内容
    let mut words = s.split_whitespace();
    let methods = words.next().unwrap();
    let resource = words.next().unwrap();
    let version = words.next().unwrap();
    (
        methods.into(),
        Resource::Path(resource.to_string()),
        version.into(),
    )
}

//取出一个一个的 key - value 对
fn process_header_line(s: &str) -> (String, String) {
    let mut header_items = s.split(':');
    let mut key = String::from("");
    let mut value = String::from("");
    if let Some(k) = header_items.next() {
        key = k.trim().to_string();
    }
    if let Some(v) = header_items.next() {
        value = v.trim().to_string();
    }
    (key, value)
}
  • 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

然后我们再编写一个测试,如果顺利通过说明我们的代码正常

 #[test]
    fn test_read_http() {
        let s =
            String::from("GET /greeting HTTP/1.1\r\nHost: localhost:3000\r\nUser-Agent: curl/7.71.1\r\nAccept: */*\r\n\r\n'");
        let mut expected_header: HashMap<String, String> = HashMap::new();
        expected_header.insert("Host".into(), "localhost".into());
        expected_header.insert("Accept".into(), "*/*".into());
        expected_header.insert("User-Agent".into(), "curl/7.71.1".into());

        println!("{:?}", expected_header);
        let req: HttpRequest = s.into();
        assert_eq!(Method::Get, req.method);
        assert_eq!(Version::V1_1, req.version);
        assert_eq!(Resource::Path("/greeting".to_string()), req.resource);
        assert_eq!(expected_header, req.headers);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

HTTP 响应

对于一个收到的 HTTP 请求,我们的服务器需要做出应答,所以和解析 HTTP 一样,我们需要编写一个封装 HTTP 应答的类,我们先定义我们的结构,它包含版本,状态码,状态信息,头部和应答体,通过实现 Default Trait ,我们可以给我们的应答体一个默认值:

use std::collections::HashMap;
use std::io::{Result, Write};

#[derive(Debug, PartialEq, Clone)]
pub struct HttpResponse<'a> {
    version: &'a str,
    status_code: &'a str,
    status_text: &'a str,
    headers: Option<HashMap<&'a str, &'a str>>,
    body: Option<String>,
}

impl<'a> Default for HttpResponse<'a> {
    fn default() -> Self {
        Self {
            version: "HTTP/1.1".into(),
            status_code: "200".into(),
            status_text: "OK".into(),
            headers: None,
            body: None,
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

之后我们给他提供一个 new 方法来初始化一个 HttpResponse,根据传入的状态码来判定状态信息,并且为空的 headers 填充一个默认的 Content-Type 字段来保证程序的健壮性:

impl<'a> HttpResponse<'a> {
    pub fn new(
        status_code: &'a str,
        headers: Option<HashMap<&'a str, &'a str>>,
        body: Option<String>,
    ) -> HttpResponse<'a> {
        let mut response: HttpResponse<'a> = HttpResponse::default();
        if status_code != "200" {
            response.status_code = status_code.into();
        };
        response.headers = match &headers {
            Some(_h) => headers,
            None => {
                let mut h = HashMap::new();
                h.insert("Content-Type", "text/html");
                Some(h)
            }
        };
        response.status_text = match response.status_code {
            "200" => "OK".into(),
            "400" => "Bad Request".into(),
            "404" => "Not Found".into(),
            "500" => "Internal Server Error".into(),
            _ => "Not Found".into(),
        };
        response.body = body;
        response
    }
}
  • 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

之后我们实现一些 get 方法来获取我们的数据,我们将数据都返回为 字符串或者字符串切片类型,目的是为了之后可以更加方便的将他们整合到一起

impl<'a> HttpResponse<'a> {
    //...
 	fn version(&self) -> &str {
        self.version
    }

    fn status_code(&self) -> &str {
        self.status_code
    }
    fn status_text(&self) -> &str {
        self.status_text
    }

    fn headers(&self) -> String {
        let map: HashMap<&str, &str> = self.headers.clone().unwrap();
        let mut header_string: String = "".into();
        for (k, v) in map.iter() {
            header_string = format!("{}{}:{}\r\n", header_string, k, v);
        }
        header_string
    }
    pub fn body(&self) -> &str {
        match &self.body {
            Some(b) => b.as_str(),
            None => "",
        }
    }
}
  • 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

有了这些方法我们就可以将我们的数据整合为一个字符串了,之后再将这个字符串发送出去即可,为了整合我们的字符串,我们为 String 实现 From 这个 Trait,传入一个 HttpResponse 返回一个组装好的 HTTP 响应字符串:

impl<'a> From<HttpResponse<'a>> for String {
    fn from(res: HttpResponse) -> String {
        let res1 = res.clone();
        format!(
            "{} {} {}\r\n{}Content-Length: {}\r\n\r\n{}",
            &res1.version(),
            &res1.status_code(),
            &res1.status_text(),
            &res1.headers(),
            &res.body.unwrap().len(),
            &res1.body()
        )
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

有了这个 Trait 我们就可以实现我们的返回方法,将我们封装的 HttpResponse 变成一个标准的 HTTP 响应发送出去,它接收一个 stream ,也就是上文中 TCP 服务器中我们使用的 stream,因为我们需要用这个 stream 发送数据,所以我们限定它需要实现 Write 这个 Trait:

impl<'a> HttpResponse<'a> {
	//...
    pub fn send_response(&self, write_stream: &mut impl Write) -> Result<()> {
        let res = self.clone();
        let response_string: String = String::from(res);
        let _ = write!(write_stream, "{}", response_string);

        Ok(())
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

最后我们为我们的 HttpResponse 编写测试测试,整个 DEMO 的完整代码如下:

use std::collections::HashMap;
use std::io::{Result, Write};

#[derive(Debug, PartialEq, Clone)]
pub struct HttpResponse<'a> {
    version: &'a str,
    status_code: &'a str,
    status_text: &'a str,
    headers: Option<HashMap<&'a str, &'a str>>,
    body: Option<String>,
}

impl<'a> Default for HttpResponse<'a> {
    fn default() -> Self {
        Self {
            version: "HTTP/1.1".into(),
            status_code: "200".into(),
            status_text: "OK".into(),
            headers: None,
            body: None,
        }
    }
}

impl<'a> From<HttpResponse<'a>> for String {
    fn from(res: HttpResponse) -> String {
        let res1 = res.clone();
        format!(
            "{} {} {}\r\n{}Content-Length: {}\r\n\r\n{}",
            &res1.version(),
            &res1.status_code(),
            &res1.status_text(),
            &res1.headers(),
            &res.body.unwrap().len(),
            &res1.body()
        )
    }
}

impl<'a> HttpResponse<'a> {
    pub fn new(
        status_code: &'a str,
        headers: Option<HashMap<&'a str, &'a str>>,
        body: Option<String>,
    ) -> HttpResponse<'a> {
        let mut response: HttpResponse<'a> = HttpResponse::default();
        if status_code != "200" {
            response.status_code = status_code.into();
        };
        response.headers = match &headers {
            Some(_h) => headers,
            None => {
                let mut h = HashMap::new();
                h.insert("Content-Type", "text/html");
                Some(h)
            }
        };

        response.status_text = match response.status_code {
            "200" => "OK".into(),
            "400" => "Bad Request".into(),
            "404" => "Not Found".into(),
            "500" => "Internal Server Error".into(),
            _ => "Not Found".into(),
        };
        response.body = body;
        response
    }

    pub fn send_response(&self, write_stream: &mut impl Write) -> Result<()> {
        let res = self.clone();
        let response_string: String = String::from(res);
        let _ = write!(write_stream, "{}", response_string);

        Ok(())
    }

    fn version(&self) -> &str {
        self.version
    }

    fn status_code(&self) -> &str {
        self.status_code
    }
    fn status_text(&self) -> &str {
        self.status_text
    }

    fn headers(&self) -> String {
        let map: HashMap<&str, &str> = self.headers.clone().unwrap();
        let mut header_string: String = "".into();
        for (k, v) in map.iter() {
            header_string = format!("{}{}:{}\r\n", header_string, k, v);
        }
        header_string
    }
    pub fn body(&self) -> &str {
        match &self.body {
            Some(b) => b.as_str(),
            None => "",
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]

    fn test_response_struct_creation_200() {
        let response_actual = HttpResponse::new("200", None, Some("xxxx".into()));
        let response_expected = HttpResponse {
            version: "HTTP/1.1",
            status_code: "200",
            status_text: "OK",
            headers: {
                let mut h = HashMap::new();
                h.insert("Content-Type", "text/html");
                Some(h)
            },
            body: Some("xxxx".into()),
        };
        assert_eq!(response_actual, response_expected);
    }

    #[test]
    fn test_response_struct_creation_404() {
        let response_actual = HttpResponse::new("404", None, Some("xxxx".into()));
        let response_expected = HttpResponse {
            version: "HTTP/1.1",
            status_code: "404",
            status_text: "Not Found",
            headers: {
                let mut h = HashMap::new();
                h.insert("Content-Type", "text/html");
                Some(h)
            },
            body: Some("xxxx".into()),
        };
        assert_eq!(response_actual, response_expected);
    }

    #[test]
    fn test_http_response_creation() {
        let response_expected = HttpResponse {
            version: "HTTP/1.1",
            status_code: "404",
            status_text: "Not Found",
            headers: {
                let mut h = HashMap::new();
                h.insert("Content-Type", "text/html");
                Some(h)
            },
            body: Some("xxxx".into()),
        };
        let http_string: String = response_expected.into();
        let actual_string =
            "HTTP/1.1 404 Not Found\r\nContent-Type:text/html\r\nContent-Length: 4\r\n\r\nxxxx"
                .to_string();
        assert_eq!(http_string, actual_string);
    }
}
  • 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
  • 158
  • 159
  • 160
  • 161
  • 162

HTTP 服务器的编写

好了现在我们拥有了可以解析和发送 HTTP 请求响应的工具了,此时我们可以编写我们的服务器了,我们需要先将之前编写的 http 类引用进来,再安装我们需要的包,首先,我们在之前的 http 项目下新建一个 lib.rs 导出我们编写的模块

pub mod httprequest;
pub mod httpresponse;
  • 1
  • 2

之后我们新建一个 httpserver 项目,在 Cargo.toml 中导入我们的包,包括我们刚刚编写的 http 以及处理json 和 序列化的库

[package]
name = "httpserver"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
http = { path = "../http" }
serde = { version = "1.0.131", features = ["derive"] }
serde_json = "1.0.72"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

之后我们就可以开始编写我们的服务器了,首先我们在 src 文件夹下新建 server.rs 文件用于服务器的运行,它接收一个服务器的地址之后在指定的地址启动我们的服务器,循环接收数据,结构和之前的 TCP server 一致,之后调用我们刚刚编写的 HttpRequest 进行解析:

use http::httprequest::HttpRequest;
use std::io::prelude::*;
use std::net::TcpListener;
use std::str;

pub struct Server<'a> {
    socket_addr: &'a str,
}
impl<'a> Server<'a> {
    pub fn new(socket_addr: &'a str) -> Self {
        Server { socket_addr }
    }
    pub fn run(&self) {
        let connection_listener = TcpListener::bind(self.socket_addr).unwrap();
        println!("Running on {}", self.socket_addr);
        for stream in connection_listener.incoming() {
            let mut stream = stream.unwrap();
            println!("Connection established");
            let mut read_buffer = [0; 200];
            stream.read(&mut read_buffer).unwrap();
            let req: HttpRequest = String::from_utf8(read_buffer.to_vec()).unwrap().into();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

在 main.rs 中,我们调用我们刚刚编写的方法在本地启动我们的服务器

mod handler;
mod router;
mod server;
use server::Server;
fn main() {
    println!("Hello, world!");
    let server = Server::new("localhost:3000");
    server.run();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

现在我们的服务器可以运行了,之后我们要对我们接收到的 HttpRequest 进行处理,我们编写一个 router.rs 和 handler.rs 处理我们的请求,首先编写我们的 handler.rs ,我们定义一个 Handler ,它提供一个 handle 方法返回 http 响应,同时包含一个 load_file 函数加载我们需要的数据:

  • env!(“”) 可以拿到编译时的环境变量,env!(“CARGO_MANIFEST_DIR”)是调用环境变量拿到根目录
  • env::var(“”) 可以拿到构建时的环境变量,注意这些变量需要编写在文件中 .env 读取,我们暂时不编写我们的路径,那么就让这个路径等于我们的 default_path ,如果后期我们的项目发布了,可以通过配置 .env 文件来修改我们需要的路径
use http::{httprequest::HttpRequest, httpresponse::HttpResponse};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::env;
use std::fs;

pub trait Handler {
    fn handle(req: &HttpRequest) -> HttpResponse;
    fn load_file(file_name: &str) -> Option<String> {
        //取到文件存放的根目录
        let default_path = format!("{}/public", env!("CARGO_MANIFEST_DIR"));
        let public_path = env::var("PUBLIC_PATH").unwrap_or(default_path);
        //完整的文件名
        let full_path = format!("{}/{}", public_path, file_name);
        let contents = fs::read_to_string(full_path);
        contents.ok()
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

我们将我们需要提供的资源文件放在 public 文件夹下方,方便进行统一的管理,之后我们创建 404.html,index.html , health.html 和 styles.css 供我们测试使用。

我们将我们的响应分为三种,找不到请求内容的使用 PageNotFoundHandler ,加载静态资源的使用 StaticPageHandler ,加载接口数据的使用 WebServiceHandler ,他们都需要实现 Handler 这个 trait

pub struct StaticPageHandler;
pub struct PageNotFoundHandler;
pub struct WebServiceHandler;
  • 1
  • 2
  • 3
  • 对于 PageNotFoundHandler 我们直接返回404状态 ,并且提供一个 404.html 作为返回
impl Handler for PageNotFoundHandler {
    fn handle(_req: &HttpRequest) -> HttpResponse {
        HttpResponse::new("404", None, Self::load_file("404.html"))
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 对于 StaticPageHandler ,我们先分割请求路径获得我们需要资源的地址,如果是页面路径则加载对应的页面;如果是请求静态文件,对于不同的文件类型,我们需要加上不一样的头部
impl Handler for StaticPageHandler {
    fn handle(req: &HttpRequest) -> HttpResponse {
        let http::httprequest::Resource::Path(s) = &req.resource;
        let route: Vec<&str> = s.split("/").collect();
        match route[1] {
            "" => HttpResponse::new("200", None, Self::load_file("index.html")),
            "health" => HttpResponse::new("200", None, Self::load_file("health.html")),
            path => match Self::load_file(path) {
                Some(contents) => {
                    let mut map: HashMap<&str, &str> = HashMap::new();
                    if path.ends_with(".css") {
                        map.insert("Content-Tvpe", "text/css");
                    } else if path.ends_with(".js") {
                        map.insert("Content-Type", "text/javascript");
                    } else {
                        map.insert("Content-Type", "text/html");
                    }
                    HttpResponse::new("200", Some(map), Some(contents))
                }
                None => HttpResponse::new("404", None, Self::load_file("404.html")),
            },
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

对于 WebServiceHandler 我们需要提供一个 load_json 方法来处理我们的 json 文件,我们编写一个 orders.json 文件放在 data 文件夹下提供我们测试使用,这里我们只是用 json 文件做一个简单的测试,后续这部分将会替换成通过数据库查询得到的数据,成为一个完整的 api 后台

[
    {
        "order_id": 1,
        "order_date": "21 Jan 2020",
        "order_status": "Delivered"
    },
    {
        "order_id": 2,
        "order_date": "2 Feb 2020",
        "order_status": "Pending"
    }
]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

为了解析 json 我们需要我们的 serde_json 这个包,同时创建一个结构体来定义我们收到的 json 的每一项的内容,因为它对应的是 json 里的数据,所以我们要给他提供序列化和反序列化的方法

#[derive(Serialize, Deserialize)]
pub struct OrderStatus {
   order_id: i32
   order_date: String,
   order_status: String,
}
impl WebServiceHandler {
   fn load_json() -> Vec<OrderStatus> {
       let default_path = format!("{}/data", env!("CARGO_MANIFEST_DIR"));
       let data_path = env::var("DATA_PATH").unwrap_or(default_path);
       let full_path = format!("{}/{}", data_path, "orders.json");
       let json_contents = fs::read_to_string(full_path);
       let orders: Vec<OrderStatus> =
           serde_json::from_str(json_contents.unwrap().as_str()).unwrap();
       orders
   }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

这里我们假设我们的路径是 127.0.0.1:3000 / api / shipping / orders,通过分割字符串来解析出我们的路径判定 / shipping / orders 是不是匹配,如果路径正确,我们就将我们的 json 数据取出放到我们的 body 中返回,同时要修改我们的头部的来表示这是一组 json 数据,至于 / api 路径的处理,我们放在我们的 router 方法中:

impl Handler for WebServiceHandler {
    fn handle(req: &HttpRequest) -> HttpResponse {
        let http::httprequest::Resource::Path(s) = &req.resource;
        let route: Vec<&str> = s.split("/").collect();

        match route[2] {
            "shipping" if route.len() > 2 && route[3] == "orders" => {
                let body = Some(serde_json::to_string(&Self::load_json()).unwrap());
                let mut headers: HashMap<&str, &str> = HashMap::new();
                headers.insert("Content-Type", "application/json");
                HttpResponse::new("2oo", Some(headers), body)
            }
            _ => HttpResponse::new("404", None, Self::load_file("404.html")),
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

至此我们的 handler.rs 编写完毕,我们对静态资源,api接口和找不到路径三种情况进行判定,提供了返回,完整的代码如下:

use http::{httprequest::HttpRequest, httpresponse::HttpResponse};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::env;
use std::fs;

pub trait Handler {
    fn handle(req: &HttpRequest) -> HttpResponse;
    fn load_file(file_name: &str) -> Option<String> {
        let default_path = format!("{}/public", env!("CARGO_MANIFEST_DIR"));
        let public_path = env::var("PUBLIC_PATH").unwrap_or(default_path);
        let full_path = format!("{}/{}", public_path, file_name);
        let contents = fs::read_to_string(full_path);
        contents.ok()
    }
}

pub struct StaticPageHandler;
pub struct PageNotFoundHandler;
pub struct WebServiceHandler;

#[derive(Serialize, Deserialize)]
pub struct OrderStatus {
    order_id: i32,
    order_date: String,
    order_status: String,
}

impl Handler for PageNotFoundHandler {
    fn handle(_req: &HttpRequest) -> HttpResponse {
        HttpResponse::new("404", None, Self::load_file("404.html"))
    }
}

impl Handler for StaticPageHandler {
    fn handle(req: &HttpRequest) -> HttpResponse {
        let http::httprequest::Resource::Path(s) = &req.resource;
        let route: Vec<&str> = s.split("/").collect();
        match route[1] {
            "" => HttpResponse::new("200", None, Self::load_file("index.html")),
            "health" => HttpResponse::new("200", None, Self::load_file("health.html")),
            path => match Self::load_file(path) {
                Some(contents) => {
                    let mut map: HashMap<&str, &str> = HashMap::new();
                    if path.ends_with(".css") {
                        map.insert("Content-Tvpe", "text/css");
                    } else if path.ends_with(".js") {
                        map.insert("Content-Type", "text/javascript");
                    } else {
                        map.insert("Content-Type", "text/html");
                    }
                    HttpResponse::new("200", Some(map), Some(contents))
                }
                None => HttpResponse::new("404", None, Self::load_file("404.html")),
            },
        }
    }
}

impl WebServiceHandler {
    fn load_json() -> Vec<OrderStatus> {
        let default_path = format!("{}/data", env!("CARGO_MANIFEST_DIR"));
        let data_path = env::var("DATA_PATH").unwrap_or(default_path);
        let full_path = format!("{}/{}", data_path, "orders.json");
        let json_contents = fs::read_to_string(full_path);
        let orders: Vec<OrderStatus> =
            serde_json::from_str(json_contents.unwrap().as_str()).unwrap();
        orders
    }
}

impl Handler for WebServiceHandler {
    fn handle(req: &HttpRequest) -> HttpResponse {
        let http::httprequest::Resource::Path(s) = &req.resource;
        let route: Vec<&str> = s.split("/").collect();

        match route[2] {
            "shipping" if route.len() > 2 && route[3] == "orders" => {
                let body = Some(serde_json::to_string(&Self::load_json()).unwrap());
                let mut headers: HashMap<&str, &str> = HashMap::new();
                headers.insert("Content-Type", "application/json");
                HttpResponse::new("2oo", Some(headers), body)
            }
            _ => HttpResponse::new("404", None, Self::load_file("404.html")),
        }
    }
}
  • 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

最后我们编写我们的路由 router.rs , 它就很简单,分割我们拿到的 http 路径,根据路径和方法的不同分别调用不同的 handler 即可,这里我们先只处理 GET 请求,POST 的处理将会后续更新

use super::handler::{Handler, PageNotFoundHandler, StaticPageHandler, WebServiceHandler};
use http::{httprequest, httprequest::HttpRequest, httpresponse::HttpResponse};
use std::io::prelude::*;

pub struct Router;

impl Router {
    pub fn route(req: HttpRequest, stream: &mut impl Write) -> () {
        match req.method {
            httprequest::Method::Get => match &&req.resource {
                httprequest::Resource::Path(s) => {
                    let route: Vec<&str> = s.split('/').collect();
                    match route[1] {
                        "api" => {
                            let resp: HttpResponse = WebServiceHandler::handle(&req);
                            let _ = resp.send_response(stream);
                        }
                        _ => {
                            let resp: HttpResponse = StaticPageHandler::handle(&req);
                            let _ = resp.send_response(stream);
                        }
                    }
                }
            },
            _ => {
                let resp: HttpResponse = PageNotFoundHandler::handle(&req);
                let _ = resp.send_response(stream);
            }
        }
    }
}
  • 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

我们将我们的路由处理方法放在之前编写的 server.rs 中,现在每当你收到一个 HTTP 的请求,服务器就会解析你的路径,通过不同的方式进行处理后返回给你数据:

use super::router::Router;
use http::httprequest::HttpRequest;
use std::io::prelude::*;
use std::net::TcpListener;
use std::str;

pub struct Server<'a> {
    socket_addr: &'a str,
}

impl<'a> Server<'a> {
    pub fn new(socket_addr: &'a str) -> Self {
        Server { socket_addr }
    }

    pub fn run(&self) {
        let connection_listener = TcpListener::bind(self.socket_addr).unwrap();
        println!("Running on {}", self.socket_addr);
        for stream in connection_listener.incoming() {
            let mut stream = stream.unwrap();
            println!("Connection established");
            let mut read_buffer = [0; 200];
            stream.read(&mut read_buffer).unwrap();
            let req: HttpRequest = String::from_utf8(read_buffer.to_vec()).unwrap().into();

            Router::route(req, &mut stream)
        }
    }
}
  • 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

你可以在命令行中启动我们的项目,然后进行如下的测试

  • 在浏览器访问 http://localhost:3000/ 可以看到 index.html 的内容(有红色字体)
  • 在浏览器访问 http://localhost:3000/health 可以看到 health.html 的 内容
  • 在浏览器访问 http://localhost:3000/api/shipping/orders 可以看到一组订单数据
  • 在浏览器访问 http://localhost:3000/hhhhhhhhh 将会弹出 404.html 页面

如果你的测试完全符合预期,那么你的服务器编写成功了,否则你可以查看这个 git 仓库

https://github.com/aiai0603/rust_web_mysql

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/640948
推荐阅读
相关标签
  

闽ICP备14008679号