国产gaysexchina男同gay,japanrcep老熟妇乱子伦视频,吃奶呻吟打开双腿做受动态图,成人色网站,国产av一区二区三区最新精品

Rust 構(gòu)建單線程 web server

2023-03-22 15:16 更新
ch20-01-single-threaded.md
commit 9c0fa2714859738ff73cbbb829592e4c037d7e46

首先讓我們創(chuàng)建一個(gè)可運(yùn)行的單線程 web server,不過(guò)在開(kāi)始之前,我們將快速了解一下構(gòu)建 web server 所涉及到的協(xié)議。這些協(xié)議的細(xì)節(jié)超出了本書(shū)的范疇,不過(guò)一個(gè)簡(jiǎn)單的概括會(huì)提供我們所需的信息。

web server 中涉及到的兩個(gè)主要協(xié)議是 超文本傳輸協(xié)議Hypertext Transfer Protocol,HTTP)和 傳輸控制協(xié)議Transmission Control Protocol,TCP)。這兩者都是 請(qǐng)求-響應(yīng)request-response)協(xié)議,也就是說(shuō),有 客戶端client)來(lái)初始化請(qǐng)求,并有 服務(wù)端server)監(jiān)聽(tīng)請(qǐng)求并向客戶端提供響應(yīng)。請(qǐng)求與響應(yīng)的內(nèi)容由協(xié)議本身定義。

TCP 是一個(gè)底層協(xié)議,它描述了信息如何從一個(gè) server 到另一個(gè)的細(xì)節(jié),不過(guò)其并不指定信息是什么。HTTP 構(gòu)建于 TCP 之上,它定義了請(qǐng)求和響應(yīng)的內(nèi)容。為此,技術(shù)上講可將 HTTP 用于其他協(xié)議之上,不過(guò)對(duì)于絕大部分情況,HTTP 通過(guò) TCP 傳輸。我們將要做的就是處理 TCP 和 HTTP 請(qǐng)求與響應(yīng)的原始字節(jié)數(shù)據(jù)。

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

所以我們的 web server 所需做的第一件事便是能夠監(jiān)聽(tīng) TCP 連接。標(biāo)準(zhǔn)庫(kù)提供了 std::net 模塊處理這些功能。讓我們一如既往新建一個(gè)項(xiàng)目:

$ cargo new hello
     Created binary (application) `hello` project
$ cd hello

并在 src/main.rs 輸入示例 20-1 中的代碼作為開(kāi)始。這段代碼會(huì)在地址 127.0.0.1:7878 上監(jiān)聽(tīng)傳入的 TCP 流。當(dāng)獲取到傳入的流,它會(huì)打印出 Connection established!

文件名: src/main.rs

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!");
    }
}

示例 20-1: 監(jiān)聽(tīng)傳入的流并在接收到流時(shí)打印信息

TcpListener 用于監(jiān)聽(tīng) TCP 連接。我們選擇監(jiān)聽(tīng)地址 127.0.0.1:7878。將這個(gè)地址拆開(kāi),冒號(hào)之前的部分是一個(gè)代表本機(jī)的 IP 地址(這個(gè)地址在每臺(tái)計(jì)算機(jī)上都相同,并不特指作者的計(jì)算機(jī)),而 7878 是端口。選擇這個(gè)端口出于兩個(gè)原因:通常 HTTP 接受這個(gè)端口而且 7878 在電話上打出來(lái)就是 "rust"(譯者注:九宮格鍵盤(pán)上的英文)。

在這個(gè)場(chǎng)景中 bind 函數(shù)類似于 new 函數(shù),在這里它返回一個(gè)新的 TcpListener 實(shí)例。這個(gè)函數(shù)叫做 bind 是因?yàn)?,在網(wǎng)絡(luò)領(lǐng)域,連接到監(jiān)聽(tīng)端口被稱為 “綁定到一個(gè)端口”(“binding to a port”)

