使用Rust构建HTTP Server
   笔记Rust基础    0 comment
使用Rust构建HTTP Server
   笔记Rust基础    0 comment

前言

要完成一次客户端和服务端之间的网络通信,需要一种通信协议。客服端通过该协议发起请求,服务端接受请求并响应,这种协议就是Http/Https。此项目使用纯Rust来构建简易的Http服务体系,将主要分别对客户端、服务端的请求和响应的模式、服务模块等进行分析。

1.客户端

客户端要实现对服务地址以及端口的监听,并写入buff数据。

use std::net::TcpStream;
use std::io::{Read,Write};
use std::str;
fn main() {
    let mut stream = TcpStream::connect("localhost:3000").unwrap();

    stream.write("Hello".as_bytes()).unwrap();

    let mut buffer = [0;5];

    stream.read(&mut buffer).unwrap();

    println!("Received from server:{}", str::from_utf8(&buffer).unwrap());

}

2.服务端

服务端要完成对请求体的解析、返回响应体格式数据、模块化路由等工作

  1. server.rs module 服务的主体部分,管理和分配route和对应的handler
  2. router.rs module 实现对路由的响应和分配
  3. handler.rs module 路由的处理函数

2.1 解析请求体

#[derive(Debug)]
pub struct HttpRequest{
    pub method : Methods,
    pub version: Version,
    pub headers: HashMap<String,String>,
    pub resource: Resource,
    pub body:String
}
impl From<String> for HttpRequest{
    fn from(value: String) -> Self {
        let mut parsed_method = Methods::Uninitialized;
        let mut parsed_version = Version::Uninitialized;
        let mut parsed_header = HashMap::new();
        let mut parsed_resource = Resource::Path("".to_string());
        let mut parsed_body = "";

        for line in value.lines() {
            if line.contains("HTTP"){
                let (method,version,resource) = process_req_line(line);
                parsed_method = method;
                parsed_version = version;
                parsed_resource = resource;
            }else if line.contains(":") {
                let (key,value) = process_header_line(line);
                parsed_header.insert(key,value);
            }else {
                parsed_body = line;
            }
        }
        HttpRequest{
            method:parsed_method,
            version:parsed_version,
            headers:parsed_header,
            resource:parsed_resource,
            body:parsed_body.to_string()
        }
    }
}
fn process_req_line(line: &str) -> (Methods,Version,Resource) {
    let mut line  = line.split_whitespace();
    //这的顺序非常重要!!
    let  method = line.next().unwrap();
    let  resource = line.next().unwrap();
    let  version = line.next().unwrap();

    (
        method.into(),
        version.into(),
        Resource::Path(resource.to_string())
    )

}
fn process_header_line (line:&str) -> (String,String) {
    let mut line = line.split(":");
    let mut key  = String::from("");
    let mut value = String::from("");

    if let Some(k) = line.next(){
        key = k.to_string();
    }
    if let Some(v) = line.next(){
        value = v.to_string();
    }
    (key,value)
}

2.2 返回响应体

#[derive(Debug,PartialEq,Clone)]
pub struct HttpResponse<'a> {
    version: &'a str,
    status_code: u16,
    reason_phrase: &'a str,
    headers: Option<HashMap<&'a str,&'a str>>,
    body: Option<String>,
}
//实现default trait
impl <'a> Default for HttpResponse<'a> {
    fn default() -> Self {
        HttpResponse {
            version: "HTTP/1.1",
            status_code: 200,
            reason_phrase: "OK",
            headers: None,
            body: None,
        }
    }
}
impl <'a> From<HttpResponse<'a>> for String {
    fn from(response: HttpResponse<'a>) -> String {
        let result = response.clone();
        format!(
            "{} {} {}\r\n{}\r\n{}",
            result.version(),
            result.status_code(),
            result.reason_phrase(),
            result.headers(),
            result.body(),
        )
    }
}
//实现new 方法
impl <'a> HttpResponse<'a> {
    //实现new放方法
    pub fn new(
        status_code: u16,
        headers: Option<HashMap<&'a str,&'a str>>,
        body: Option<String>,
    ) -> HttpResponse {
            let mut response = HttpResponse::default();
            if status_code != 200 {
                response.status_code = status_code
            };
            response.reason_phrase = match response.status_code {
                200 => "OK",
                404 => "Not Found",
                500 => "Internal Server Error",
                501 => "Not Implemented",
                303 => "See Other",
                _ => "Unknown",
            };
            response.headers = match &headers {
                Some(_h) => headers,
                None => {
                    let mut h = HashMap::new();
                    h.insert("Content-Type", "text/html");
                    Some(h)
                }
            };
            response.body = body;
            response
        }
    //实现send方法
    pub fn send(&self, stream: &mut impl Write) -> Result<(), ()>{
        let response = self.clone();
        let res_string = String::from(response);
        println!("response:\r\n{}",res_string);
        let _ = write!(stream, "{}", res_string);
        Ok(())
    }
    //返回响应体各个部分的方法
    fn version(&self) -> &str {
        self.version
    }
    fn status_code(&self) -> u16 {
        self.status_code
    }
    fn reason_phrase(&self) -> &str {
        self.reason_phrase
    }
    pub fn headers(&self) -> String {
        let map = self.headers.clone().unwrap();
        let mut header_string = String::from("").into();
        for (key, value) in map.iter() {
            header_string = format!("{}{}: {}\r\n", header_string, key, value);
        };
        header_string
    }
    pub fn body(&self) -> &str {
        match &self.body {
            Some(b) => b.as_str(),
            None => "",
        }
    }
}

2.3 处理返回的静态页面和json数据等

服务端是通过匹配路由的状态码(由Request所携带),通过内置的read方法读取文件并返回返回静态页面,以及Json数据等。下面是部分处理代码:

fn handle(req: &HttpRequest) -> HttpResponse {
        let httplib::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")),
            "about" => HttpResponse::new(200, None, Self::load_file("about.html")),
            path => match Self::load_file(path) {
                Some(contents) => {
                    let mut map = HashMap::new();
                    if path.ends_with(".js") {
                        map.insert("Content-type", "text/javascript");
                    } else if path.ends_with(".css") {
                        map.insert( "Content-type", "text/css");
                    } else {
                        map.insert("Content-type", "text/html");
                    }
                    HttpResponse::new(200, Some(map), Some(contents))
                },
                None => HttpResponse::new(404, None, Self::load_file("404.html"))
            }
        }
    }

3.写在最后

核心要点:

1.    客户端监听本地端口,写入buffer。
2.    请求体解析、响应体的返回。
3.    handler处理返回数据、错误情况等。

:star2:如果你对此感到有兴趣,可参考Rust Client-Server

Responses