使用rust编写一个基于HTTP协议的Web服务器。HTTP是更高层的通信协议,一般来说都基于TCP来构建的,除了HTTP/3,后者是基于UDP构建的协议
仓库地址: 1037827920/web-server:
使用rust编写的简单web服务器 (github.com)
下面分为五个步骤去完成这个单线程Web服务器:
- 监听TCP连接
- 读取HTTP Reqeust
- 返回HTTP Response
- 返回HTML页面
- 验证Request和选择性Response
监听TCP连接
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | use std::net::TcpListener;
 fn main() {
 
 let listener = TcpListener::bind("localhost:8080").unwrap();
 
 
 for stream in listener.incoming() {
 let stream = stream.unwrap();
 
 println!("Connection established!");
 }
 }
 
 | 
运行代码后访问localhost:8080,可以看到如下结果:
| 12
 3
 
 | Connection established!Connection established!
 Connection established!
 
 | 
为啥浏览器访问依次,会在终端打印多次连接建立的信息?
原因在于stream超出作用域时,会触发drop的扫尾工作,其中包含了关闭连接。但是,浏览器可能会存在自动重试的情况,因此还会重新建立连接,最终打印了多次。
注意:
由于listener.incoming()会在当前阻塞式监听,所以main线程会被阻塞。
读取HTTP Reqeust
连接建立后,就可以开始读取客户端传来请求数据,先了解一下HTTP
Reqeust
HTTP Request格式:
| 12
 3
 
 | Method Request-URI HTTP-Versionheaders CRLF
 message-body
 
 | 
- Method是请求的方法,例如GET、POST等,Reqeust-URI是该请求希望访问的目标资源路径,例如/、/sleep
- 类似JSON格式的数据都是HTTP请求报头headers,例如“Host:
localhost:8080”
- message-body是消息体,它包含了用户请求携带的具体数据,例如更改用户名的请求,就要提交新的用户名数据,而GET请求是没有message-body的
代码:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 
 | use std::{
 
 io::{prelude::*, BufReader},
 net::{TcpListener, TcpStream},
 };
 
 
 fn main() {
 let listener = TcpListener::bind("192.168.218.128:8080").unwrap();
 
 for stream in listener.incoming() {
 let stream = stream.unwrap();
 
 handle_connection(stream);
 }
 }
 
 
 
 fn handle_connection(mut stream: TcpStream) {
 let buf_reader = BufReader::new(&mut stream);
 let http_request: Vec<_> = buf_reader
 .lines()
 .map(|result| result.unwrap())
 .take_while(|line| !line.is_empty())
 .collect();
 
 println!("Request: {:#?}", http_request);
 }
 
 | 
运行代码后访问localhost:8080,可以看到如下结果:
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | Request: ["GET / HTTP/1.1",
 "Host: 192.168.218.128:8080",
 "Connection: keep-alive",
 "Cache-Control: max-age=0",
 "Upgrade-Insecure-Requests: 1",
 "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0",
 ...
 ]
 
 | 
如何判断客户端发来的HTTP数据是否读取完成:
客户端会在请求数据的结尾附上两个换行符,放我们检测某一行字符串为空时,就意味着请求数据已经传输完毕了,可以collect了。
返回HTTP Response
客户端请求后,服务端需要给予相应的请求应答
HTTP Response格式:
| 12
 3
 
 | HTTP-Version Status-Code Reason-Phrase CRLFheaders CRLF
 message-body
 
 | 
Status-Code用于告诉客户端,当前的请求是否成功,若失败,大概是什么原因
Response示例:
修改handle_conneciton,将Response发送回客户端:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | 
 fn handle_connection(mut stream: TcpStream) {
 let buf_reader = BufReader::new(&mut 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();
 }
 
 | 
运行代码后访问localhost:8080,浏览器已经不会再报错,已经收到了来自服务器的Response,虽然是空白页面
返回HTML页面
hello.html:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | <!DOCTYPE html><html lang="en">
 <head>
 <meta charset="utf-8">
 <title>This is title</title>
 </head>
 <body>
 <h1>Hello!</h1>
 <p>Hi from Web Server</p>
 </body>
 </html>
 
 | 
添加导包:
修改handle_connection函数:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | 
 fn handle_connection(mut stream: TcpStream) {
 let buf_reader = BufReader::new(&mut 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();
 }
 
 | 
运行代码后访问localhost:8080,浏览器会显示hello.html页面
验证Request和选择性Response
404.html内容:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | <!DOCTYPE html><html lang="en">
 <head>
 <meta charset="utf-8">
 <title>This is 404 Page</title>
 </head>
 <body>
 <h1>Sorry!</h1>
 <p>404</p>
 </body>
 </html>
 
 | 
继续修改handle_connection,针对客户端不同的Request给出相应的Response
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | fn handle_connection(mut stream: TcpStream) {let buf_reader = BufReader::new(&mut stream);
 
 let request_line = buf_reader.lines().next().unwrap().unwrap();
 
 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();
 }
 
 | 
运行代码后访问localhost:8080,浏览器会显示hello.html页面,范围localhost:8080/sleep,会显示404.html页面