监听TCP连接
http请求
use std::{
io::{prelude::*, BufReader},
net::{TcpListener, TcpStream},
};
fn main() {
// 监听地址: 127.0.0.1:7878
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 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 CRLF
headers CRLF
message-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:7878
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 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();
}