当前位置:   article > 正文

Rust Web(二)—— 自建HTTP Server_rust lib模式搭建server

rust lib模式搭建server

  • Rust没有自带HTTP支持,因此很多的方法及接口都需要开发者自行设计实现,不过对于Web Server,不同业务对其规模及性能的要求不尽相同,这样一想也情有可原;
  • 对于Rust基础以及HTTP原理,需要读者有所认识;
  • 本文的设计思路也可以自行设计扩展进而发展成更完整的方案;

目录

Rust Web(二)—— 自建HTTP Server

一、项目创建

二、解析HTTP请求

测试示例

全部实现

测试

三、构建HTTP响应

测试

四、构建Server模块

模块准备

功能实现

五、构建 Router & Handler 模块

实现代码

六、完整测试

运行


Rust Web(二)—— 自建HTTP Server

一、项目创建


  • 在自定的目录下,创建两个子项目目录

    • httpserver

    • http

      • http 为·lib 库,故命令中添加 --lib

  • 在根项目的 Cargo.toml文件中添加这两个子项目


  • 进入 http 子项目,在 src/lib.rs 内写入公共模块 pub mod httprequest;

  • 在同级 src 目录下新建:

    • httprequest.rs

    • httpresponse.rs

二、解析HTTP请求


  • httprequest.rs 中,先尝试实现 枚举 Method,并进行一次测试

  1. #[derive(Debug, PartialEq)]
  2. pub enum Method {
  3.    Get,
  4.    Post,
  5.    Uninitialized,
  6. }
  7. impl From<&str> for Method {
  8.    fn from(s: &str) -> Method {
  9.        match s {
  10.            "GET" => Method::Get,
  11.            "POST" => Method::Post,
  12.            _ => Method::Uninitialized,
  13.       }
  14.   }
  15. }
  16. #[cfg(test)]
  17. mod tests {
  18.    use super::*;
  19.    #[test]
  20.    fn test_method_into() {
  21.        let m: Method = "GET".into();
  22.        assert_eq!(m, Method::Get);
  23.   }
  24. }

测试示例

全部实现

  • 依照HTTP协议原理以及Rust本身的特性,先实现 http 库内的内容;

