欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Rust 中單線程 Web 服務(wù)器的實現(xiàn)

 更新時間:2025年06月26日 09:46:27   作者:UestcXiye  
本文用Rust構(gòu)建單線程Web服務(wù)器,通過HTTP/TCP處理請求,返回hello.html或404.html,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

Web 服務(wù)器中涉及的兩個主要協(xié)議是超文本傳輸協(xié)議(HTTP)和傳輸控制協(xié)議(TCP)。這兩種協(xié)議都是請求-響應(yīng)協(xié)議,這意味著客戶端發(fā)起請求,服務(wù)器偵聽請求并向客戶端提供響應(yīng)。這些請求和響應(yīng)的內(nèi)容由協(xié)議定義。

TCP 是較低級別的協(xié)議,它描述了信息如何從一臺服務(wù)器傳遞到另一臺服務(wù)器的細節(jié),但沒有指定該信息是什么。HTTP 通過定義請求和響應(yīng)的內(nèi)容建立在 TCP 之上。在技術(shù)上可以將 HTTP 與其他協(xié)議一起使用,但在絕大多數(shù)情況下,HTTP 通過 TCP 發(fā)送數(shù)據(jù)。

我們將處理 TCP 和 HTTP 請求和響應(yīng)的原始字節(jié)。

監(jiān)聽 TCP 連接

標準庫提供了一個 std::net 模塊,可以讓我們監(jiān)聽 TCP 連接。

下面這段代碼將在本地地址 127.0.0.1:7878 上監(jiān)聽傳入的 TCP 流。當它收到一個傳入流時,它將打印 Connection established!

use std::net::TcpListener;

fn main() {
    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();

    for stream in listener.incoming() {
        let stream = stream.unwrap();

        println!("Connection established!");
    }
}

使用 TcpListener,我們可以監(jiān)聽地址為 127.0.0.1:7878 的 TCP 連接。在地址中,冒號之前的部分是代表本地地址,7878 是端口。

bind 函數(shù)類似于 new 函數(shù),作用是監(jiān)聽一個端口,它返回 Result<T, E>,這表明綁定有可能失敗。若成功,則得到一個新的 TcpListener 實例;若失敗,我們使用 unwrap 來停止程序。

TcpListener 上的 incoming 方法返回一個迭代器,該迭代器為我們提供一個 TcpStream 類型的流。單個流表示客戶端和服務(wù)器之間的連接,在該過程中,客戶機連接到服務(wù)器,服務(wù)器生成響應(yīng),服務(wù)器關(guān)閉連接。因此,我們將從 TcpStream 中讀取以查看客戶端發(fā)送的內(nèi)容,然后將響應(yīng)寫入流以將數(shù)據(jù)發(fā)送回客戶端??偟膩碚f,這個 for 循環(huán)將依次處理每個連接,并產(chǎn)生一系列流供我們處理。

目前,我們對流的處理包括:如果流有任何錯誤,調(diào)用 unwrap 來終止程序;如果沒有任何錯誤,程序?qū)⒋蛴∫粭l消息。

在終端中調(diào)用 cargo run,然后在瀏覽器中加載 127.0.0.1:7878。瀏覽器應(yīng)該顯示一個錯誤消息,因為服務(wù)器當前沒有發(fā)回任何數(shù)據(jù)。

在這里插入圖片描述

但是終端上有瀏覽器連接到服務(wù)器時打印的幾條消息。

在這里插入圖片描述

閱讀請求

實現(xiàn)一個 handle_connection 函數(shù),從 TCP 流中讀取數(shù)據(jù)并打印出來,這樣我們就可以看到從瀏覽器發(fā)送的數(shù)據(jù)。

use std::net::{TcpListener, TcpStream};
use std::io::{BufReader, prelude::*};

fn handle_connection(mut stream: TcpStream) {
    let buf_reader = BufReader::new(&stream);
    let http_request: Vec<_> = buf_reader
        .lines()
        .map(|result| result.unwrap())
        .take_while(|line| !line.is_empty())
        .collect();

    println!("Request: {http_request:#?}");
}

fn main() {
    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();

    for stream in listener.incoming() {
        let stream = stream.unwrap();

        handle_connection(stream);
    }
}

在 handle_connection 函數(shù)中,我們創(chuàng)建了一個新的 BufReader 實例,該實例包裝了對流的引用。BufReader 通過為我們管理對 std::io::Read trait 方法的調(diào)用來增加緩沖。

我們創(chuàng)建了一個名為 http_request 的變量來收集瀏覽器發(fā)送到服務(wù)器的請求行。我們通過添加 Vec<_> 類型注釋來表示希望將這些行收集到一個向量中。

