天天看點

Rust程式設計知識拾遺:記憶體布局

目的

本節的例子教大家用Rust語言建立一個簡單的web server程式。

web server 中涉及到的兩個主要協定是 超文本傳輸協定(Hypertext Transfer Protocol,HTTP)和 傳輸控制協定(Transmission Control Protocol,TCP)。這兩者都是 請求-響應(request-response)協定,也就是說,有 用戶端(client)來初始化請求,并有 服務端(server)監聽請求并向用戶端提供響應。請求與響應的内容由協定本身定義。

TCP為底層協定,一般來說,HTTP建構于HTTP之上。本節就是處理 TCP 和 HTTP 請求與響應的原始位元組資料。

參考

在本節的例子中用到一個非常重要的結構,就是TcpListener,其定義如下:

pub struct TcpListener(_); //A TCP socket server, listening for connections.

該結構實作了一些方法,有興趣的可以去查閱rust标準庫的文檔。

重點關注其以下兩個函數:

函數:pub fn bind<A: ToSocketAddrs>(addr: A) -> Result;

功能描述:綁定伊特特定的位址建立一個TcpListener。

函數:pub fn incoming(&self) -> Incoming;

功能描述:傳回連結接收的疊代器。

監聽TCP連結

use std::net::{TcpListener, TcpStream};
 fn handle_client(_stream: TcpStream) {
 println!(“有一個連結”);
 }
 fn main() -> std::io::Result<()> {
 let listener = TcpListener::bind(“127.0.0.1:80”)?;
 for stream in listener.incoming() {
 handle_client(stream?);
 }
 Ok(())
 }      

讀取請求内容

将handle_client函數修改為如下:

fn handle_client(stream: TcpStream) {
 let mut buffer = [0; 512];
 stream.read(&mut buffer).unwrap();
 println!(“Request: {}”, String::from_utf8_lossy(&buffer[…]));
 }      

重新運作,即列印連結請求的内容。

編寫響應

1、HTTP簡單介紹

http請求封包包含三個部分内容 : 請求行 、 請求頭 、請求體

Method Request-URI HTTP-Version CRLF //請求行:請求方式、協定版本等

headers CRLF //請求頭:包含若幹個屬性,格式為“屬性名:屬性值”,服務端據此擷取用戶端的資訊

message-body //請求體:用戶端真正要傳送給服務端的内容

http響應封包也有三部分内容:響應行、響應頭、響應體

HTTP-Version Status-Code Reason-Phrase CRLF //響應行:封包協定及版本,狀态碼及狀态描述;

headers CRLF //響應頭:由多個屬性組成

message-body //響應體:真正響應的内容

2、傳回一個響應行

修改handle_client:

fn handle_client(stream: TcpStream) {

let mut buffer = [0; 512];

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();

}

3、傳回一個真正的網頁

準備html網頁:

//main.html

Hello!

Hello!

This is a response from a Rust server

Rust代碼修改:

use std::fs;
 // --snip–fn handle_client(stream: TcpStream) {
 let mut buffer = [0; 512];
 stream.read(&mut buffer).unwrap();let contents = fs::read_to_string("main.html").unwrap();

let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", contents);

stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();      
let contents = fs::read_to_string("main.html").unwrap();

let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", contents);

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

4、有選擇的響應

準備404.html檔案:

Hello!

Oops!

Sorry, I don't know what you're asking for.

Rust代碼修改:

fn handle_client(stream: TcpStream) {
 let mut buffer = [0; 512];
 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(“main.html”).unwrap();
 let response = format!(“HTTP/1.1 200 OK\r\n\r\n{}”, contents);
 stream.write(response.as_bytes()).unwrap();
 stream.flush().unwrap();
 } else {
 let status_line = “HTTP/1.1 404 NOT FOUND\r\n\r\n”;
 let contents = fs::read_to_string(“404.html”).unwrap();
 let response = format!("{}{}", status_line, contents);
 stream.write(response.as_bytes()).unwrap();
 stream.flush().unwrap();
 }
 }      

5、優化

最後一步,我們可以針對handle_client的代碼進行優化:

let (status_line, filename) = if buffer.starts_with(get) {
    ("HTTP/1.1 200 OK\r\n\r\n", "main.html")
} else {
    ("HTTP/1.1 404 NOT FOUND\r\n\r\n", "404.html")
};

let contents = fs::read_to_string(filename).unwrap();
let response = format!("{}{}", status_line, contents);

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