bind 函數(shù)返回 Result<T, E>,這表明綁定可能會(huì)失敗,例如,連接 80 端口需要管理員權(quán)限(非管理員用戶只能監(jiān)聽(tīng)大于 1024 的端口),所以如果不是管理員嘗試連接 80 端口,則會(huì)綁定失敗。另一個(gè)例子是如果運(yùn)行兩個(gè)此程序的實(shí)例這樣會(huì)有兩個(gè)程序監(jiān)聽(tīng)相同的端口,綁定會(huì)失敗。因?yàn)槲覀兪浅鲇趯W(xué)習(xí)目的來(lái)編寫(xiě)一個(gè)基礎(chǔ)的 server,將不用關(guān)心處理這類錯(cuò)誤,使用 unwrap 在出現(xiàn)這些情況時(shí)直接停止程序。

TcpListener 的 incoming 方法返回一個(gè)迭代器,它提供了一系列的流(更準(zhǔn)確的說(shuō)是 TcpStream 類型的流)。stream)代表一個(gè)客戶端和服務(wù)端之間打開(kāi)的連接。連接connection)代表客戶端連接服務(wù)端、服務(wù)端生成響應(yīng)以及服務(wù)端關(guān)閉連接的全部請(qǐng)求 / 響應(yīng)過(guò)程。為此,TcpStream 允許我們讀取它來(lái)查看客戶端發(fā)送了什么,并可以編寫(xiě)響應(yīng)??傮w來(lái)說(shuō),這個(gè) for 循環(huán)會(huì)依次處理每個(gè)連接并產(chǎn)生一系列的流供我們處理。

目前為止,處理流的過(guò)程包含 unwrap 調(diào)用,如果出現(xiàn)任何錯(cuò)誤會(huì)終止程序,如果沒(méi)有任何錯(cuò)誤,則打印出信息。下一個(gè)示例我們將為成功的情況增加更多功能。當(dāng)客戶端連接到服務(wù)端時(shí) incoming 方法返回錯(cuò)誤是可能的,因?yàn)槲覀儗?shí)際上沒(méi)有遍歷連接,而是遍歷 連接嘗試connection attempts)。連接可能會(huì)因?yàn)楹芏嘣虿荒艹晒?,大部分是操作系統(tǒng)相關(guān)的。例如,很多系統(tǒng)限制同時(shí)打開(kāi)的連接數(shù);新連接嘗試產(chǎn)生錯(cuò)誤,直到一些打開(kāi)的連接關(guān)閉為止。

讓我們?cè)囋囘@段代碼!首先在終端執(zhí)行 cargo run,接著在瀏覽器中加載 127.0.0.1:7878。瀏覽器會(huì)顯示出看起來(lái)類似于“連接重置”(“Connection reset”)的錯(cuò)誤信息,因?yàn)?server 目前并沒(méi)響應(yīng)任何數(shù)據(jù)。但是如果我們觀察終端,會(huì)發(fā)現(xiàn)當(dāng)瀏覽器連接 server 時(shí)會(huì)打印出一系列的信息!

     Running `target/debug/hello`
Connection established!
Connection established!
Connection established!

有時(shí)會(huì)看到對(duì)于一次瀏覽器請(qǐng)求會(huì)打印出多條信息;這可能是因?yàn)闉g覽器在請(qǐng)求頁(yè)面的同時(shí)還請(qǐng)求了其他資源,比如出現(xiàn)在瀏覽器 tab 標(biāo)簽中的 favicon.ico。

這也可能是因?yàn)闉g覽器嘗試多次連接 server,因?yàn)?server 沒(méi)有響應(yīng)任何數(shù)據(jù)。當(dāng) stream 在循環(huán)的結(jié)尾離開(kāi)作用域并被丟棄,其連接將被關(guān)閉,作為 drop 實(shí)現(xiàn)的一部分。瀏覽器有時(shí)通過(guò)重連來(lái)處理關(guān)閉的連接,因?yàn)檫@些問(wèn)題可能是暫時(shí)的?,F(xiàn)在重要的是我們成功的處理了 TCP 連接!