BufReader 實現(xiàn)了 std::io::BufRead trait,它提供了 lines 方法。lines 方法返回一個 Result<String, std::io::Error> 的迭代器,方法是在看到換行符時拆分數(shù)據(jù)流。為了獲得每個 String,我們使用 map 方法展開每個 Result。

瀏覽器通過在一行中發(fā)送兩個換行符來表示 HTTP 請求的結(jié)束,因此為了從流中獲得一個請求,我們一直讀取行,直到得到空字符串的行。一旦我們將這些行收集到 vector 中,我們將使用 #? 調(diào)試格式將它們打印出來,這樣我們就可以查看 Web 瀏覽器發(fā)送給服務(wù)器的指令。

運行程序并再次在 Web 瀏覽器中發(fā)出請求。我們?nèi)匀粫跒g覽器中得到一個錯誤頁面,但是我們的程序在終端中的輸出現(xiàn)在看起來像這樣:

Request: [
    "GET / HTTP/1.1",
    "Host: 127.0.0.1:7878",
    "Connection: keep-alive",
    "sec-ch-ua: \"Google Chrome\";v=\"137\", \"Chromium\";v=\"137\", \"Not/A)Brand\";v=\"24\"",
    "sec-ch-ua-mobile: ?0",
    "sec-ch-ua-platform: \"Windows\"",
    "Upgrade-Insecure-Requests: 1",
    "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
    "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
    "Sec-Fetch-Site: none",
    "Sec-Fetch-Mode: navigate",
    "Sec-Fetch-User: ?1",
    "Sec-Fetch-Dest: document",
    "Accept-Encoding: gzip, deflate, br, zstd",
    "Accept-Language: zh-CN,zh;q=0.9",
]

讓我們分解這個請求數(shù)據(jù)來理解瀏覽器對程序的要求。

仔細看看 HTTP 請求

HTTP 是一個基于文本的協(xié)議,請求采用以下格式:

Method Request-URI HTTP-Version CRLF
headers CRLF
message-body

第一行是請求行,包含有關(guān)客戶端請求內(nèi)容的信息。

請求行的第一部分表明正在使用的方法,例如 GET 或 POST,它描述了客戶端如何發(fā)出此請求。我們的客戶端使用 GET 請求,這意味著它正在請求信息。

請求行的下一部分是/,它指示客戶機請求的統(tǒng)一資源標識符(URI)。URI 類似于 URL,但是 HTTP 規(guī)范使用術(shù)語 URI。

請求行的最后一部分是客戶端使用的 HTTP 版本,然后請求行以 CRLF 序列 \r\n 結(jié)束,其中 \r 是回車,\n 是換行符。CRLF 序列將請求行與請求數(shù)據(jù)的其余部分分開。

查看我們收到的請求行數(shù)據(jù),可以看到 GET 是方法,/ 是請求 URI, HTTP/1.1 是版本。

在請求行之后,從 Host: 開始的其余行是請求頭。GET 請求沒有請求體。

現(xiàn)在我們知道了瀏覽器在請求什么,讓我們發(fā)回一些數(shù)據(jù)吧!

編寫響應(yīng)

我們將實現(xiàn)發(fā)送數(shù)據(jù)以響應(yīng)客戶機請求。HTTP 響應(yīng)的格式如下:

HTTP-Version Status-Code Reason-Phrase CRLF
headers CRLF
message-body

第一行是狀態(tài)行,其中包含響應(yīng)中使用的 HTTP 版本、總結(jié)請求結(jié)果的數(shù)字狀態(tài)碼,以及提供狀態(tài)碼文本描述的原因短語。在 CRLF 序列之后是任何響應(yīng)頭、另一個 CRLF 序列和響應(yīng)體。

下面是一個使用 HTTP 1.1 版本的響應(yīng)示例,它的狀態(tài)碼是 200,一個 OK 原因短語,沒有響應(yīng)頭、響應(yīng)體:

HTTP/1.1 200 OK\r\n\r\n

狀態(tài)碼 200 是標準的成功響應(yīng)。讓我們將其寫入流,作為對成功請求的響應(yīng)。修改 handle_connection 函數(shù):

fn handle_connection(mut stream: TcpStream) {
    let buf_reader = BufReader::new(&stream);
    let http_request: Vec<_> = buf_reader
        .lines()
        .map(|result| result.unwrap())
        .take_while(|line| !line.is_empty())
        .collect();

    let response = "HTTP/1.1 200 OK\r\n\r\n";

    stream.write_all(response.as_bytes()).unwrap();
}

as_bytes 方法將字符串數(shù)據(jù)轉(zhuǎn)換為字節(jié)。流上的 write_all 方法接受 &[u8],并將這些字節(jié)直接發(fā)送到連接。因為 write_all 操作可能失敗,所以我們像以前一樣對任何錯誤結(jié)果使用 unwrap。