httprequest.rs

  1. use std::collections::HashMap;
  2. #[derive(Debug, PartialEq)]
  3. pub enum Method {
  4.    Get,
  5.    Post,
  6.    Uninitialized,
  7. }
  8. impl From<&str> for Method {
  9.    fn from(s: &str) -> Method {
  10.        match s {
  11.            "GET" => Method::Get,
  12.            "POST" => Method::Post,
  13.            _ => Method::Uninitialized,
  14.       }
  15.   }
  16. }
  17. #[derive(Debug, PartialEq)]
  18. pub enum Version {
  19.    V11,
  20.    V20,
  21.    Uninitialized,
  22. }
  23. impl From<&str> for Version {
  24.    fn from(s: &str) -> Version {
  25.        match s {
  26.            "HTTP/1.1" => Version::V11,
  27.            "HTTP/2.0" => Version::V20,
  28.            _ => Version::Uninitialized,
  29.       }
  30.   }
  31. }
  32. #[derive(Debug, PartialEq)]
  33. pub enum Resource {
  34.    Path(String),
  35. }
  36. #[derive(Debug)]
  37. pub struct HttpRequest {
  38.    pub method: Method,
  39.    pub version: Version,
  40.    pub resource: Resource,
  41.    pub headers: HashMap<String, String>,
  42.    pub msg_body: String,
  43. }
  44. impl From<String> for HttpRequest {
  45.    fn from(req: String) -> Self {
  46.        let mut parsed_method = Method::Uninitialized;
  47.        let mut parsed_version = Version::V11;
  48.        let mut parsed_resource = Resource::Path("".to_string());
  49.        let mut parsed_headers = HashMap::new();
  50.        let mut parsed_msg_body = "";
  51.        for line in req.lines() {
  52.            if line.contains("HTTP") {
  53.                let (method, resource, version) = process_req_line(line);
  54.                parsed_method = method;
  55.                parsed_resource = resource;
  56.                parsed_version = version;
  57.           } else if line.contains(":") {
  58.                let (key, value) = process_header_line(line);
  59.                parsed_headers.insert(key, value);
  60.           } else if line.len() == 0 {
  61.                // No operation
  62.           } else {
  63.                parsed_msg_body = line;
  64.           }
  65.       }
  66.         HttpRequest {
  67.                method: parsed_method,
  68.                resource: parsed_resource,
  69.                version: parsed_version,
  70.                headers: parsed_headers,
  71.                msg_body: parsed_msg_body.to_string(),
  72.           }
  73.   }
  74. }
  75. fn process_req_line(s: &str) -> (Method, Resource, Version) {
  76.    let mut words = s.split_whitespace();
  77.    let method = words.next().unwrap();
  78.    let resource = words.next().unwrap();
  79.    let version = words.next().unwrap();
  80.   (
  81.        method.into(),
  82.        Resource::Path(resource.to_string()),
  83.        version.into()
  84.   )
  85. }
  86. fn process_header_line(s: &str) -> (String, String) {
  87.    let mut header_items = s.split(":");
  88.    let mut key = String::from("");
  89.    let mut value = String::from("");
  90.    if let Some(k) = header_items.next() {
  91.        key = k.to_string();
  92.   }
  93.    if let Some(v) = header_items.next() {
  94.        value = v.to_string();
  95.   }
  96.   (key, value)
  97. }
  98. #[cfg(test)]
  99. mod tests {
  100.    use super::*;
  101.    #[test]
  102.    fn test_method_into() {
  103.        let m: Method = "GET".into();
  104.        assert_eq!(m, Method::Get);
  105.   }
  106.    #[test]
  107.    fn test_version_into() {
  108.        let v: Version = "HTTP/1.1".into();
  109.        assert_eq!(v, Version::V11);
  110.   }
  111.    #[test]
  112.    fn test_read_http() {
  113.        let s: String = String::from("GET /index HTTP/1.1\r\n\
  114.       Host: localhost\r\n\
  115.       User-Agent: Curl/7.64.1\r\n\
  116.       Accept: */*\r\n\r\n");
  117.        let mut headers_expected = HashMap::new();
  118.        headers_expected.insert("Host".into(), " localhost".into());
  119.        headers_expected.insert("User-Agent".into(), " Curl/7.64.1".into());
  120.        headers_expected.insert("Accept".into(), " */*".into());
  121.        let req: HttpRequest = s.into();
  122.        assert_eq!(Method::Get, req.method);
  123.        assert_eq!(Resource::Path("/index".to_string()), req.resource);
  124.        assert_eq!(Version::V11, req.version);
  125.        assert_eq!(headers_expected, req.headers);
  126.   }
  127. }

测试

  • 测试结果

  • 编写过程中以下问题值得注意

    • 测试请求中的大小写要严格区分;

    • 由于请求头部仅以冒号分割,因此值 value 内的空格不能忽略,或者进行进一步优化;

三、构建HTTP响应


  • 以下为自建库的响应构建部分;