記得當(dāng)運(yùn)行完特定版本的代碼后使用 ctrl-C 來(lái)停止程序。并在做出最新的代碼修改之后執(zhí)行 cargo run 重啟服務(wù)。

讀取請(qǐng)求

讓我們實(shí)現(xiàn)讀取來(lái)自瀏覽器請(qǐng)求的功能!為了分離獲取連接和接下來(lái)對(duì)連接的操作的相關(guān)內(nèi)容,我們將開(kāi)始一個(gè)新函數(shù)來(lái)處理連接。在這個(gè)新的 handle_connection 函數(shù)中,我們從 TCP 流中讀取數(shù)據(jù)并打印出來(lái)以便觀察瀏覽器發(fā)送過(guò)來(lái)的數(shù)據(jù)。將代碼修改為如示例 20-2 所示:

文件名: src/main.rs

use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;

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

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

        handle_connection(stream);
    }
}

fn handle_connection(mut stream: TcpStream) {
    let mut buffer = [0; 1024];

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

    println!("Request: {}", String::from_utf8_lossy(&buffer[..]));
}

示例 20-2: 讀取 TcpStream 并打印數(shù)據(jù)

這里將 std::io::prelude 引入作用域來(lái)獲取讀寫(xiě)流所需的特定 trait。在 main 函數(shù)的 for 循環(huán)中,相比獲取到連接時(shí)打印信息,現(xiàn)在調(diào)用新的 handle_connection 函數(shù)并向其傳遞 stream。

在 handle_connection 中,stream 參數(shù)是可變的。這是因?yàn)?nbsp;TcpStream 實(shí)例在內(nèi)部記錄了所返回的數(shù)據(jù)。它可能讀取了多于我們請(qǐng)求的數(shù)據(jù)并保存它們以備下一次請(qǐng)求數(shù)據(jù)。因此它需要是 mut 的因?yàn)槠鋬?nèi)部狀態(tài)可能會(huì)改變;通常我們認(rèn)為 “讀取” 不需要可變性,不過(guò)在這個(gè)例子中則需要 mut 關(guān)鍵字。

接下來(lái),需要實(shí)際讀取流。這里分兩步進(jìn)行:首先,在棧上聲明一個(gè) buffer 來(lái)存放讀取到的數(shù)據(jù)。這里創(chuàng)建了一個(gè) 1024 字節(jié)的緩沖區(qū),它足以存放基本請(qǐng)求的數(shù)據(jù)并滿足本章的目的需要。如果希望處理任意大小的請(qǐng)求,緩沖區(qū)管理將更為復(fù)雜,不過(guò)現(xiàn)在一切從簡(jiǎn)。接著將緩沖區(qū)傳遞給 stream.read ,它會(huì)從 TcpStream 中讀取字節(jié)并放入緩沖區(qū)中。

接下來(lái)將緩沖區(qū)中的字節(jié)轉(zhuǎn)換為字符串并打印出來(lái)。String::from_utf8_lossy 函數(shù)獲取一個(gè) &[u8] 并產(chǎn)生一個(gè) String。函數(shù)名的 “l(fā)ossy” 部分來(lái)源于當(dāng)其遇到無(wú)效的 UTF-8 序列時(shí)的行為:它使用 ?,U+FFFD REPLACEMENT CHARACTER,來(lái)代替無(wú)效序列。你可能會(huì)在緩沖區(qū)的剩余部分看到這些替代字符,因?yàn)樗麄儧](méi)有被請(qǐng)求數(shù)據(jù)填滿。

