Rust Web 全棧開發(二):構建 HTTP Server

Rust Web 全棧開發(二):構建 HTTP Server

  • Rust Web 全棧開發(二):構建 HTTP Server
    • 創建成員包/庫:httpserver、http
    • 解析 HTTP 請求
      • HTTP 請求的構成
      • 構建 HttpRequest
    • 構建 HTTP 響應
      • HTTP 響應的構成
      • 構建 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 響應轉換回字節流

構建步驟:

  1. 解析 HTTP 請求消息
  2. 構建 HTTP 響應消息
  3. 路由與 Handler
  4. 測試 Web Server

創建成員包/庫:httpserver、http

在原項目下新建成員包 httpserver、成員庫 http:

cargo new httpserver
cargo new --lib http

在這里插入圖片描述

在工作區內運行 cargo new 會自動將新創建的包添加到工作區內 Cargo.toml 的 [workspace] 定義中的 members 鍵中,如下所示:

在這里插入圖片描述

在 http 成員庫的 src 目錄下新建兩個文件:httprequest.rs、httpresponse.rs。

此時,我們可以通過運行 cargo build 來構建工作區。項目目錄下的文件應該是這樣的:

├── Cargo.lock
├── Cargo.toml
├── httpserver
│   ├── Cargo.toml
│   └── src
│       └── main.rs
├── http
│   ├── Cargo.toml
│   └── src
│       └── lib.rs
│       └── httprequest.rs
│       └── httpresponse.rs
├── tcpclient
│   ├── Cargo.toml
│   └── src
│       └── main.rs
├── tcpserver
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── target

解析 HTTP 請求

HTTP 請求的構成

HTTP 請求報文由 3 部分組成:請求行、請求頭、請求體。

在這里插入圖片描述

構建 HttpRequest

3 個數據結構:

名稱類型描述
HttpRequeststruct表示 HTTP 請求
Methodenum指定所允許的 HTTP 方法
Versionenum指定所允許的 HTTP 版本

以上 3 個數據結構都需要實現的 3 個 trait:

名稱描述
From<&str>用于把傳進來的字符串切片轉換為 HttpRequest
Debug打印調試信息
PartialEq用于解析和自動化測試腳本里做比較

打開 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 個測試都通過了:

在這里插入圖片描述

構建 HTTP 響應

HTTP 響應的構成

HTTP 響應報文由 3 部分組成:響應行、響應頭、響應體。

在這里插入圖片描述

構建 HttpResponse

HttpResponse 需要實現的方法或 trait:

名稱描述
Default trait指定成員的默認值
From trait將 HttpResponse 轉化為 String
new()使用默認值創建一個新的 HttpResponse 結構體
getter 方法獲取 HttpResponse 成員變量的值
send_response()構建響應,將原始字節通過 TCP 傳送

