监听TCP连接
http请求
use std::{io::{prelude::*, BufReader},net::{TcpListener, TcpStream},};fn main() {// 监听地址: 127.0.0.1:7878let 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 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();println!("Request: {:#?}", http_request);}
只有请求,没有响应,页面报错如下:

http请求长啥样
Request: ["GET / HTTP/1.1","Host: 127.0.0.1:7878","Connection: keep-alive","Cache-Control: max-age=0","sec-ch-ua: \"Not A(Brand\";v=\"99\", \"Google Chrome\";v=\"121\", \"Chromium\";v=\"121\"","sec-ch-ua-mobile: ?0","sec-ch-ua-platform: \"macOS\"","Upgrade-Insecure-Requests: 1","User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.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","Accept-Language: zh-CN,zh;q=0.9,en;q=0.8",]
也就是以下格式
Method Request-URI HTTP-Version headers CRLF message-body
- 第一行 Method 是请求的方法,例如 GET、POST 等,Request-URI 是该请求希望访问的目标资源路径,例如 /、/hello/world 等
- 类似 JSON 格式的数据都是 HTTP 请求报头 headers,例如 “Host: 127.0.0.1:7878”
- 至于 message-body 是消息体, 它包含了用户请求携带的具体数据,例如更改用户名的请求,就要提交新的用户名数据,至于刚才的 GET 请求,它是没有 message-body 的
请求应答
放开http请求的如下这两行代码


http响应长啥样
应答的格式与请求相差不大,其中 Status-Code 是最重要的,它用于告诉客户端,当前的请求是否成功,若失败,大概是什么原因,它就是著名的 HTTP 状态码,常用的有 200: 请求成功,404 目标不存在,等等。为了帮助大家更直观的感受下应答格式第一行长什么样,下面给出一个示例:
HTTP-Version Status-Code Reason-Phrase CRLFheaders CRLFmessage-body
HTTP/1.1 200 OK\r\n\r\n
返回html页面
空白页面显然会让人不知所措,那就返回一个简单的 HTML 页面,给用户打给招呼。1.在项目的根目录下创建 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>
2.读取HTML 的内容,并按照 HTTP 格式,将内容传回给客户端。

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}");
重新启动服务器,页面响应如下:

验证请求和选择性应答
针对用户的不同请求给出相应的不同回复,让场景模拟更加真实。以下代码判断了用户是否请求了 / 根路径,如果是,返回之前的 hello.html 页面;如果不是…尚未实现。重新运行服务器,如果你继续访问 127.0.0.1:7878 ,那么看到的依然是 hello.html 页面,因为默认访问根路径,但是一旦换一个路径访问,例如 127.0.0.1:7878/something-else,那你将继续看到之前看过多次的连接错误。
fn handle_connection(mut stream: TcpStream) {let buf_reader = BufReader::new(&mut 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}}

访问根路径之外的页面,展示404 页面
- 在根路径下创建 404.html并填入下面内容:
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><title>404</title></head><body><h1>很抱歉!</h1><p>由于运维删库跑路,我们的数据全部丢失,总监也已经准备跑路,88</p></body></html>
2.完善rust文件
// --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();}
重启服务器,效果如下:

最后,上面的代码其实有很多重复,可以提取出来进行简单重构:

完整rust代码如下:
至此,单线程版本的服务器已经完成,但是存在请求排队的糟糕事实。
use std::{fs,io::{prelude::*, BufReader},net::{TcpListener, TcpStream},};fn main() {// 监听地址: 127.0.0.1:7878let 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 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();}