通過這些更改,讓我們運行代碼并在瀏覽器中加載 127.0.0.1:7878。你應(yīng)該得到一個空白頁面,而不是一個錯誤頁面。

在這里插入圖片描述

返回真正的 HTML

讓我們實現(xiàn)不止返回一個空白頁的功能。在項目目錄的根目錄中創(chuàng)建新文件 hello.html。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Hello!</title>
  </head>
  <body>
    <h1>Hello!</h1>
    <p>Hi from Rust</p>
  </body>
</html>

接著修改 handle_connection 函數(shù),讀取 HTML 文件,將其作為正文添加到響應(yīng)中,然后發(fā)送。

fn handle_connection(mut stream: TcpStream) {
    let buf_reader = BufReader::new(&stream);
    let http_request: Vec<_> = buf_reader
        .lines()
        .map(|result| result.unwrap())
        .take_while(|line| !line.is_empty())
        .collect();

    let status_line = "HTTP/1.1 200 OK";
    let contents = fs::read_to_string("hello.html").unwrap();
    let length = contents.len();

    let response =
        format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");

    stream.write_all(response.as_bytes()).unwrap();
}

我們使用 format! 將 hello.html 的內(nèi)容添加到響應(yīng)體中。為了確保有效的 HTTP 響應(yīng),我們添加了 Content-Length,該報頭設(shè)置為響應(yīng)體的大小,在本例中為 hello.html 的大小。

運行這段代碼,并在瀏覽器中加載 127.0.0.1:7878。我們看到瀏覽器接收并渲染了 hello.html。

在這里插入圖片描述

目前,我們忽略了 http_request 中的請求數(shù)據(jù),只是無條件地發(fā)回 HTML 文件的內(nèi)容。

我們希望根據(jù)請求定制響應(yīng),只響應(yīng)格式良好的請求。

驗證請求并選擇性地響應(yīng)

讓我們添加一些功能,在返回 HTML 文件之前檢查瀏覽器是否正在請求 /,如果瀏覽器請求任何其他內(nèi)容,則返回一個錯誤。

我們需要修改 handle_connection 函數(shù),檢查收到的請求的內(nèi)容,并添加 if 和 else 塊以區(qū)別對待請求。

// --snip--

fn handle_connection(mut stream: TcpStream) {
    let buf_reader = BufReader::new(&stream);
    let request_line = buf_reader.lines().next().unwrap().unwrap();

    if request_line == "GET / HTTP/1.1" {
        let status_line = "HTTP/1.1 200 OK";
        let contents = fs::read_to_string("hello.html").unwrap();
        let length = contents.len();

        let response = format!(
            "{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"
        );

        stream.write_all(response.as_bytes()).unwrap();
    } else {
        // some other request
    }
}

我們將只查看 HTTP 請求的第一行,因此我們將調(diào)用 next 來從迭代器中獲取第一項,而不是將整個請求讀入 vector。第一次 unwrap 處理 Option,如果迭代器沒有項,則停止程序。第二個 unwrap 處理 Result,取出請求內(nèi)容。

接下來,我們檢查 request_line,看看它是否等于對 / 路徑的 GET 請求的請求行。如果是,if 塊返回 hello.html 文件的內(nèi)容。

現(xiàn)在運行此代碼并請求 127.0.0.1:7878,還是成功的。

在這里插入圖片描述

如果發(fā)出任何其他請求,例如 127.0.0.1:7878/other,你將得到一個連接錯誤。

在這里插入圖片描述

現(xiàn)在,讓我們完善 else 塊中的代碼,返回一個狀態(tài)碼為 404、原因短語為 NOT FOUND 的響應(yīng),響應(yīng)體是 404.html 文件。

    // --snip--
    } else {
        let status_line = "HTTP/1.1 404 NOT FOUND";
        let contents = fs::read_to_string("404.html").unwrap();
        let length = contents.len();

        let response = format!(
            "{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"
        );

        stream.write_all(response.as_bytes()).unwrap();
    }

404.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Hello!</title>
</head>
<body>
<h1>Oops!</h1>
<p>Sorry, I don't know what you're asking for.</p>
</body>
</html>

通過這些更改,再次運行服務(wù)器。請求 127.0.0.1:7878 應(yīng)該返回 hello.html 的內(nèi)容。而任何其他請求,如 127.0.0.1:7878/other,應(yīng)該返回 404.html 中的錯誤 HTML。

在這里插入圖片描述

代碼重構(gòu)

目前,if 和 els e塊有很多重復(fù):它們都在讀取文件并將文件的內(nèi)容寫入流,唯一的區(qū)別是狀態(tài)行和文件名。

