rust构建多线程websever(1)

158 阅读3分钟

webserver中主要涉及两个连接。传输控制协议Transmission Control ProtocolTCP超文本传输协议Hypertext Transfer ProtocolHTTP)。 这两者都是 请求-响应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 响应,它一次只能处理一个请求。