httpresponse.rs

  1. use std::collections::HashMap;
  2. use std::io::{Result, Write};
  3. // 当涉及到成员变量中有引用类型,就需要引入生命周期
  4. #[derive(Debug, PartialEq, Clone)]
  5. pub struct HttpResponse<'a> {
  6.    version: &'a str,
  7.    status_code: &'a str,
  8.    status_text: &'a str,
  9.    headers: Option<HashMap<&'a str, &'a str>>,
  10.    body: Option<String>,
  11. }
  12. impl<'a> Default for HttpResponse<'a> {
  13.    fn default() -> Self {
  14.        Self {
  15.            version: "HTTP/1.1".into(),
  16.            status_code: "200".into(),
  17.            status_text: "OK".into(),
  18.            headers: None,
  19.            body: None,
  20.       }
  21.   }
  22. }
  23. impl<'a> From<HttpResponse<'a>> for String {
  24.    fn from(res: HttpResponse) -> String {
  25.        let res1 = res.clone();
  26.        format!(
  27.            "{} {} {}\r\n{}Content-Length: {}\r\n\r\n{}",
  28.           &res1.version(),
  29.           &res1.status_code(),
  30.           &res1.status_text(),
  31.           &res1.headers(),
  32.           &res.body.unwrap().len(),
  33.           &res1.body() //
  34.       )
  35.   }
  36. }
  37. impl<'a> HttpResponse<'a> {
  38.    pub fn new(
  39.        status_code: &'a str,
  40.        headers: Option<HashMap<&'a str, &'a str>>,
  41.        body: Option<String>
  42.   ) -> HttpResponse<'a> {
  43.        let mut response: HttpResponse<'a> = HttpResponse::default(); // mut
  44.        if status_code != "200" {
  45.            response.status_code = status_code.into();
  46.       };
  47.        response.headers = match &headers {
  48.            Some(_h) => headers,
  49.            None => {
  50.                let mut h = HashMap::new();
  51.                h.insert("Content-Type", "text/html");
  52.                Some(h)
  53.           }
  54.       };
  55.        response.status_text = match response.status_code {
  56.            "200" => "OK".into(),
  57.            "400" => "Bad Request".into(),
  58.            "404" => "Not Found".into(),
  59.            "500" => "Internal Server Error".into(),
  60.            _ => "Not Found".into(), //
  61.       };
  62.        response.body = body;
  63.        response
  64.   }
  65.    pub fn send_response(&self, write_stream: &mut impl Write) -> Result<()> {
  66.        let res = self.clone();
  67.        let response_string: String = String::from(res); // from trait
  68.        let _ = write!(write_stream, "{}", response_string);
  69.        Ok(())
  70.   }
  71.    fn version(&self) -> &str {
  72.        self.version
  73.   }
  74.    fn status_code(&self) -> &str {
  75.        self.status_code
  76.   }
  77.    fn status_text(&self) -> &str {
  78.        self.status_text
  79.   }
  80.    fn headers(&self) -> String {
  81.        let map: HashMap<&str, &str> = self.headers.clone().unwrap();
  82.        let mut header_string: String = "".into();
  83.        for (k, v) in map.iter() {
  84.            header_string = format!("{}{}:{}\r\n", header_string, k, v);
  85.       }
  86.        header_string
  87.   }
  88.    pub fn body(&self) -> &str {
  89.        match &self.body {
  90.            Some(b) => b.as_str(),
  91.            None => "",
  92.       }
  93.   }
  94. }
  95. #[cfg(test)]
  96. mod tests {
  97.    use super::*;
  98.    #[test]
  99.    fn test_response_struct_creation_200() {
  100.        let response_actual = HttpResponse::new(
  101.            "200",
  102.            None,
  103.            Some("nothing for now".into()),
  104.       );
  105.        let response_expected = HttpResponse {
  106.            version: "HTTP/1.1",
  107.            status_code: "200",
  108.            status_text: "OK",
  109.            headers: {
  110.                let mut h = HashMap::new();
  111.                h.insert("Content-Type", "text/html");
  112.                Some(h)
  113.           },
  114.            body: Some("nothing for now".into()),
  115.       };
  116.        assert_eq!(response_actual, response_expected);
  117.   }
  118.    #[test]
  119.    fn test_response_struct_creation_404() {
  120.        let response_actual = HttpResponse::new(
  121.            "404",
  122.            None,
  123.            Some("nothing for now".into()),
  124.       );
  125.        let response_expected = HttpResponse {
  126.            version: "HTTP/1.1",
  127.            status_code: "404",
  128.            status_text: "Not Found",
  129.            headers: {
  130.                let mut h = HashMap::new();
  131.                h.insert("Content-Type", "text/html");
  132.                Some(h)
  133.           },
  134.            body: Some("nothing for now".into()),
  135.       };
  136.        assert_eq!(response_actual, response_expected);
  137.   }
  138.    #[test]
  139.    fn test_http_response_creation() {
  140.        let response_expected = HttpResponse {
  141.            version: "HTTP/1.1",
  142.            status_code: "404",
  143.            status_text: "Not Found",
  144.            headers: {
  145.                let mut h = HashMap::new();
  146.                h.insert("Content-Type", "text/html");
  147.                Some(h)
  148.           },
  149.            body: Some("nothing for now".into()),
  150.       };
  151.        let http_string: String = response_expected.into();
  152.        let actual_string: String =
  153.            "HTTP/1.1 404 Not Found\r\n\
  154.           Content-Type:text/html\r\n\
  155.           Content-Length: 15\r\n\r\n\
  156.           nothing for now".into(); // 此处注意Content-Length值
  157.        assert_eq!(http_string, actual_string);
  158.   }
  159. }

测试

  • 测试结果

  • 其中需要留意的点位

    • 在实现 Stringtrait 时,不能从 &res1.body 获取长度,以避免内部 body 成员的所有权转移;

    • 测试整个相应,自定义响应实例中的请求体数据长度要保持一致;

四、构建Server模块


模块准备

  • 此时转至 httpserver 子项目内,将前文所涉及的 http 子项目导入 Cargo.toml 文件;

  • 并在 httpserver/src 下再创建三文件

    • server.rs

    • router.rs

    • handler.rs

功能实现

  • 大概的调用逻辑

    • main - 调用 -> server - 调用 -> router - 调用 -> handler