讓我們?cè)囈辉嚕?dòng)程序并再次在瀏覽器中發(fā)起請(qǐng)求。注意瀏覽器中仍然會(huì)出現(xiàn)錯(cuò)誤頁(yè)面,不過(guò)終端中程序的輸出現(xiàn)在看起來(lái)像這樣:

$ cargo run
   Compiling hello v0.1.0 (file:///projects/hello)
    Finished dev [unoptimized + debuginfo] target(s) in 0.42s
     Running `target/debug/hello`
Request: GET / HTTP/1.1
Host: 127.0.0.1:7878
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101
Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
????????????????????????????????????

根據(jù)使用的瀏覽器不同可能會(huì)出現(xiàn)稍微不同的數(shù)據(jù)。現(xiàn)在我們打印出了請(qǐng)求數(shù)據(jù),可以通過(guò)觀察 Request: GET 之后的路徑來(lái)解釋為何會(huì)從瀏覽器得到多個(gè)連接。如果重復(fù)的連接都是請(qǐng)求 /,就知道了瀏覽器嘗試重復(fù)獲取 / 因?yàn)樗鼪](méi)有從程序得到響應(yīng)。

拆開(kāi)請(qǐng)求數(shù)據(jù)來(lái)理解瀏覽器向程序請(qǐng)求了什么。

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

HTTP 是一個(gè)基于文本的協(xié)議,同時(shí)一個(gè)請(qǐng)求有如下格式:

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

第一行叫做 請(qǐng)求行request line),它存放了客戶端請(qǐng)求了什么的信息。請(qǐng)求行的第一部分是所使用的 method,比如 GET 或 POST,這描述了客戶端如何進(jìn)行請(qǐng)求。這里客戶端使用了 GET 請(qǐng)求。

請(qǐng)求行接下來(lái)的部分是 /,它代表客戶端請(qǐng)求的 統(tǒng)一資源標(biāo)識(shí)符Uniform Resource IdentifierURI) —— URI 大體上類似,但也不完全類似于 URL(統(tǒng)一資源定位符,Uniform Resource Locators)。URI 和 URL 之間的區(qū)別對(duì)于本章的目的來(lái)說(shuō)并不重要,不過(guò) HTTP 規(guī)范使用術(shù)語(yǔ) URI,所以這里可以簡(jiǎn)單的將 URL 理解為 URI。

最后一部分是客戶端使用的HTTP版本,然后請(qǐng)求行以 CRLF序列 (CRLF代表回車和換行,carriage return line feed,這是打字機(jī)時(shí)代的術(shù)語(yǔ)?。┙Y(jié)束。CRLF序列也可以寫(xiě)成\r\n,其中\r是回車符,\n是換行符。 CRLF序列將請(qǐng)求行與其余請(qǐng)求數(shù)據(jù)分開(kāi)。 請(qǐng)注意,打印CRLF時(shí),我們會(huì)看到一個(gè)新行,而不是\r\n。

觀察目前運(yùn)行程序所接收到的數(shù)據(jù)的請(qǐng)求行,可以看到 GET 是 method,/ 是請(qǐng)求 URI,而 HTTP/1.1 是版本。

從 Host: 開(kāi)始的其余的行是 headers;GET 請(qǐng)求沒(méi)有 body。

如果你希望的話,嘗試用不同的瀏覽器發(fā)送請(qǐng)求,或請(qǐng)求不同的地址,比如 127.0.0.1:7878/test,來(lái)觀察請(qǐng)求數(shù)據(jù)如何變化。

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

編寫(xiě)響應(yīng)

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

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

第一行叫做 狀態(tài)行status line),它包含響應(yīng)的 HTTP 版本、一個(gè)數(shù)字狀態(tài)碼用以總結(jié)請(qǐng)求的結(jié)果和一個(gè)描述之前狀態(tài)碼的文本原因短語(yǔ)。CRLF 序列之后是任意 header,另一個(gè) CRLF 序列,和響應(yīng)的 body。

