【Rust自学】20.1. 最后的项目:单线程Web服务器

喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
请添加图片描述

20.1.1. 什么是TCP和HTTP

Web 服务器涉及的两个主要协议是超文本传输​​协议(Hypertext Transfer Protocol,简称HTTP)和传输控制协议(Transmission Control Protocol,简称TCP)。这两种协议都是请求-响应协议,即客户端发起请求,然后服务器监听请求并向客户端提供响应。这些请求和响应的内容由协议定义。

TCP是较低级别的协议,它描述信息如何从一台服务器传输到另一台服务器的详细信息,但不指定该信息是什么。HTTP通过定义请求和响应的内容构建在TCP之上。从技术上讲,可以将HTTP与其他协议结合使用,但在绝大多数情况下,HTTP通过TCP发送数据。我们将使用TCP和HTTP请求和响应的原始字节。

20.1.2. 监听TCP

了解了以上信息之后,我们就开始实践吧!首先创建这个项目:

cargo new web_server

打开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!");
    }
}
  • std::net::TcpListener是一个标准库提供的监听TCP的模块

  • TcpListener::bind函数会监听传进去的这个地址,我们这里传进去的是"127.0.0.1:7878",也就是本地的7878接口,它的返回类型是一个Result<T, E>,需要使用unwrap进行错误处理。如果能成功监听,就会返回TcpListener类型赋给变量listener

  • TcpListener类型上有incoming方法,它会返回一个产生流序列的这个迭代器,也就是TcpStream流,而单个流就表示客户端和服务器之间打开了一个连接,而使用for循环就会依次处理每一个连接,生成一系列的流让我们处理。

让我们尝试运行这段代码。在终端调用cargo run然后加载 Web 浏览器中的127.0.0.1:7878。浏览器应该显示一条错误消息,例如“连接重置”,因为服务器当前没有发回任何数据。但是当你查看终端时,你应该会看到浏览器连接到服务器时打印的几条消息。
请添加图片描述

20.1.3. 读取请求

我们已经实现了监听TCP,接下来我们来尝试读取请求。我们直接在上文的代码上修改:

use std::{  
    io::{prelude::*, BufReader},  
    net::{TcpListener, 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 buf_reader = BufReader::new(&stream);  
    let http_request: Vec<_> = buf_reader  
        .lines()  
        .map(|result| result.unwrap())  
        .take_while(|line| !line.is_empty())  
        .collect();  

	println!("Request: {:#?}", http_request);
}
  • 我们定义了一个名为handle_connection的函数,用于处理客户端连接。参数stream是一个可变的TcpStream类型实例,用于与客户端通信。TcpStream的内部状态可能会随着数据读取和写入发生变化,因此需要将其声明为mut

  • 我们通过BufReader包裹了stream,创建了一个缓冲读取器buf_reader

  • 我们使用map(|result| result.unwrap())解包Result,提取其中的字符串。如果读取发生错误,程序会因unwrap调用而恐慌。

  • take_while(|line| !line.is_empty())过滤迭代器中的元素,直到遇到空行为止。HTTP请求以空行("")标志请求头结束,因此我们仅收集非空行。

  • 我们将所有非空行收集到一个向量Vec<_>中,存储为http_request

  • 使用println!打印出来http_request

试一下:
请添加图片描述

终端输出的信息如下:

Request: [
    "GET / HTTP/1.1",
    "Host: 127.0.0.1:7878",
    "Connection: keep-alive",
    "sec-ch-ua: \"Not(A:Brand\";v=\"99\", \"Google Chrome\";v=\"133\", \"Chromium\";v=\"133\"",
    "sec-ch-ua-mobile: ?0",
    "sec-ch-ua-platform: \"macOS\"",
    "Upgrade-Insecure-Requests: 1",
    "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
    "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
    "Sec-Fetch-Site: none",
    "Sec-Fetch-Mode: navigate",
    "Sec-Fetch-User: ?1",
    "Sec-Fetch-Dest: document",
    "Accept-Encoding: gzip, deflate, br, zstd",
    "Accept-Language: zh-US,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6",
]

HTTP是基于文本的协议,它的请求采用以下格式:

Method Request-URI HTTP-Version CRLF
headers CRLF
message-body

第一行是请求行,保存有关客户端请求的信息。请求行的第一部分指示正在使用的方法,例如GETPOST ,它描述了客户端如何发出此请求。我们的客户使用了GET请求,这意味着它正在询问信息。

请求行的下一部分是/,它表示客户端请求的统一资源标识符 (URI):URI几乎与统一资源定位符(URL)相同,但不完全相同。URI和URL之间的区别对于本章的目的并不重要,但HTTP 规范使用了术语URI,因此我们可以在这里用URL代替URI。

最后一部分是客户端使用的 HTTP 版本,然后请求行以CRLF序列结束。(CRLF代表回车换行) CRLF序列也可以写成 \r\n,其中 \r 是回车,\n 是换行。CRLF 序列将请求行与其余请求数据分开。请注意,当打印CRLF时,我们会看到一个新的行而不是 \r\n

20.1.4. 编写响应

实现了读取请求,接下来我们来写响应。响应和请求是差不多的格式:

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

第一行是一个状态行,其中包含响应中使用的HTTP版本、一个数字状态代码,以及状态代码对应的文本描述,最后是一个CRLF序列。

有了格式就好写代码:

let response = "HTTP/1.1 200 OK\r\n\r\n";  

stream.write_all(response.as_bytes()).unwrap();
  • HTTP/1.1是HTTP版本,200是数字状态码,OK是文本描述,\r\n\r\n是CRLF序列。

  • 我们在response中调用as_bytes将字符串数据转换为字节。 stream上的write_all方法采用&[u8]并直接通过连接发送这些字节。由于write_all操作可能会失败,因此我们使用unwrap 。在实际应用程序中,也可以添加其它错误处理方法。

接下来我们来返回一个真正的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>

这是一个最小的HTML5文档,带有标题和一些文本。

为了返回HTML,我们需要修改main.rs。首先引入std::fs:

use std::fs;

fs是文件系统包。

然后在handle_connection函数里稍微修改一下response变量:

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}");  
  
stream.write_all(response.as_bytes()).unwrap();
  • 通过fs下的read_to_string方法把文件的内容转化为字符串
  • 然后通过format!函数把字符串按照刚才写的响应格式放在消息里

完整代码:

use std::{  
    io::{prelude::*, BufReader},  
    net::{TcpListener, TcpStream},
    fs,  
};  
  
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 buf_reader = BufReader::new(&stream);
    let http_request: Vec<_> = buf_reader
        .lines()
        .map(|result| result.unwrap())
        .take_while(|line| !line.is_empty())
        .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}");

    stream.write_all(response.as_bytes()).unwrap();
}

试一下:
请添加图片描述

20.1.5. 有选择地响应

现在,无论客户端请求什么,我们的Web服务器都会返回文件中的HTML。让我们添加功能来检查浏览器是否在正常访问。正常访问就是访问127.0.0.1:7878/127.0.0.1:7878。在返回HTML文件之前,如果浏览器请求其他任何内容,则返回错误。

在项目根目录下创建一个404.html,里面的内容如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Hello!</title>
  </head>
  <body>
    <h1>Oops!</h1>
    <p>Sorry, I don't know what you're asking for.</p>
  </body>
</html>

我们把之前返回HTML的代码部分放在if判断里,如果是正常的访问就返回正常的内容,否则就返回404.html

fn handle_connection(mut stream: TcpStream) {  
    let buf_reader = BufReader::new(&stream);  
    let request_line = buf_reader.lines().next().unwrap().unwrap();  
  
    if request_line == "Get / HTTP/1.1\r\n" {  
        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}"
            );  
  
        stream.write_all(response.as_bytes()).unwrap();  
    } else {  
        let status_line = "HTTP/1.1 404 NOT FOUND";  
        let contents = fs::read_to_string("404.html").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();  
    }  
}
  • 我们删去了打印出请求的部分,反正用不到。
  • 通过请求行来判断用户是否是正常访问。正常访问的流程不变,非正常访问就返回404.html的内容。

