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

文章目录

    • 监听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连接

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的

代码:

use std::{// 帮助我们读取和写入数据// BufReader可以实现缓冲区读取,底层其实是基于std::io::Read实现,可以使用lines方法获取一个迭代器,可以对传输的内容流进行按行迭代读取,要使用该方法,需引入std::io::BufReadio::{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发送回客户端:

/// # 函数作用
/// 处理连接:读取请求,回应请求
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>

添加导包:

use std::fs;

修改handle_connection函数:

/// # 函数作用
/// 处理连接:读取请求,回应请求
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

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.mzph.cn/news/880849.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

若无向图G(V,E)中含7个顶点,为保证图G在任何情况下都是连通的,则需要的边数最少是多少?

这乍一看是不是可抽象&#xff08;迷糊&#xff09;了&#xff0c;butttt待我小翻译一下。 先举少一点的例子&#xff0c;假如我们有三个点&#xff0c;我给你两条边&#xff0c;那是不是不管咋连都一定一定是连通的。 那我们再进一步&#xff0c;假如四个点呢&#xff1f;我给…

RabbitMQ 界面管理说明

1.RabbitMQ界面访问端口和后端代码连接端口不一样 界面端口是15672 http://localhost:15672/ 后端端口是 5672 默认账户密码登录 guest 2.总览图 3.RabbitMq数据存储位置 4.队列 4.客户端消费者连接状态 5.队列运行状态 6.整体运行状态

在Linux中将设备驱动的地址映射到用户空间

本期主题&#xff1a; MMU的简单介绍&#xff0c;以及如何实现设备地址映射到用户空间 往期链接&#xff1a; Linux内核链表零长度数组的使用inline的作用嵌入式C基础——ARRAY_SIZE使用以及踩坑分析Linux下如何操作寄存器&#xff08;用户空间、内核空间方法讲解&#xff09;…

Redis篇(最佳实践)(持续更新迭代)

介绍一&#xff1a;键值设计 一、优雅的key结构 Redis 的 Key 虽然可以自定义&#xff0c;但最好遵循下面的几个最佳实践约定&#xff1a; 遵循基本格式&#xff1a;[业务名称]:[数据名]:[id]长度不超过 44 字节不包含特殊字符 例如&#xff1a; 我们的登录业务&#xff0…

『功能项目』宠物的攻击巨型化【80】

本章项目成果展示 我们打开上一篇79宠物的召唤跟随的项目&#xff0c; 本章要做的事情是实现在战斗中有几率触发宠物巨型化攻击将怪物击飞的效果 首先在主角预制体中增加隐藏的宠物巨型化 制作巨型化宠物的攻击效果 将该动画控制器放置在隐藏的巨型化宠物的动画控制器上 首先查…

Linux下的基本指令/命令(一)

目录 基本命令 1. Is命令/指令: 罗列当前目录下指定的文件或者目录. 2. pwd命令&#xff1a; 查看当前工作的路径 3. cd命令&#xff1a; 切换到指定路径下。 只能切换到目录中 4. tree命令: 树状显式目录 使用前要输入命令 yum install -y tree &#xff0c;用来安装一个…

基于Hive和Hadoop的哔哩哔哩网站分析系统

本项目是一个基于大数据技术的哔哩哔哩平台分析系统&#xff0c;旨在为用户提供全面的哔哩哔哩视频数据和深入的用户行为分析。系统采用 Hadoop 平台进行大规模数据存储和处理&#xff0c;利用 MapReduce 进行数据分析和处理&#xff0c;通过 Sqoop 实现数据的导入导出&#xf…

数据流处理技术与Flink框架

一数据流 数据流定义&#xff1a; 数据流&#xff08;Data Stream&#xff09;是指数据以连续不断的方式到达和处理的序列。在现实世界中&#xff0c;许多数据来源都是以流的形式存在&#xff0c;比如&#xff1a; 1. 用户行为&#xff1a;用户在网站上的点击流、移动应用中…

【Linux】几种常见配置文件介绍

配置文件目录 linux 系统中有很多配置文件目录/etc/systemd/system、/lib/systemd/system 以及/usr/lib/systemd/system 等&#xff0c;这三者有什么样的关系呢&#xff1f; 以下是网络上找的资料汇总&#xff0c;并加了一些操作验证。方便后期使用 介绍 目录/lib/systemd/s…

使用C#,MSSQL开发的钢结构加工系统

很久以前的项目&#xff0c;上位机使用C#开发。数据库使用mssql。控制系统选用了三菱PLC&#xff0c;上位机和PLC之间走ModbusTCP通讯协议。 主要功能&#xff1a;读取加工文件&#xff08;csv格式&#xff09;&#xff0c;导入到数据库&#xff0c;并根据机床刀具规则&#x…

Python编码系列—Python命令模式:将请求封装为对象

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

【软件测试】详解软件测试中的测试级别

目录 一、测试级别二、组件测试三、开发者测试3.1测试与调试3.2 组件测试目标3.3 测试功能 四、稳健性测试4.1 效率的测试4.2 测试可维护性4.3 测试策略4.4 白盒测试 一、测试级别 软件系统通常是由许多子系统组成的&#xff0c;而这些子系统又是由多个组件组成的&#xff0c;…

VMware Aria Operations for Logs 8.18 发布,新增功能概览

VMware Aria Operations for Logs 8.18 - 集中式日志管理 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-aria-operations-for-logs/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org 集中式日志管理 VMware Aria …

C#测试调用FreeSpire.PDFViewer浏览PDF文件

Free Spire.PDFViewer是商业版Spire.PDFViewer的社区版本&#xff0c;支持以控件形式打开并查看PDf文件&#xff0c;但由于是免费版本&#xff0c;存在使用限制&#xff0c;打开的PDF文档只显示前10页内容。如果日常操作的pdf文件都不超过10页&#xff0c;可以考虑使用Free Spi…

Redis: Sorted Set 底层算法的简单分析

概述 我们先看下 Shorted Set 有序集合的内部数据结构所谓有序集合&#xff0c;比如有个容器&#xff0c;容器里边都已经排好序了&#xff0c;那无非就是快速的查找和插入不管你是查找还是插入&#xff0c;肯定要确定那个位置最简单的办法就是从最开头开始&#xff0c;挨个比较…

【unity进阶知识6】Resources的使用,如何封装一个Resources资源管理器

文章目录 一、Unity资源加载的几种方式1、Inspector窗口拖拽2、Resources3、AssetBundle4、Addressables&#xff08;可寻址资源系统&#xff09;5、AssetDatabase 二、准备三、同步加载Resources资源1、Resources.Load同步加载单个资源1.1、基本加载1.2、加载指定类型的资源1.…

无源码实现免登录功能

因项目要求需要对一个没有源代码的老旧系统实现免登录功能&#xff0c;系统采用前后端分离的方式部署&#xff0c;登录时前端调用后台的认证接口&#xff0c;认证接口返回token信息&#xff0c;然后将token以json的方式存储到cookie中&#xff0c;格式如下&#xff1a; 这里有…

HTTPS加密流程

本文尽量用最小的篇幅来介绍HTTPS的加密过程&#xff0c;如果还看不懂可以参考文末尾的文章。 加密算法 先简单介绍一下HTTPS中使用的是混合加密算法&#xff0c;即对称加密和非对称加密的混合使用&#xff1a; 1.对称加密:顾名思义就是加密和解密都是使用同一个密钥。 优点…

MySQL 问题小结

mysqld --initialize 初始化 data 文件夹 初始化的密码在这个 err 文件夹中

DC00025【含论文】基于协同过滤推荐算法springboot视频推荐管理系统

1、项目功能演示 DC00025【含文档】基于springboot短视频推荐管理系统协同过滤算法视频推荐系统javaweb开发程序设计vue 2、项目功能描述 短视频推荐系统分为用户和系统管理员两个角色 2.1 用户角色 1、用户登录、用户注册 2、视频中心&#xff1a;信息查看、视频收藏、点赞、…