這里是一個(gè)使用 HTTP 1.1 版本的響應(yīng)例子,其狀態(tài)碼為 200,原因短語(yǔ)為 OK,沒(méi)有 header,也沒(méi)有 body:

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

狀態(tài)碼 200 是一個(gè)標(biāo)準(zhǔn)的成功響應(yīng)。這些文本是一個(gè)微型的成功 HTTP 響應(yīng)。讓我們將這些文本寫(xiě)入流作為成功請(qǐng)求的響應(yīng)!在 handle_connection 函數(shù)中,我們需要去掉打印請(qǐng)求數(shù)據(jù)的 println!,并替換為示例 20-3 中的代碼:

文件名: src/main.rs

fn handle_connection(mut stream: TcpStream) {
    let mut buffer = [0; 1024];

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

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

    stream.write(response.as_bytes()).unwrap();
    stream.flush().unwrap();
}

示例 20-3: 將一個(gè)微型成功 HTTP 響應(yīng)寫(xiě)入流

新代碼中的第一行定義了變量 response 來(lái)存放將要返回的成功響應(yīng)的數(shù)據(jù)。接著,在 response 上調(diào)用 as_bytes,因?yàn)?nbsp;stream 的 write 方法獲取一個(gè) &[u8] 并直接將這些字節(jié)發(fā)送給連接。

因?yàn)?nbsp;write 操作可能會(huì)失敗,所以像之前那樣對(duì)任何錯(cuò)誤結(jié)果使用 unwrap。同理,在真實(shí)世界的應(yīng)用中這里需要添加錯(cuò)誤處理。最后,flush 會(huì)等待并阻塞程序執(zhí)行直到所有字節(jié)都被寫(xiě)入連接中;TcpStream 包含一個(gè)內(nèi)部緩沖區(qū)來(lái)最小化對(duì)底層操作系統(tǒng)的調(diào)用。

有了這些修改,運(yùn)行我們的代碼并進(jìn)行請(qǐng)求!我們不再向終端打印任何數(shù)據(jù),所以不會(huì)再看到除了 Cargo 以外的任何輸出。不過(guò)當(dāng)在瀏覽器中加載 127.0.0.1:7878 時(shí),會(huì)得到一個(gè)空頁(yè)面而不是錯(cuò)誤。太棒了!我們剛剛手寫(xiě)了一個(gè) HTTP 請(qǐng)求與響應(yīng)。

返回真正的 HTML

讓我們實(shí)現(xiàn)不只是返回空頁(yè)面的功能。在項(xiàng)目根目錄創(chuàng)建一個(gè)新文件,hello.html —— 也就是說(shuō),不是在 src 目錄。在此可以放入任何你期望的 HTML;列表 20-4 展示了一個(gè)可能的文本:

文件名: 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>

示例 20-4: 一個(gè)簡(jiǎn)單的 HTML 文件用來(lái)作為響應(yīng)

這是一個(gè)極小化的 HTML5 文檔,它有一個(gè)標(biāo)題和一小段文本。為了在 server 接受請(qǐng)求時(shí)返回它,需要如示例 20-5 所示修改 handle_connection 來(lái)讀取 HTML 文件,將其加入到響應(yīng)的 body 中,并發(fā)送:

文件名: src/main.rs

use std::fs;
// --snip--

fn handle_connection(mut stream: TcpStream) {
    let mut buffer = [0; 1024];
    stream.read(&mut buffer).unwrap();

    let contents = fs::read_to_string("hello.html").unwrap();

    let response = format!(
        "HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}",
        contents.len(),
        contents
    );

    stream.write(response.as_bytes()).unwrap();
    stream.flush().unwrap();
}

示例 20-5: 將 hello.html 的內(nèi)容作為響應(yīng) body 發(fā)送