目前的代码有很多重复的地方,我们来重构一下:

fn handle_connection(mut stream: TcpStream) {  
    let buf_reader = BufReader::new(&stream);  
    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();  
}

通过元组的模式匹配和if语句来确定status_linefilename的值。

试一下:

正常访问:
请添加图片描述

非正常访问:
请添加图片描述

20.1.5. 总结

以下是源代码:
main.rs:

use std::{  
    io::{prelude::*, BufReader},  
    net::{TcpListener, TcpStream},  
    fs,  
};  
  
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 buf_reader = BufReader::new(&stream);  
    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();  
}

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>

404.html:

<!DOCTYPE html>  
<html lang="en">  
<head>  
  <meta charset="utf-8">  
  <title>Hello!</title>  
</head>  
<body>  
<h1>Oops!</h1>  
<p>Sorry, I don't know what you're asking for.</p>  
</body>  
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/964863.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

19.[前端开发]Day19-王者荣项目耀实战(二)

01_(掌握)王者荣耀-main-banner展示实现 完整代码 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewpor…

Java 基于微信小程序的高校失物招领平台小程序(附源码,文档)

博主介绍&#xff1a;✌程序员徐师兄、8年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战*✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447…

题解:洛谷 P5837 [USACO19DEC] Milk Pumping G

题目https://www.luogu.com.cn/problem/P5837 温馨提示&#xff1a;鉴于数据范围小的可怜&#xff0c;我们可以用暴力一些的想法去做&#xff0c;别看到是普及/提高就被吓退了。 枚举最小流量 &#xff0c;然后跑一遍最短路&#xff0c;求出带限制的 到 的最短路的长度&#…

动态规划——斐波那契数列模型问题

文章目录 1137. 第 N 个泰波那契数算法原理代码实现 面试题 08.01. 三步问题算法原理代码实现 746. 使用最小花费爬楼梯算法原理代码实现 91. 解码方法算法原理代码实现 1137. 第 N 个泰波那契数 题目链接&#xff1a;1137. 第 N 个泰波那契数 算法原理 状态表示&#xff1a;…

LabVIEW涡轮诊断系统

一、项目背景与行业痛点 涡轮机械是发电厂、航空发动机、石油化工等领域的核心动力设备&#xff0c;其运行状态直接关系到生产安全与经济效益。据统计&#xff0c;涡轮故障导致的非计划停机可造成每小时数十万元的经济损失&#xff0c;且突发故障可能引发严重安全事故。传统人…

java程序员面试自身优缺点,详细说明

程序员面试大厂经常被问到的Java异常机制问题,你搞懂了吗运行时异常:运行时异常是可能被程序员避免的异常。与检查性相反,运行时异常可以在编译时被忽略。错误(ERROR):错误不是异常,而是脱离程序员控制的问题。错误通常在代码中容易被忽略。例如:当栈溢出时,一个错误就发生了,它…

大话特征工程:3.特征扩展

公元 2147 年&#xff0c;人类文明站在科技的巅峰&#xff0c;所有决策、发展甚至感知都被“全维计算网络”所掌控。这套系统以高维空间中的数据为基础&#xff0c;试图预测并塑造未来。然而&#xff0c;这场辉煌的技术革命却在悄无声息之间酿成了人类最大的危机——维数灾难。…

CSV数据分析智能工具(基于OpenAI API和streamlit)

utils.py&#xff1a; from langchain_openai import ChatOpenAI from langchain_experimental.agents.agent_toolkits import create_csv_agent import jsonPROMPT_TEMPLATE """你是一位数据分析助手&#xff0c;你的回应内容取决于用户的请求内容。1. 对于文…

2025.2.5

Web [SWPUCTF 2021 新生赛]ez_unserialize: 这个题先了解一下反序列化&#xff1a;反序列化是序列化的逆过程。序列化是将对象或数据结构转换为可以存储或传输的格式&#xff08;如JSON、XML或二进制格式&#xff09;的过程。反序列化则是将这个格式的数据转换回原始的对象或…

新版AndroidStudio 修改 jdk版本