打開 http 成員庫中的 httpresponse.rs,編寫代碼:

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(response: HttpResponse) -> String {let res = response.clone();format!("{} {} {}\r\n{}Content-Length: {}\r\n\r\n{}",&res.version(),&res.status_code(),&res.status_text(),&res.headers(),&response.body.unwrap().len(),&res.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.status_text = match response.status_code {// 消息"100" => "Continue".into(),"101" => "Switching Protocols".into(),"102" => "Processing".into(),// 成功"200" => "OK".into(),"201" => "Created".into(),"202" => "Accepted".into(),"203" => "Non-Authoritative Information".into(),"204" => "No Content".into(),"205" => "Reset Content".into(),"206" => "Partial Content".into(),"207" => "Multi-Status".into(),// 重定向"300" => "Multiple Choices".into(),"301" => "Moved Permanently".into(),"302" => "Move Temporarily".into(),"303" => "See Other".into(),"304" => "Not Modified".into(),"305" => "Use Proxy".into(),"306" => "Switch Proxy".into(),"307" => "Temporary Redirect".into(),// 請求錯誤"400" => "Bad Request".into(),"401" => "Unauthorized".into(),"402" => "Payment Required".into(),"403" => "Forbidden".into(),"404" => "Not Found".into(),"405" => "Method Not Allowed".into(),"406" => "Not Acceptable".into(),"407" => "Proxy Authentication Required".into(),"408" => "Request Timeout".into(),"409" => "Conflict".into(),"410" => "Gone".into(),"411" => "Length Required".into(),"412" => "Precondition Failed".into(),"413" => "Request Entity Too Large".into(),"414" => "Request-URI Too Long".into(),"415" => "Unsupported Media Type".into(),"416" => "Requested Range Not Satisfiable".into(),"417" => "Expectation Failed".into(),"421" => "Misdirected Request".into(),"422" => "Unprocessable Entity".into(),"423" => "Locked".into(),"424" => "Failed Dependency".into(),"425" => "Too Early".into(),"426" => "Upgrade Required".into(),"449" => "Retry With".into(),"451" => "Unavailable For Legal Reasons".into(),// 服務器錯誤"500" => "Internal Server Error".into(),"501" => "Not Implemented".into(),"502" => "Bad Gateway".into(),"503" => "Service Unavailable".into(),"504" => "Gateway Timeout".into(),"505" => "HTTP Version Not Supported".into(),"506" => "Variant Also Negotiates".into(),"507" => "Insufficient Storage".into(),"509" => "Bandwidth Limit Exceeded".into(),"510" => "Not Extended".into(),"600" => "Unparseable Response Headers".into(),_ => "Not Found".into(),};response.headers = match &headers {Some(_h) => headers,None => {let mut header = HashMap::new();header.insert("Content-Type", "text/html");Some(header)}};response.body = body;response}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 = self.headers.clone().unwrap();let mut headers_string = "".into();for (key, value) in map.iter() {headers_string = format!("{headers_string}{}:{}\r\n", key, value);}headers_string}fn body(&self) -> &str {match &self.body {Some(b) => b.as_str(),None => "",}}pub fn send_response(&self, write_stream: &mut impl Write) -> Result<()> {let response = self.clone();let response_string: String = String::from(response);let _ = write!(write_stream, "{}", response_string);Ok(())}
}#[cfg(test)]
mod test {use super::*;#[test]fn test_response_struct_creation_200() {let response_actual = HttpResponse::new("200",None,Some("xxxx".into()),);let response_excepted = 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_excepted);}#[test]fn test_response_struct_creation_404() {let response_actual = HttpResponse::new("404",None,Some("xxxx".into()),);let response_excepted = 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_excepted);}#[test]fn test_http_response_creation() {let response_excepted = 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_excepted.into();let actual_string = "HTTP/1.1 404 Not Found\r\nContent-Type:text/html\r\nContent-Length: 4\r\n\r\nxxxx";assert_eq!(http_string, actual_string);}
}

運行命令 cargo test -p http,測試 http 成員庫。

現在一共有 6 個測試,都通過了:

在這里插入圖片描述

lib

打開 http 成員庫中的 lib.rs,編寫代碼:

pub mod httprequest;
pub mod httpresponse;

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/912949.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/912949.shtml
英文地址,請注明出處:http://en.pswp.cn/news/912949.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

小架構step系列01:小架構初衷

1 概述 小公司做業務服務&#xff0c;需要聚焦到實際的業務上&#xff0c;盡快通過業務服務客戶&#xff0c;給客戶創建價值&#xff0c;公司才能生存下去。在技術上采用的Web應用架構具備以下特點&#xff1a; 主要由開源組件組裝而成。這樣既可以節省成本&#xff0c;也可以把…

蘋果AR/VR頭顯路線圖曝光,微美全息推進AI/AR智能眼鏡新品開啟視覺體驗篇章

日前&#xff0c;郭明錤發表了一篇關于蘋果&#xff08;AAPL.US&#xff09;2025-2028頭戴式產品路線圖的文章&#xff0c;里面提到蘋果正在開發涵蓋MR頭顯、AI眼鏡、AR眼鏡、Birdbath眼鏡等共計7款設備。 蘋果的頭顯設備中&#xff0c;大量出貨的產品是類似于Ray-Ban Meta的智…

python pyecharts 數據分析及可視化(2)

一、任務要求 任務二&#xff1a;感冒高發期分析 【任務說明】 感冒是一種常見的急性上呼吸道病毒性感染性疾病&#xff0c;多由鼻病 毒、副流感病毒、呼吸道合胞病毒、埃可病毒、柯薩奇病毒、冠狀病 毒、腺病毒等引起。臨床表現為鼻塞、噴嚏、流涕、發熱、咳嗽、頭 痛等&#…

React自學 基礎一

React基礎 React 是一個由 Facebook&#xff08;現 Meta&#xff09;開發并維護的、開源的 JavaScript 庫&#xff0c;主要用于 構建用戶界面&#xff08;UI&#xff09;&#xff0c;尤其是單頁面應用程序中的動態、交互式界面。 簡單示例&#xff1a; import React, { useSt…

PHP語法基礎篇(八):超全局變量

超全局變量是在 PHP 4.1.0 中引入的&#xff0c;并且是內置變量&#xff0c;可以在所有作用域中始終可用。 PHP 中的許多預定義變量都是"超全局的"&#xff0c;這意味著它們在一個腳本的全部作用域中都可用。在函數或方法中無需執行 global $variable; 就可以訪問它們…

NumPy-核心函數concatenate()深度解析

NumPy-核心函數concatenate深度解析 一、concatenate()基礎語法與核心參數函數簽名與核心作用基礎特性&#xff1a;形狀匹配規則 二、多維數組拼接實戰示例1. 一維數組&#xff1a;最簡單的序列拼接2. 二維數組&#xff1a;按行與按列拼接對比按行拼接&#xff08;垂直方向&…

aws(學習筆記第四十八課) appsync-graphql-dynamodb

aws(學習筆記第四十八課) appsync-graphql-dynamodb 使用graphql來方便操作dynamodb 理解graphql中的graphql api&#xff0c;schema&#xff0c;resolver 學習內容&#xff1a; graphqlgraphql apischemaresolver 1. 代碼連接和修改 1.1 代碼鏈接 代碼鏈接&#xff08;app…

關于微前端框架micro,子應用設置--el-primary-color失效的問題

設置了manualChunks導致失效,去掉即可,比較小眾的問題 下面是deepseek的分析 關于 manualChunks 導致 Element Plus 主題變量失效的問題 你找到的確實是問題的關鍵所在。這個 manualChunks 配置影響了 Element Plus 樣式和變量的加載順序&#xff0c;從而導致主題變量失效。…

MySQL 學習 之 你還在用 TIMESTAMP 嗎?

目錄 1. 弊端1.1. 取值范圍1.2. 時區依賴1.3. 隱式轉換 2. 區別3. 解決 1. 弊端 1.1. 取值范圍 TIMESTAMP 的取值范圍為 1970-01-01 00:00:01 UTC 到 2038-01-19 03:14:07 UTC&#xff0c;超出范圍的數據會被強制歸零或觸發異常?。 具體表現為在基金債券等業務中&#xff0…

java中字節和字符有何區別,為什么有字節流和字符流?

在Java中&#xff0c;字節&#xff08;byte&#xff09;和字符&#xff08;char&#xff09;是兩種不同的數據類型&#xff0c;它們的主要區別在于所表示的數據單位、用途以及編碼方式,字節流和字符流的區分就是為了解決編碼問題。 字節&#xff08;byte&#xff09;&#xff…

伴隨矩陣 線性代數

伴隨矩陣的定義 伴隨矩陣的作用是什么&#xff1f;我們可以看到其伴隨矩陣乘上自己等于一個數&#xff08;自身的行列式&#xff09;乘以E&#xff0c;所以對于一個方陣來說&#xff0c;其逆矩陣就是自己的伴隨矩陣的倍數。 所以說伴隨矩陣的作用就是用來更好的求解逆矩陣的。…

百勝軟件獲邀走進華為,AI實踐經驗分享精彩綻放

在數字化浪潮席卷全球的當下&#xff0c;零售行業正經歷著深刻變革&#xff0c;人工智能技術成為重塑行業格局的關鍵力量。6月26日&#xff0c;“走進華為——智領零售&#xff0c;AI賦能新未來”活動在華為練秋湖研發中心成功舉辦。百勝軟件作為數字零售深耕者&#xff0c;攜“…

六種扎根理論的編碼方法

一、實境編碼 1.概念&#xff1a;實境編碼是一種基于參與者原生語言的質性編碼方法&#xff0c;其核心在于直接采用研究對象在訪談、觀察或文本中使用的原始詞匯、短語或獨特表達作為分析代碼。該方法通過保留數據的"原生態"語言形式&#xff08;如方言、隱喻、習慣用…

【Spring篇09】:制作自己的spring-boot-starter依賴1

文章目錄 1. Spring Boot Starter 的本質2. Starter 的模塊結構&#xff08;推薦&#xff09;3. 制作 xxx-spring-boot-autoconfigure 模塊3.1 添加必要的依賴3.2 編寫具體功能的配置類3.3 編寫自動化配置類 (AutoConfiguration)3.4 注冊自動化配置類 (.imports 或 spring.fact…

Qt6之qml自定義控件開發流程指南

Qt6之qml自定義控件開發流程指南 &#x1f6e0;? 一、基礎控件創建 定義 QML 文件 在工程中新建 QML 文件&#xff08;如 CustomButton.qml&#xff09;&#xff0c;文件名首字母大寫。 使用基礎組件&#xff08;如 Rectangle、Text&#xff09;構建控件邏輯&#xff0c;通過…

Vue簡介,什么是Vue(Vue3)?

什么是Vue&#xff1f; Vue是一款用于構建用戶界面的JavaScript框架。 它基于標準HTML、CSS和JavaScript構建&#xff0c;并提供了一套聲明式的、組件化的編程模型&#xff0c;幫助你高效地開發用戶界面。無論是簡單的還是復雜地界面&#xff0c;Vue都可以勝任。 聲明式渲染…

從零開始構建Airbyte數據管道:PostgreSQL到BigQuery實戰指南

作為數據工程師&#xff0c;ETL&#xff08;Extract, Transform, Load&#xff09;流程是日常工作的核心。然而&#xff0c;構建和維護數據管道往往耗時且復雜。幸運的是&#xff0c;開源工具Airbyte提供了一種更便捷的解決方案——它支持350預構建連接器&#xff0c;允許通過無…

JavaScript的初步學習

目錄 JavaScript簡介 主要特點 主要用途 JavaScript的基本特性 JavaScript的引入方式 1. 內聯方式 (Inline JavaScript) 2. 內部方式 (Internal JavaScript / Embedded JavaScript) 3. 外部方式 (External JavaScript) JavaScript的語法介紹 1.書寫語法 2.輸出語句 3.…

洛谷P1379 八數碼難題【A-star】

P1379 八數碼難題 八數碼難題首先要進行有解性判定&#xff0c;避免無解情況下盲目搜索浪費時間。 有解性判定 P10454 奇數碼問題 題意簡述 在一個 n n n \times n nn 的網格中進行&#xff0c;其中 n n n 為奇數&#xff0c; 1 1 1 個空格和 [ 1 , n 2 ? 1 ] [1,n^2…

MySQL Buffer Pool 深度解析:從架構設計到性能優化(附詳細結構圖解)

在 MySQL 數據庫的世界里&#xff0c;有一個決定性能上限的"神秘倉庫"——Buffer Pool。它就像超市的貨架&#xff0c;把最常用的商品&#xff08;數據&#xff09;放在最方便拿取的地方&#xff0c;避免每次都要去倉庫&#xff08;磁盤&#xff09;取貨。今天我們就…