在開(kāi)頭增加了一行來(lái)將標(biāo)準(zhǔn)庫(kù)中的 File 引入作用域。打開(kāi)和讀取文件的代碼應(yīng)該看起來(lái)很熟悉,因?yàn)榈谑?I/O 項(xiàng)目的示例 12-4 中讀取文件內(nèi)容時(shí)出現(xiàn)過(guò)類似的代碼。

接下來(lái),使用 format! 將文件內(nèi)容加入到將要寫(xiě)入流的成功響應(yīng)的 body 中。

使用 cargo run 運(yùn)行程序,在瀏覽器加載 127.0.0.1:7878,你應(yīng)該會(huì)看到渲染出來(lái)的 HTML 文件!

目前忽略了 buffer 中的請(qǐng)求數(shù)據(jù)并無(wú)條件的發(fā)送了 HTML 文件的內(nèi)容。這意味著如果嘗試在瀏覽器中請(qǐng)求 127.0.0.1:7878/something-else 也會(huì)得到同樣的 HTML 響應(yīng)。如此其作用是非常有限的,也不是大部分 server 所做的;讓我們檢查請(qǐng)求并只對(duì)格式良好(well-formed)的請(qǐng)求 / 發(fā)送 HTML 文件。

驗(yàn)證請(qǐng)求并有選擇的進(jìn)行響應(yīng)

目前我們的 web server 不管客戶端請(qǐng)求什么都會(huì)返回相同的 HTML 文件。讓我們?cè)黾釉诜祷?HTML 文件前檢查瀏覽器是否請(qǐng)求 /,并在其請(qǐng)求任何其他內(nèi)容時(shí)返回錯(cuò)誤的功能。為此需要如示例 20-6 那樣修改 handle_connection。新代碼接收到的請(qǐng)求的內(nèi)容與已知的 / 請(qǐng)求的一部分做比較,并增加了 if 和 else 塊來(lái)區(qū)別處理請(qǐng)求:

文件名: src/main.rs

// --snip--

fn handle_connection(mut stream: TcpStream) {
    let mut buffer = [0; 1024];
    stream.read(&mut buffer).unwrap();

    let get = b"GET / HTTP/1.1\r\n";

    if buffer.starts_with(get) {
        let contents = fs::read_to_string("hello.html").unwrap();

        let response = format!(
            "HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}",
            contents.len(),
            contents
        );

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

示例 20-6: 匹配請(qǐng)求并區(qū)別處理 / 請(qǐng)求與其他請(qǐng)求

首先,將與 / 請(qǐng)求相關(guān)的數(shù)據(jù)硬編碼進(jìn)變量 get。因?yàn)槲覀儗⒃甲止?jié)讀取進(jìn)了緩沖區(qū),所以在 get 的數(shù)據(jù)開(kāi)頭增加 b"" 字節(jié)字符串語(yǔ)法將其轉(zhuǎn)換為字節(jié)字符串。接著檢查 buffer 是否以 get 中的字節(jié)開(kāi)頭。如果是,這就是一個(gè)格式良好的 / 請(qǐng)求,也就是 if 塊中期望處理的成功情況,并會(huì)返回 HTML 文件內(nèi)容的代碼。

如果 buffer  以 get 中的字節(jié)開(kāi)頭,就說(shuō)明接收的是其他請(qǐng)求。之后會(huì)在 else 塊中增加代碼來(lái)響應(yīng)所有其他請(qǐng)求。

現(xiàn)在如果運(yùn)行代碼并請(qǐng)求 127.0.0.1:7878,就會(huì)得到 hello.html 中的 HTML。如果進(jìn)行任何其他請(qǐng)求,比如 127.0.0.1:7878/something-else,則會(huì)得到像運(yùn)行示例 20-1 和 20-2 中代碼那樣的連接錯(cuò)誤。

現(xiàn)在向示例 20-7 的 else 塊增加代碼來(lái)返回一個(gè)帶有 404 狀態(tài)碼的響應(yīng),這代表了所請(qǐng)求的內(nèi)容沒(méi)有找到。接著也會(huì)返回一個(gè) HTML 向?yàn)g覽器終端用戶表明此意:

