亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

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

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

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

監(jiān)聽(tīng) TCP 連接

標(biāo)準(zhǔn)庫(kù)提供了一個(gè) std::net 模塊,可以讓我們監(jiān)聽(tīng) TCP 連接。

下面這段代碼將在本地地址 127.0.0.1:7878 上監(jiān)聽(tīng)傳入的 TCP 流。當(dāng)它收到一個(gè)傳入流時(shí),它將打印 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)聽(tīng)地址為 127.0.0.1:7878 的 TCP 連接。在地址中,冒號(hào)之前的部分是代表本地地址,7878 是端口。

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

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

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

在終端中調(diào)用 cargo run,然后在瀏覽器中加載 127.0.0.1:7878。瀏覽器應(yīng)該顯示一個(gè)錯(cuò)誤消息,因?yàn)榉?wù)器當(dāng)前沒(méi)有發(fā)回任何數(shù)據(jù)。

在這里插入圖片描述

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

在這里插入圖片描述

閱讀請(qǐng)求

實(shí)現(xiàn)一個(gè) handle_connection 函數(shù),從 TCP 流中讀取數(shù)據(jù)并打印出來(lái),這樣我們就可以看到從瀏覽器發(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)建了一個(gè)新的 BufReader 實(shí)例,該實(shí)例包裝了對(duì)流的引用。BufReader 通過(guò)為我們管理對(duì) std::io::Read trait 方法的調(diào)用來(lái)增加緩沖。

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

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

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

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

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",
]

讓我們分解這個(gè)請(qǐng)求數(shù)據(jù)來(lái)理解瀏覽器對(duì)程序的要求。

仔細(xì)看看 HTTP 請(qǐng)求

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

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

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

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

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

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

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

在請(qǐng)求行之后,從 Host: 開(kāi)始的其余行是請(qǐng)求頭。GET 請(qǐng)求沒(méi)有請(qǐng)求體。

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

編寫響應(yīng)

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

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

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

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

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

狀態(tài)碼 200 是標(biāo)準(zhǔn)的成功響應(yīng)。讓我們將其寫入流,作為對(duì)成功請(qǐ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 方法將字符串?dāng)?shù)據(jù)轉(zhuǎn)換為字節(jié)。流上的 write_all 方法接受 &[u8],并將這些字節(jié)直接發(fā)送到連接。因?yàn)?write_all 操作可能失敗,所以我們像以前一樣對(duì)任何錯(cuò)誤結(jié)果使用 unwrap。

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

在這里插入圖片描述

返回真正的 HTML

讓我們實(shí)現(xiàn)不止返回一個(gè)空白頁(yè)的功能。在項(xiàng)目目錄的根目錄中創(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,該報(bào)頭設(shè)置為響應(yīng)體的大小,在本例中為 hello.html 的大小。

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

在這里插入圖片描述

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

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

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

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

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

// --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 請(qǐng)求的第一行,因此我們將調(diào)用 next 來(lái)從迭代器中獲取第一項(xiàng),而不是將整個(gè)請(qǐng)求讀入 vector。第一次 unwrap 處理 Option,如果迭代器沒(méi)有項(xiàng),則停止程序。第二個(gè) unwrap 處理 Result,取出請(qǐng)求內(nèi)容。

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

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

在這里插入圖片描述

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

在這里插入圖片描述

現(xiàn)在,讓我們完善 else 塊中的代碼,返回一個(gè)狀態(tài)碼為 404、原因短語(yǔ)為 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>

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

在這里插入圖片描述

代碼重構(gòu)

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

讓我們將這些差異提取到單獨(dú)的 if 和 else 行中,將狀態(tài)行和文件名的值分配給變量,然后使用這些變量來(lá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 代碼就實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的單線程 Web 服務(wù)器,用hello.html 響應(yīng)一個(gè)請(qǐng)求,用 404.html 響應(yīng)所有其他請(qǐng)求。

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

相關(guān)文章

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

    如何使用Rust直接編譯單個(gè)的Solidity合約

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

    rust中trait的使用方法詳解

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

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

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

    Rust anyhow 簡(jiǎn)明示例教程

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

    Rust中實(shí)例化動(dòng)態(tài)對(duì)象的示例詳解

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

    Rust文本處理快速入門

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

    rust交叉編譯問(wèn)題及報(bào)錯(cuò)解析

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

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

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

    Rust中的Drop特性之解讀自動(dòng)化資源清理的魔法

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

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

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

最新評(píng)論