webserver中主要涉及两个连接。传输控制协议(Transmission Control Protocol,TCP)超文本传输协议(Hypertext Transfer Protocol,HTTP)。 这两者都是 请求-响应(request-response)协议,也就是说,有 客户端(client)来初始化请求,并有 服务端(server)监听请求并向客户端提供响应。请求与响应的内容由协议本身定义。
tcp是一个底层协议,主要描述信息从一个server到另一个的细节,但是不指定具体信息是什么。而http在tcp之上,定义了请求跟响应的内容。大多数情况http通过tcp传输,我们首先要做的就是处理tcp跟http的请求与响应的原始字节数据。
所以第一步
监听tcp连接
使用标准库std::net 首先创建一个叫hello的项目
$ cargo new hello
// --进入hello dir--
$ cd hello
// --vscode打开--
$ code .
/src/main.rs
use std::net::TcpListener;
fn main() {
let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
for stream in listener.incoming() {
let stream = stream.unwrap();
println!("Connection established!");
}
}
上面代码通过tcplistener连接(bind类似于new)7878端口,并用unwrap做了简单的错误处理。
incoming返回一个iterator它提供了一系列的流(更准确的说是 TcpStream 类型的流)
incoming:
Returns an iterator over the connections being received on this listener.
读取请求
使用一个新的函数处理连接,在这个新的 handle_connection 函数中,我们从 TCP 流中读取数据并打印出来以便观察浏览器发送过来的数据。
use std::net::TcpListener;
use std::net::TcpStream;
fn main() {
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 mut buffer = [0; 1024];
stream.read(&mut buffer).unwrap();
println!("Request: {}", String::from_utf8_lossy(&buffer[..]));
}
上面代码需要注意的是在 handle_connection 中,stream 参数是可变的。这是因为 TcpStream 实例在内部记录了所返回的数据。它可能读取了多于我们请求的数据并保存它们以备下一次请求数据。因此它需要是 mut 的因为其内部状态可能会改变。
接下来就是实现读取流。我们在栈上声明了一个buffer,并且创建了一个1024的缓冲区域。
接下来就是将buffer传给stream.read,他会从TcpStream中读取字节并放入缓冲区。
当然也unwrap,错误处理。
使用String::from_utf8_lossy 函数获取一个 &[u8] 并产生一个 String。将字节转化为String。
接下来就可以cargo run了
$ cargo run
Compiling hello v0.1.0 (file:///projects/hello)
Finished dev [unoptimized + debuginfo] target(s) in 0.42s
Running `target/debug/hello`
Request: GET / HTTP/1.1
Host: 127.0.0.1:7878
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101
Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
������������������������������������
http的东西就不说了。
编写响应
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;
fn main() {
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 mut buffer = [0; 1024];
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();
}
let response = "HTTP/1.1 200 OK\r\n\r\n";
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
定义了变量 response 来存放将要返回的成功响应的数据。接着,在 response 上调用 as_bytes,因为 stream 的 write 方法获取一个 &[u8] 并直接将这些字节发送给连接。
完成一个html
我需要返回一个html,所以需要一个hello.html
然后使用fs模块下的read_to_string读取html。
use std::fs;
// --snip--忽略的代码
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;
fn main() {
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 mut buffer = [0; 1024];
stream.read(&mut buffer).unwrap();
// 这里的将html存入变量contents
let contents = fs::read_to_string("hello.html").unwrap();
//使用format!格式化要发送的数据
let response = format!(
"HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}",
contents.len(),
contents
);
//再转化为byte
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
使用 cargo run 运行程序,在浏览器加载 127.0.0.1:7878,你应该会看到渲染出来的 HTML 文件
完成到这里就是第一个成功了。可以回头再看看完整的代码~
验证
现在无论请求什么都返回hello.html,这不是想要的。我们想要请求什么就返回什么页面,错误请求响应404.html。所以先写一个404.html。
fn handle_connection(mut stream: TcpStream) {
let mut buffer = [0; 1024];
stream.read(&mut buffer).unwrap();
// 使用b将字符串转化字节,才能存到缓冲区
let get = b"GET / HTTP/1.1\r\n";
// 使用解构加判断并通过starts_with确定数据
let (status_line, filename) = if buffer.starts_with(get) {
("HTTP/1.1 200 OK", "hello.html")
} else {
("HTTP/1.1 404 NOT FOUND", "404.html")
};
// 使用判断的结果值决定要发的html文件
let contents = fs::read_to_string(filename).unwrap();
// 格式化
let response = format!(
"{}\r\nContent-Length: {}\r\n\r\n{}",
status_line,
contents.len(),
contents
);
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
现在,我们有了一个又小又简单的单线程server,它对一个请求返回页面内容而对所有其他请求返回 404 响应,它一次只能处理一个请求。