server.rs

  1. use super::router::Router;
  2. use http::httprequest::HttpRequest;
  3. use std::io::prelude::*;
  4. use std::net::TcpListener;
  5. use std::str;
  6. pub struct Server<'a> {
  7.    socket_addr: &'a str,
  8. }
  9. impl<'a> Server<'a> {
  10.    pub fn new(socket_addr: &'a str) -> Self {
  11.        Server {socket_addr}
  12.   }
  13.    pub fn run(&self) {
  14.        let connection_listener = TcpListener::bind(self.socket_addr).unwrap();
  15.        println!("Running on {}", self.socket_addr);
  16.        for stream in connection_listener.incoming() {
  17.            let mut stream = stream.unwrap();
  18.            println!("Connection established");
  19.            let mut read_buffer = [0; 200];
  20.            stream.read(&mut read_buffer).unwrap();
  21.            let req: HttpRequest = String::from_utf8( read_buffer.to_vec()).unwrap().into();
  22.            Router::route(req, &mut stream);
  23.       }
  24.   }
  25. }
  • 实现至当前阶段还不能直接运行;

五、构建 Router & Handler 模块


  • 这两个模块联合起来处理接收到的请求,其中

    • 判定请求的合法性,适当返回错误反馈;

    • 解析后台的数据部分,进行相应的序列化和反序列化;

  • 不同的请求状况交由不同类型的句柄 Handler 来处理,同名可重写的方法通过 Trait 来定义;

  • 其中的 handler.rs 需要引入两个crate

    • serde (本文使用的是1.0.140版本)

    • serde_json (本文使用的是1.0.82版本)

实现代码

router.rs

  1. use super::handler::{Handler, PageNotFoundHandler, StaticPageHandler, WebServiceHandler};
  2. use http::{httprequest, httprequest::HttpRequest, httpresponse::HttpResponse};
  3. use std::io::prelude::*;
  4. pub struct Router;
  5. impl Router {
  6. pub fn route(req: HttpRequest, stream: &mut impl Write) -> () {
  7. match req.method {
  8. httprequest::Method::Get => match &req.resource {
  9. httprequest::Resource::Path(s) => {
  10. let route: Vec<&str> = s.split("/").collect();
  11. match route[1] {
  12. "api" => {
  13. let resp: HttpResponse = WebServiceHandler::handle(&req);
  14. let _ = resp.send_response(stream);
  15. },
  16. _ => {
  17. let resp: HttpResponse = StaticPageHandler::handle(&req);
  18. let _ = resp.send_response(stream);
  19. }
  20. }
  21. }
  22. },
  23. _ => {
  24. let resp: HttpResponse = PageNotFoundHandler::handle(&req);
  25. let _ = resp.send_response(stream);
  26. }
  27. }
  28. }
  29. }