讓我們將這些差異提取到單獨的 if 和 else 行中,將狀態(tài)行和文件名的值分配給變量,然后使用這些變量來讀取文件并寫入響應(yīng)。

fn handle_connection(mut stream: TcpStream) {
    // --snip--

    let (status_line, filename) = if request_line == "GET / HTTP/1.1" {
        ("HTTP/1.1 200 OK", "hello.html")
    } else {
        ("HTTP/1.1 404 NOT FOUND", "404.html")
    };

    let contents = fs::read_to_string(filename).unwrap();
    let length = contents.len();

    let response =
        format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");

    stream.write_all(response.as_bytes()).unwrap();
}

總結(jié)

我們只用 32 行 Rust 代碼就實現(xiàn)了一個簡單的單線程 Web 服務(wù)器,用hello.html 響應(yīng)一個請求,用 404.html 響應(yīng)所有其他請求。

目前,我們的服務(wù)器在單線程中運行,這意味著它一次只能處理一個請求。在下一個項目中,我們先通過模擬一些慢速請求來檢查這是如何造成問題的。然后我們將修復(fù)它,以便我們的服務(wù)器可以同時處理多個請求。

相關(guān)文章

  • 如何使用Rust直接編譯單個的Solidity合約

    如何使用Rust直接編譯單個的Solidity合約

    本文介紹了如何使用Rust語言直接編譯Solidity智能合約,特別適用于沒有外部依賴或flatten后的合約,一般情況下,Solidity開發(fā)者使用Hardhat或Foundry框架,本文給大家介紹如何使用Rust直接編譯單個的Solidity合約,感興趣的朋友一起看看吧
    2024-09-09
  • rust中trait的使用方法詳解

    rust中trait的使用方法詳解

    trait用中文來講就是特征,它就是一個標記,只不過這個標記被用在特定的地方,也就是類型參數(shù)的后面,下面我們就來學(xué)習(xí)一下trait的具體使用方法吧
    2023-12-12
  • Rust常用特型之ToOwned特型示例詳解

    Rust常用特型之ToOwned特型示例詳解

    在Rust中,假定某類型實現(xiàn)了Clone特型,如果給你一個對它引用,那我們得到它指向內(nèi)容的備份的最常見方式是調(diào)用其clone()函數(shù),這篇文章主要介紹了Rust常用特型之ToOwned特型,需要的朋友可以參考下
    2024-04-04
  • Rust anyhow 簡明示例教程

    Rust anyhow 簡明示例教程

    anyhow 是 Rust 中的一個庫,旨在提供靈活的、具體的錯誤處理能力,建立在 std::error::Error 基礎(chǔ)上,主要用于那些需要簡單錯誤處理的應(yīng)用程序和原型開發(fā)中,本文給大家分享Rust anyhow 簡明教程,一起看看吧
    2024-06-06
  • Rust中實例化動態(tài)對象的示例詳解

    Rust中實例化動態(tài)對象的示例詳解

    這篇文章主要為大家詳細介紹了Rust中實例化動態(tài)對象的多種方法,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2025-02-02
  • Rust文本處理快速入門

    Rust文本處理快速入門

    編程過程中有許多類型的數(shù)據(jù)要處理,其中文本處理必不可少,本文主要介紹了Rust文本處理快速入門 ,文中通過示例代碼介紹的非常詳細,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-03-03
  • rust交叉編譯問題及報錯解析

    rust交叉編譯問題及報錯解析

    這篇文章主要為大家介紹了rust交叉編譯問題及報錯解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-07-07
  • rust程序靜態(tài)編譯的兩種方法實例小結(jié)

    rust程序靜態(tài)編譯的兩種方法實例小結(jié)

    這篇文章主要介紹了rust程序靜態(tài)編譯的兩種方法總結(jié),本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2025-05-05
  • Rust中的Drop特性之解讀自動化資源清理的魔法

    Rust中的Drop特性之解讀自動化資源清理的魔法

    Rust通過Drop特性實現(xiàn)了自動清理機制,確保資源在對象超出作用域時自動釋放,避免了手動管理資源時可能出現(xiàn)的內(nèi)存泄漏或雙重釋放問題,智能指針如Box、Rc和RefCell都依賴于Drop來管理資源,提供了靈活且安全的資源管理方案
    2025-02-02
  • rust?zip異步壓縮與解壓的代碼詳解

    rust?zip異步壓縮與解壓的代碼詳解

    在使用actix-web框架的時候,如果使用zip解壓任務(wù)將會占用一個工作線程,因為zip庫是同步阻塞的,想用異步非阻塞需要用另一個庫,下面介紹下rust?zip異步壓縮與解壓的示例,感興趣的朋友一起看看吧
    2024-04-04

最新評論