目的
本節的例子教大家用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();