Rust Web 全栈开发(二):构建 HTTP Server
Rust Web 全栈开发(二):构建 HTTP Server
- Rust Web 全栈开发(二):构建 HTTP Server
- 解析 HTTP 请求
- HTTP 请求的构成
- 创建成员包/库:httpserver、http
- 构建 http 成员库
- httprequest
- httpresponse
- lib
Rust Web 全栈开发(二):构建 HTTP Server
参考视频:https://www.bilibili.com/video/BV1RP4y1G7KF
Web Server 的消息流动图:
Server:监听 TCP 字节流
Router:接收 HTTP 请求,并决定调用哪个 Handler
Handler:处理 HTTP 请求,构建 HTTP 响应
HTTP Library:
- 解释字节流,把它转换为 HTTP 请求
- 把 HTTP 响应转换回字节流
构建步骤:
- 解析 HTTP 请求消息
- 构建 HTTP 响应消息
- 路由与 Handler
- 测试 Web Server
解析 HTTP 请求
3 个数据结构:
名称 | 类型 | 描述 |
---|---|---|
HttpRequest | struct | 表示 HTTP 请求 |
Method | enum | 指定所允许的 HTTP 方法 |
Version | enum | 指定所允许的 HTTP 版本 |
以上 3 个数据结构都需要实现的 3 个 trait:
名称 | 描述 |
---|---|
From<&str> | 用于把传进来的字符串切片转换为 HttpRequest |
Debug | 打印调试信息 |
PartialEq | 用于解析和自动化测试脚本里做比较 |
HTTP 请求的构成
HTTP 请求报文由 3 部分组成:请求行、请求头、请求体。
创建成员包/库:httpserver、http
在原项目下新建成员包 httpserver、成员库 http:
cargo new httpserver
cargo new --lib http
在工作区内运行 cargo new 会自动将新创建的包添加到工作区内 Cargo.toml 的 [workspace] 定义中的 members 键中,如下所示:
此时,我们可以通过运行 cargo build 来构建工作区。项目目录下的文件应该是这样的:
├── Cargo.lock
├── Cargo.toml
├── httpserver
│ ├── Cargo.toml
│ └── src
│ └── main.rs
├── http
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── tcpclient
│ ├── Cargo.toml
│ └── src
│ └── main.rs
├── tcpserver
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
构建 http 成员库
在 http 成员库的 src 目录下新建两个文件:httprequest.rs、httpresponse.rs。
httprequest
打开 http 成员库中的 httprequest.rs,编写代码:
use std::collections::HashMap;#[derive(Debug, PartialEq)]
pub enum Method {Get,Post,Uninitialized,
}
impl From<&str> for Method {fn from(s: &str) -> Method {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,}}
}#[derive(Debug, PartialEq)]
pub enum Resource {Path(String),
}#[derive(Debug)]
pub struct HttpRequest {pub method: Method,pub resource: Resource,pub version: Version,pub headers: HashMap<String, String>,pub body: String,
}impl From<String> for HttpRequest {fn from(request: String) -> HttpRequest {let mut parsed_method = Method::Uninitialized;let mut parsed_resource = Resource::Path("".to_string());let mut parsed_version = Version::V1_1;let mut parsed_headers = HashMap::new();let mut parsed_body = "";for line in request.lines() {if line.contains("HTTP") {let (method, resource, version) = process_request_line(line);parsed_method = method;parsed_resource = resource;parsed_version = version;} else if line.contains(":") {let (key, value) = process_header_line(line);parsed_headers.insert(key, value);} else if line.len() == 0 {} else {parsed_body = line;}}HttpRequest {method: parsed_method,resource: parsed_resource,version: parsed_version,headers: parsed_headers,body: parsed_body.to_string(),}}
}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.to_string();}if let Some(v) = header_items.next() {value = v.to_string();}(key, value)
}fn process_request_line(s: &str) -> (Method, Resource, Version) {let mut words = s.split_whitespace();let method = words.next().unwrap();let resource = words.next().unwrap();let version = words.next().unwrap();(method.into(),Resource::Path(resource.to_string()),version.into())
}#[cfg(test)]
mod test {use super::*;#[test]fn test_method_into() {let method: Method = "GET".into();assert_eq!(method, Method::Get);}#[test]fn test_version_into() {let version: Version = "HTTP/1.1".into();assert_eq!(version, Version::V1_1);}#[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 headers_excepted = HashMap::new();headers_excepted.insert("Host".into(), " localhost".into());headers_excepted.insert("Accept".into(), " */*".into());headers_excepted.insert("User-Agent".into(), " curl/7.71.1".into());let request: HttpRequest = s.into();assert_eq!(request.method, Method::Get);assert_eq!(request.resource, Resource::Path("/greeting".to_string()));assert_eq!(request.version, Version::V1_1);assert_eq!(request.headers, headers_excepted);}
}
运行命令 cargo test -p http,测试 http 成员库。
3 个测试都通过了:
httpresponse
打开 http 成员库中的 httpresponse.rs,编写代码:
在这里插入代码片
lib
打开 http 成员库中的 lib.rs,编写代码:
pub mod httprequest;
pub mod httpresponse;