[易学易懂系列|rustlang语言|零基础|快速入门|(26)|实战3:Http服务器]
项目实战
实战3:Http服务器
我们今天来开发我们的Http服务器。
我们先用命令创建一个工程目录:
cargo new h_server
我们现在开始在src/main.rs写如下代码:
use std::net::TcpListener;
fn main() {
//绑定IP和端口
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
println!("server is up!");
//从监听器得到网络IO流
for stream in listener.incoming() {
let stream = stream.unwrap();
println!("Connection established!");
}
}
然后我们直接启动我们的服务器,用命令跑一下:
cargo run
控制台打印:
Finished dev [unoptimized + debuginfo] target(s) in 0.87s
Running `target\\debug\\h_server.exe`
server is up!
说明服务器已经启动。
我们现在直接用浏览器访问:http://127.0.0.1:7878/
然后,我们回到vscode的控制台,会打印如下结果:
......
Connection established!
Connection established!
Connection established!
好的,我们简单的服务器开发好了!哈哈!
这里为什么会打印三次呢?
因为浏览器跟我们的服务器通讯的时候,可能各种原因,会请求重试几次。
所以,服务器有可能会收到多次的请求。
先不管这些细节,总之我们已经成功地连接上我们的服务器。
这时,我们直接用:CTRL+c,直接退出服务器。
那浏览器发过来的请求信息,究竟是什么样的呢?
我们来加一些代码,如下:
use std::io::{Read, Write};
use std::net::{Shutdown, TcpListener, TcpStream};
use std::thread;
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap(); //绑定监听端口
println!("server is up!");
for stream in listener.incoming() {
let stream = stream.unwrap();
println!("Connection established!");
handle_connection(stream);
}
}
//处理连接业务逻辑
fn handle_connection(mut stream: TcpStream) {
let mut buffer = [0; 512]; //用一个长度为512的buffer
stream.read(&mut buffer).unwrap(); //从Stream流中读取数据
println!("Request: {}", String::from_utf8_lossy(&buffer[..])); //把buffer中的流数据转化成字符串
}
同样运行命令:
cargo run
浏览器访问:
服务器打印结果为:
Connection established!
Request: GET / HTTP/1.1
Host: 127.0.0.1:7878
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/79.0.3945.88 Safari/537.36
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0
Connection established!
Request: GET / HTTP/1.1
Host: 127.0.0.1:7878
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0
Connection established!
Request: GET / HTTP/1.1
Host: 127.0.0.1:7878
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0
我会发现,这里浏览器发过来的信息,是一些Key:Value键值对的信息。
这些信息,其实就代表http请求协议。
简单来说,http协议,就是浏览器与Http服务器对话的通讯标准,就像我们跟中国人用中文交流,跟美国人,用英语交流一样。
不同的协议,就像不同的语言。
关于http协议,请看:关于HTTP协议,一篇就够了
HTTP协议详解(真的很经典)
这里不再说明 。
我们再来增加一些代码,用来响应http的请求。
我们这次直接返回html页面。
我们在工程目录下新建一个html文件: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>
main.rs代码中函数handle_connection,更新为如下代码:
//处理连接业务逻辑
fn handle_connection(mut stream: TcpStream) {
let mut buffer = [0; 512]; //用一个长度为512的buffer
stream.read(&mut buffer).unwrap(); //从Stream流中读取数据
println!("Request: {}", String::from_utf8_lossy(&buffer[..])); //把buffer中的流数据转化成字符串
let contents = fs::read_to_string("hello.html").unwrap();
let response = format!("HTTP/1.1 200 OK\\r\\n\\r\\n{}", contents);
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
我们再次用命令运行:
cargo run
启动服务器。
然后用浏览器访问:http://127.0.0.1:7878/
我们将得到一个页面 ,里面内容显示:
Hello!
Hi from Rust
很好。
我们现在来增加对浏览器请求的检查处理逻辑,handle_connection函数代码更新如下:
fn handle_connection(mut 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("hello.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 {
// some other request
}
}
我们再次用命令运行:
cargo run
启动服务器。
然后用浏览器访问:http://127.0.0.1:7878/
内容跟上次一样,但我们现在用:http://127.0.0.1:7878/a
结果显示为错误。
因为我们没有处理错误情况。
好吧,我们现在来处理一下。
在当前工程目录下,创建一个html文件:404.html,整个目录结构如下:
|-Cargo.toml
|-404.html
|-hello.html
|-src
|- main.rs
|-tests
|- test.rs
src/main.rs的函数handle_connection代码更新如下:
fn handle_connection(mut stream: TcpStream) {
let mut buffer = [0; 512];
stream.read(&mut buffer).unwrap();
let get = b"GET / HTTP/1.1\\r\\n";
// --snip--
let (status_line, filename) = if buffer.starts_with(get) {
("HTTP/1.1 200 OK\\r\\n\\r\\n", "hello.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();
}
我们再次用命令运行:
cargo run
启动服务器。
然后用浏览器访问:http://127.0.0.1:7878/
内容跟上次一样,但我们现在用:http://127.0.0.1:7878/a
结果显示:
Oops!
Sorry, I don\'t know what you\'re asking for.
很好!符合我们的预期。
我们现在已经打造好一个简单版本的http服务器。
以上,希望对你有用。
如果遇到什么问题,欢迎加入:rust新手群,在这里我可以提供一些简单的帮助,加微信:360369487,注明:博客园+rust
参考文章:
https://doc.rust-lang.org/stable/book/ch20-01-single-threaded.html
https://riptutorial.com/rust/example/4404/a-simple-tcp-client-and-server-application--echo