打赏

[易学易懂系列|rustlang语言|零基础|快速入门|(26)|实战3:Http服务器]

[易学易懂系列|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

posted @ 2019-12-20 17:40  gyc567  阅读(468)  评论(0编辑  收藏  举报