handler.rs

  1. use http::{httprequest::HttpRequest, httpresponse::HttpResponse};
  2. use serde::{Deserialize, Serialize};
  3. use std::collections::HashMap;
  4. use std::env;
  5. use std::fs;
  6. use std::ops::Index;
  7. pub trait Handler {
  8.    fn handle(req: &HttpRequest) -> HttpResponse;
  9.    fn load_file(file_name: &str) -> Option<String> {
  10.        let default_path = format!("{}/public", env!("CARGO_MANIFEST_DIR"));
  11.        let public_path = env::var("PUBLIC_PATH").unwrap_or(default_path);
  12.        let full_path = format!("{}/{}", public_path, file_name);
  13.        let contents = fs::read_to_string(full_path);
  14.        contents.ok()
  15.   }
  16. }
  17. pub struct StaticPageHandler;
  18. pub struct PageNotFoundHandler;
  19. pub struct WebServiceHandler;
  20. #[derive(Serialize, Deserialize)]
  21. pub struct OrderStatus {
  22.    order_id: i32,
  23.    order_date: String,
  24.    order_status: String,
  25. }
  26. impl Handler for PageNotFoundHandler {
  27.    fn handle(_req: &HttpRequest) -> HttpResponse {
  28.        HttpResponse::new("404", None, Self::load_file("404.html"))
  29.   }
  30. }
  31. impl Handler for StaticPageHandler {
  32.    fn handle(req: &HttpRequest) -> HttpResponse {
  33.        let http::httprequest::Resource::Path(s) = &req.resource;
  34.        let route: Vec<&str> = s.split("/").collect();
  35.        match route[1] {
  36.            "" => HttpResponse::new("200", None, Self::load_file("index.html")),
  37.            "health" => HttpResponse::new("200", None, Self::load_file("health.html")),
  38.            path => match Self::load_file(path) {
  39.                Some(contents) => {
  40.                    let mut map: HashMap<&str, &str> = HashMap::new();
  41.                    if path.ends_with(".css") {
  42.                        map.insert("Content-Type", "text/css");
  43.                   } else if path.ends_with(".js") {
  44.                        map.insert("Content-Type", "text/javascript");
  45.                   } else {
  46.                        map.insert("Content-Type", "text/html");
  47.                   }
  48.                    HttpResponse::new("200", Some(map), Some(contents))
  49.               },
  50.                None => HttpResponse::new("404", None, Self::load_file("404.html"))
  51.           }
  52.       }
  53.   }
  54. }
  55. impl WebServiceHandler {
  56.    fn load_json() -> Vec<OrderStatus> {
  57.        let default_path = format!("{}/data", env!("CARGO_MANIFEST_DIR"));
  58.        let data_path = env::var("DATA_PATH").unwrap_or(default_path);
  59.        let full_path = format!("{}/{}", data_path, "orders.json");
  60.        let json_contents = fs::read_to_string(full_path);
  61.        let orders: Vec<OrderStatus> = serde_json::from_str(json_contents.unwrap().as_str()).unwrap();
  62.        orders
  63.   }
  64. }
  65. impl Handler for WebServiceHandler {
  66.    fn handle(req: &HttpRequest) -> HttpResponse {
  67.        let http::httprequest::Resource::Path(s) = &req.resource;
  68.        let route: Vec<&str> = s.split("/").collect();
  69.        // localhost:2333/api/air/orders
  70.        match route[2] {
  71.            "air" if route.len() > 2 && route[3] == "orders" => {
  72.                let body = Some(serde_json::to_string(&Self::load_json()).unwrap());
  73.                let mut headers: HashMap<&str, &str> = HashMap::new();
  74.                headers.insert("Content-Type", "application/json");
  75.                HttpResponse::new("200", Some(headers), body)
  76.           },
  77.            _ => HttpResponse::new("404", None, Self::load_file("404.html"))
  78.       }
  79.   }
  80. }

六、完整测试


  • httpserver 项目中分别添加

    • data/orders.json

    • public/index.html

    • public/404.html

    • public/health.html

    • styles.css

  • 测试文件内容

  1. <!-- index.html -->
  2. <!DOCTYPE html>
  3. <html lang="en">
  4.  <head>
  5.    <meta charset="utf-8" />
  6.    <link rel="stylesheet" href="styles.css">
  7.    <title>Index</title>
  8.  </head>
  9.  <body>
  10.    <h1>Hello,welcome to home page</h1>
  11.    <p>This is the index page for the web site</p>
  12.  </body>
  13. </html>

  1. <!-- health.html -->
  2. <!DOCTYPE html>
  3. <html lang="en">
  4.  <head>
  5.    <meta charset="utf-8" />
  6.    <title>Health!</title>
  7.  </head>
  8.  <body>
  9.    <h1>Hello,welcome to health page!</h1>
  10.    <p>This site is perfectly fine</p>
  11.  </body>
  12. </html>

  1. <!-- 404.html -->
  2. <!DOCTYPE html>
  3. <html lang="en">
  4. <head>
  5. <meta charset="utf-8" /> <title>Not Found!</title>
  6.  </head>
  7.  <body>
  8.    <h1>404 Error</h1>
  9.    <p>Sorry! The requested page does not exist</p>
  10.  </body>
  11. </html>

  1. /* styles.css */
  2. h1 {
  3.     color: red;
  4.     margin-left: 25px;
  5. }

  1. // orders.json
  2. [
  3. {
  4.    "order_id": 1,
  5.    "order_date": "20 June 2022",
  6.    "order_status": "Delivered"
  7. },
  8. {
  9.    "order_id": 2,
  10.    "order_date": "27 October 2022",
  11.    "order_status": "Pending"
  12. }
  13. ]

运行

  • 效果如下

访问 index.html

访问 health.html

访问 orders.json

访问一个错误地址

  • 至此,HTTP的基本功能实现就到此为止;

  • 可以基于此框架做性能优化以及扩展自己所需要的功能;

  • 通过本次HTTP《简易》设计,可以更深刻地体会一些后端设计思想、Rust本身的特点以及基于HTTP协议的Server设计思路;

每一个不曾起舞的日子,都是对生命的辜负。

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

闽ICP备14008679号