一、问题 之前&#xff0c;在安卓项目中配置JDK和Gradle的过程非常直观&#xff0c;只需要进入Android Studio的File菜单中的Project Structure即可进行设置&#xff0c;十分方便。 如下图可以在这修改JDK: 但是升级AndroidStudio之后&#xff0c;比如我升级到了Android Stu…

Web3技术详解

Web3技术代表着互联网技术的最新进展&#xff0c;它致力于打造一个去中心化的互联网生态系统。以下是对Web3技术的详细解析&#xff1a; 一、Web3技术的核心概念 Web3是第三代互联网技术的代名词&#xff0c;代表着去中心化、区块链驱动和用户自有控制的理念。在Web3的世界中…

景联文科技:专业数据采集标注公司 ,助力企业提升算法精度!

随着人工智能技术加速落地&#xff0c;高质量数据已成为驱动AI模型训练与优化的核心资源。据统计&#xff0c;全球AI数据服务市场规模预计2025年突破200亿美元&#xff0c;其中智能家居、智慧交通、医疗健康等数据需求占比超60%。作为国内领先的AI数据服务商&#xff0c;景联文…

3.【BUUCTF】XSS-Lab1

进入题目页面如下 好好好&#xff0c;提示点击图片&#xff0c;点进去页面如下&#xff0c;且url中有传参&#xff0c;有注入点 发现题目给出了源码 查看得到本题的源码 分析一下代码 <!DOCTYPE html><!--STATUS OK--> <!-- 声明文档类型为 HTML5&#xff0c;告…

进程、线程、内存和IO模型的概念详解

进程、线程、内存和IO模型的概念详解 1 进程与线程1.1 进程1.1.1 进程分类1.1.2 进程的状态和转换1.1.3 僵尸进程和孤儿进程的区别1.1.4 进程之间的通信1.1.5 用户态和内核态1.1.6 用户空间和内核空间 1.2 线程1.2.1 线程的状态和转换1.2.2 进程与线程的区别 1.3 多进程和多线程…

浅谈密码相关原理及代码实现

本代码仅供学习、研究、教育或合法用途。开发者明确声明其无意将该代码用于任何违法、犯罪或违反道德规范的行为。任何个人或组织在使用本代码时&#xff0c;需自行确保其行为符合所在国家或地区的法律法规。 开发者对任何因直接或间接使用该代码而导致的法律责任、经济损失或…

Swagger相关内容整合

mvc:pathmatch:matching-strategy: ant_path_matcher 一、引入相关依赖 <!-- 图像化依赖 --> <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.9.2</version> </de…

【数据结构】循环链表

循环链表 单链表局限性单向循环链表判断链表是否有环思路code 找到链表入口思路代码结构与逻辑 code 单链表局限性 单链表作为一种基本的数据结构&#xff0c;虽然在很多场景下都非常有用&#xff0c;但它也存在一些局限性&#xff1a; 单向访问&#xff1a;由于每个节点仅包含…

简易C语言矩阵运算库

参考网址&#xff1a; 异想家纯C语言矩阵运算库 - Sandeepin - 博客园 这次比opencv快⑥倍&#xff01;&#xff01;&#xff01; 参考上述网址&#xff0c;整理了一下代码&#xff1a; //main.c#include <stdio.h> #include <stdlib.h> #include <string.h…

微服务知识——微服务架构的演进过程

文章目录 初始架构&#xff1a;单机架构第一次演进&#xff1a;Tomcat与数据库分开部署第二次演进&#xff1a;引入本地缓存和分布式缓存第三次演进&#xff1a;引入反向代理实现负载均衡第四次演进&#xff1a;数据库读写分离第五次演进&#xff1a;数据库按业务分库第六次演进…

Hackmyvm crack

简介 难度&#xff1a;简单 靶机地址&#xff1a; 环境 kali&#xff1a;192.168.194.9 靶机&#xff1a;192.168.194.23 扫描 nmap全端口扫描查看tcp服务 三个端口服务21的ftp服务、4200的shellinabox服务&#xff0c;是一个web界面的shell连接工具&#xff0c;12359的一…