文件名: src/main.rs

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

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

        stream.write(response.as_bytes()).unwrap();
        stream.flush().unwrap();
    }

示例 20-7: 對(duì)于任何不是 / 的請(qǐng)求返回 404 狀態(tài)碼的響應(yīng)和錯(cuò)誤頁(yè)面

這里,響應(yīng)的狀態(tài)行有狀態(tài)碼 404 和原因短語(yǔ) NOT FOUND。仍然沒(méi)有返回任何 header,而其 body 將是 404.html 文件中的 HTML。需要在 hello.html 同級(jí)目錄創(chuàng)建 404.html 文件作為錯(cuò)誤頁(yè)面;這一次也可以隨意使用任何 HTML 或使用示例 20-8 中的示例 HTML:

文件名: 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>

示例 20-8: 任何 404 響應(yīng)所返回錯(cuò)誤頁(yè)面內(nèi)容樣例

有了這些修改,再次運(yùn)行 server。請(qǐng)求 127.0.0.1:7878 應(yīng)該會(huì)返回 hello.html 的內(nèi)容,而對(duì)于任何其他請(qǐng)求,比如 127.0.0.1:7878/foo,應(yīng)該會(huì)返回 404.html 中的錯(cuò)誤 HTML!

少量代碼重構(gòu)

目前 if 和 else 塊中的代碼有很多的重復(fù):他們都讀取文件并將其內(nèi)容寫(xiě)入流。唯一的區(qū)別是狀態(tài)行和文件名。為了使代碼更為簡(jiǎn)明,將這些區(qū)別分別提取到一行 if 和 else 中,對(duì)狀態(tài)行和文件名變量賦值;然后在讀取文件和寫(xiě)入響應(yīng)的代碼中無(wú)條件的使用這些變量。重構(gòu)后取代了大段 if 和 else 塊代碼后的結(jié)果如示例 20-9 所示:

文件名: src/main.rs

// --snip--

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

    let (status_line, filename) = if buffer.starts_with(get) {
        ("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 response = format!(
        "{}\r\nContent-Length: {}\r\n\r\n{}",
        status_line,
        contents.len(),
        contents
    );

    stream.write(response.as_bytes()).unwrap();
    stream.flush().unwrap();
}

示例 20-9: 重構(gòu)使得 if 和 else 塊中只包含兩個(gè)情況所不同的代碼

現(xiàn)在 if 和 else 塊所做的唯一的事就是在一個(gè)元組中返回合適的狀態(tài)行和文件名的值;接著使用第十八章講到的使用模式的 let 語(yǔ)句通過(guò)解構(gòu)元組的兩部分為 filename 和 header 賦值。

之前讀取文件和寫(xiě)入響應(yīng)的冗余代碼現(xiàn)在位于 if 和 else 塊之外,并會(huì)使用變量 status_line 和 filename。這樣更易于觀察這兩種情況真正有何不同,還意味著如果需要改變?nèi)绾巫x取文件或?qū)懭腠憫?yīng)時(shí)只需要更新一處的代碼。示例 20-9 中代碼的行為與示例 20-8 完全一樣。

好極了!我們有了一個(gè) 40 行左右 Rust 代碼的小而簡(jiǎn)單的 server,它對(duì)一個(gè)請(qǐng)求返回頁(yè)面內(nèi)容而對(duì)所有其他請(qǐng)求返回 404 響應(yīng)。

目前 server 運(yùn)行于單線程中,它一次只能處理一個(gè)請(qǐng)求。讓我們模擬一些慢請(qǐng)求來(lái)看看這如何會(huì)成為一個(gè)問(wèn)題,并進(jìn)行修復(fù)以便 server 可以一次處理多個(gè)請(qǐng)求。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)