【Rust http编程】Rust搭建webserver的底层原理与应用实战

在这里插入图片描述

✨✨ 欢迎大家来到景天科技苑✨✨

🎈🎈 养成好习惯,先赞后看哦~🎈🎈

🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Rust开发,Python全栈,Golang开发,云原生开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。

所属的专栏:Rust语言通关之路
景天的主页:景天科技苑

在这里插入图片描述

文章目录

  • Rust http编程
    • 1. HTTP基础与Rust生态系统
      • 1.1 HTTP协议回顾
      • 1.2 Rust HTTP生态系统概览
    • 2. 使用标准库进行HTTP编程
      • 2.1 基本HTTP服务端
      • 2.2 简单HTTP客户端
      • 2.3 服务端响应网页
      • 2.4 有条件地响应网页
      • 2.5 多线程的http服务器
      • 2.6 线程池webserver
      • 2.7 实现线程池清除的webserver

Rust http编程

Rust作为一门系统级编程语言,凭借其出色的性能、内存安全性和并发特性,在网络编程领域展现出强大的潜力。
本文将详细介绍如何使用Rust进行HTTP编程,涵盖从基础概念到实际应用的各个方面。

1. HTTP基础与Rust生态系统

1.1 HTTP协议回顾

HTTP(HyperText Transfer Protocol)是应用层协议,基于请求-响应模型工作。Rust提供了多种处理HTTP协议的方式:
标准库:基础但功能有限
第三方库:功能丰富,如reqwest、hyper等
Web框架:如actix-web、rocket等

1.2 Rust HTTP生态系统概览

Rust的HTTP生态系统包含多个层次的组件:
底层库:hyper、h2、http等
客户端库:reqwest、ureq等
服务器框架:actix-web、rocket、warp等
工具库:serde(序列化)、tokio(异步运行时)等

2. 使用标准库进行HTTP编程

虽然不推荐在生产环境中使用标准库进行HTTP编程,但了解其基本用法有助于理解底层原理。
可以参考官方标准库net库 https://doc.rust-lang.org/stable/std/net/index.html
TcpListener可以创建http客户端和服务端
在这里插入图片描述

HTTP简单介绍
(1)http请求报文包含三个部分内容 :请求行、请求头 、请求体
Method Request-URI HTTP-Version CRLF //请求行:请求方式、协议版本等
headers CRLF //请求头:包含若干个属性,格式为“属性名:属性值”,格式为"属性名:属性值",服务端据此获取客户端的信息
message-body //请求体 :客户端真正要传送给服务端的内容

(2)http响应报文也有三部分内容:响应行、响应头、响应体
HTTP-Version status-Code Reason-Phrase CRLF //响应行:报文协议及版本,状态码及状态描述
headers CRLF //响应头:由多个属性组成
message-body //响应体:真正响应的内容

2.1 基本HTTP服务端

主要使用标准库中的net库和io库

use std::net::{ TcpListener, TcpStream }; //导入TcpListener和TcpStream
use std::io::{ Read, Write }; //导入Read和Writefn handle_client(mut stream: TcpStream) {//读取客户端请求,每次读取1024个字节let mut buffer = [0; 1024];stream.read(&mut buffer).unwrap();//打印客户端请求println!("Request: {}", String::from_utf8_lossy(&buffer[..]));//构建http响应,向客户端打招呼//获取客户端地址let client_addr = stream.peer_addr().unwrap();println!("New connection: {}", client_addr);let response = format!("HTTP/1.1 200 OK\r\n\r\nhello {client_addr}!");//将响应写入到客户端stream.write_all(response.as_bytes()).unwrap();//刷新缓冲区stream.flush().unwrap();
}fn main() -> std::io::Result<()> {//创建监听器let listener = TcpListener::bind("127.0.0.1:8080")?;//处理客户端请求//listener.incoming()返回一个迭代器,用于接收客户端的连接请求for stream in listener.incoming() {//处理客户端请求的逻辑//listener.incoming()返回的迭代器包含错误,需要使用?来处理handle_client(stream?);}Ok(())
}

2.2 简单HTTP客户端

use std::io::{ Read, Write };
use std::net::TcpStream;fn main() -> std::io::Result<()> {//创建TCP连接let mut stream = TcpStream::connect("localhost:8080")?;//构建HTTP请求let request ="GET / HTTP/1.1\r\n\Host: localhost:8080\r\n\Connection: close\r\n\\r\n";stream.write_all(request.as_bytes())?;//创建个缓冲区,用于读取服务器的响应let mut buffer = Vec::new();//读取服务器的响应stream.read_to_end(&mut buffer)?;//打印服务器的响应println!("{}", String::from_utf8_lossy(&buffer));Ok(())
}

服务端收到客户端请求
在这里插入图片描述

客户端收到服务端响应
在这里插入图片描述

2.3 服务端响应网页

use std::net::{ TcpListener, TcpStream }; //导入TcpListener和TcpStream
use std::io::{ Read, Write }; //导入Read和Writefn handle_client(mut stream: TcpStream) {//读取客户端请求,每次读取1024个字节let mut buffer = [0; 1024];stream.read(&mut buffer).unwrap();//打印客户端请求println!("Request: {}", String::from_utf8_lossy(&buffer[..]));//构建http响应,向客户端打招呼//获取客户端地址let client_addr = stream.peer_addr().unwrap();println!("New connection: {}", client_addr);// let response = format!("HTTP/1.1 200 OK\r\n\r\nhello {client_addr}!");//从文件读取内容响应给客户端let content = std::fs::read_to_string("index.html").unwrap();let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", content);//将响应写入到客户端stream.write_all(response.as_bytes()).unwrap();//刷新缓冲区stream.flush().unwrap();
}fn main() -> std::io::Result<()> {//创建监听器let listener = TcpListener::bind("127.0.0.1:8080")?;//处理客户端请求//listener.incoming()返回一个迭代器,用于接收客户端的连接请求for stream in listener.incoming() {//处理客户端请求的逻辑//listener.incoming()返回的迭代器包含错误,需要使用?来处理handle_client(stream?);}Ok(())
}

直接浏览器访问查看
在这里插入图片描述

2.4 有条件地响应网页

有条件地响应网页,主要是对客户端的请求进行判断,不同的请求路径、请求方法等响应不同内容

use std::net::{ TcpListener, TcpStream }; //导入TcpListener和TcpStream
use std::io::{ Read, Write }; //导入Read和Writefn handle_client(mut stream: TcpStream) {//读取客户端请求,每次读取1024个字节let mut buffer = [0; 1024];stream.read(&mut buffer).unwrap();//打印客户端请求println!("Request: {}", String::from_utf8_lossy(&buffer[..]));//构建http响应,向客户端打招呼//获取客户端地址let client_addr = stream.peer_addr().unwrap();println!("New connection: {}", client_addr);// let response = format!("HTTP/1.1 200 OK\r\n\r\nhello {client_addr}!");//获取客户端的请求方法let request_method = std::str::from_utf8(&buffer).unwrap().lines().next().unwrap();let request_method = request_method.split(" ").nth(0).unwrap();println!("Request method: {}", request_method);//判断请求方法是否为GETif request_method != "GET" {let content = std::fs::read_to_string("404.html").unwrap();let response = format!("HTTP/1.1 404 Not Found\r\n\r\n{}", content);stream.write_all(response.as_bytes()).unwrap();stream.flush().unwrap();return;} else {//获取客户端的请求路径let request_path = std::str::from_utf8(&buffer).unwrap().lines().next().unwrap();let request_path = request_path.split(" ").nth(1).unwrap();println!("Request path: {}", request_path);//判断请求路径是否为/if request_path == "/" {let content = std::fs::read_to_string("index.html").unwrap();let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", content);stream.write_all(response.as_bytes()).unwrap();stream.flush().unwrap();return;} else {let content = std::fs::read_to_string("404.html").unwrap();let response = format!("HTTP/1.1 404 Not Found\r\n\r\n{}", content);stream.write_all(response.as_bytes()).unwrap();stream.flush().unwrap();return;}}
}fn main() -> std::io::Result<()> {//创建监听器let listener = TcpListener::bind("127.0.0.1:8080")?;//处理客户端请求//listener.incoming()返回一个迭代器,用于接收客户端的连接请求for stream in listener.incoming() {//处理客户端请求的逻辑//listener.incoming()返回的迭代器包含错误,需要使用?来处理handle_client(stream?);}Ok(())
}

get方法 /路径
在这里插入图片描述

get方法其他路径
在这里插入图片描述

代码优化,将一些重复的代码封装

use std::net::{ TcpListener, TcpStream }; //导入TcpListener和TcpStream
use std::io::{ Read, Write }; //导入Read和Writefn handle_client(mut stream: TcpStream) {//读取客户端请求,每次读取1024个字节let mut buffer = [0; 1024];stream.read(&mut buffer).unwrap();//打印客户端请求println!("Request: {}", String::from_utf8_lossy(&buffer[..]));//获取客户端地址let client_addr = stream.peer_addr().unwrap();println!("New connection: {}", client_addr);// let response = format!("HTTP/1.1 200 OK\r\n\r\nhello {client_addr}!");//获取客户端的请求方法let request_method = std::str::from_utf8(&buffer).unwrap().lines().next().unwrap();let request_method = request_method.split(" ").nth(0).unwrap();println!("Request method: {}", request_method);//封装一个函数,响应客户端fn response_client(mut stream: TcpStream, response: String) {stream.write_all(response.as_bytes()).unwrap();stream.flush().unwrap();}//判断请求方法是否为GETif request_method != "GET" {let content = std::fs::read_to_string("404.html").unwrap();let response = format!("HTTP/1.1 404 Not Found\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);} else {//获取客户端的请求路径let request_path = std::str::from_utf8(&buffer).unwrap().lines().next().unwrap();let request_path = request_path.split(" ").nth(1).unwrap();println!("Request path: {}", request_path);//判断请求路径是否为/if request_path == "/" {let content = std::fs::read_to_string("index.html").unwrap();let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);} else {let content = std::fs::read_to_string("404.html").unwrap();let response = format!("HTTP/1.1 404 Not Found\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);}}
}fn main() -> std::io::Result<()> {//创建监听器let listener = TcpListener::bind("127.0.0.1:8080")?;//处理客户端请求//listener.incoming()返回一个迭代器,用于接收客户端的连接请求for stream in listener.incoming() {//处理客户端请求的逻辑//listener.incoming()返回的迭代器包含错误,需要使用?来处理handle_client(stream?);}Ok(())
}

2.5 多线程的http服务器

单线程的的webserver存在的问题:
请求只能串行处理,也就是说当第一个连接处理完之前不会处理第二个连接。
这样,当有海量请求的时候,就会出问题
我们采用多线程

//多线程的http服务器
use std::thread;
use std::net::{ TcpListener, TcpStream };
use std::io::{ Read, Write };fn handle_client(mut stream: TcpStream) {//读取客户端请求,每次读取1024个字节let mut buffer = [0; 1024];stream.read(&mut buffer).unwrap();//打印客户端请求println!("Request: {}", String::from_utf8_lossy(&buffer[..]));//获取客户端地址let client_addr = stream.peer_addr().unwrap();println!("New connection: {}", client_addr);// let response = format!("HTTP/1.1 200 OK\r\n\r\nhello {client_addr}!");//获取客户端的请求方法let request_method = std::str::from_utf8(&buffer).unwrap().lines().next().unwrap();let request_method = request_method.split(" ").nth(0).unwrap();println!("Request method: {}", request_method);//封装一个函数,响应客户端fn response_client(mut stream: TcpStream, response: String) {stream.write_all(response.as_bytes()).unwrap();stream.flush().unwrap();}//判断请求方法是否为GETif request_method != "GET" {let content = std::fs::read_to_string("404.html").unwrap();let response = format!("HTTP/1.1 404 Not Found\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);} else {//获取客户端的请求路径let request_path = std::str::from_utf8(&buffer).unwrap().lines().next().unwrap();let request_path = request_path.split(" ").nth(1).unwrap();println!("Request path: {}", request_path);//判断请求路径是否为/if request_path == "/" {let content = std::fs::read_to_string("index.html").unwrap();let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);} else {let content = std::fs::read_to_string("404.html").unwrap();let response = format!("HTTP/1.1 404 Not Found\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);}}
}fn main() -> std::io::Result<()> {//创建监听器let listener = TcpListener::bind("127.0.0.1:8080")?;//创建线程句柄let mut handles = Vec::new();//处理客户端请求//listener.incoming()返回一个迭代器,用于接收客户端的连接请求for stream in listener.incoming() {//处理客户端请求的逻辑//使用多线程let handle = thread::spawn(move || {handle_client(stream.unwrap());});handles.push(handle);}//等待所有线程结束for handle in handles {handle.join().unwrap();}Ok(())
}

2.6 线程池webserver

上面通过多线程创建的webserver,当请求不断太多时,还是可以用一用。
但是当请求比较海量时,系统也会跟着创建海量的线程,最终造成系统资源耗尽而崩溃
此时,我们采用线程池来处理
在这里插入图片描述

多线程,管道
从主线程将任务发送到管道,工作线程等待在管道的接收端,当收到任务时,进行处理。

✅ 创建文件结构:
.
├── main.rs
├── lib.rs // 线程池模块

🔧 Cargo.toml(依赖可以不用加,使用标准库)

[package]
name = "myhttpserver3"
version = "0.1.0"
edition = "2024"[dependencies]

📄 src/main.rs

use std::net::TcpListener;
use std::io::prelude::*;
use std::net::TcpStream;
use myhttpserver3::ThreadPool; //这里myhttpserver3是Cargo.toml中定义的依赖库名称,就是项目的名称fn main() {//创建监听器,监听7878端口let listener = TcpListener::bind("127.0.0.1:7878").unwrap();//创建线程池,线程池大小为4let pool = ThreadPool::new(4);println!("Server running on 127.0.0.1:7878");//使用线程池处理请求for stream in listener.incoming().take(10) {let stream = stream.unwrap();pool.execute(|| {handle_connection(stream);});}println!("Shutting down.");
}fn handle_connection(mut stream: TcpStream) {//读取客户端请求,每次读取1024个字节let mut buffer = [0; 1024];stream.read(&mut buffer).unwrap();//打印客户端请求println!("Request: {}", String::from_utf8_lossy(&buffer[..]));//获取客户端地址let client_addr = stream.peer_addr().unwrap();println!("New connection: {}", client_addr);// let response = format!("HTTP/1.1 200 OK\r\n\r\nhello {client_addr}!");//获取客户端的请求方法let request_method = std::str::from_utf8(&buffer).unwrap().lines().next().unwrap();let request_method = request_method.split(" ").nth(0).unwrap();println!("Request method: {}", request_method);//封装一个函数,响应客户端fn response_client(mut stream: TcpStream, response: String) {stream.write_all(response.as_bytes()).unwrap();stream.flush().unwrap();}//判断请求方法是否为GETif request_method != "GET" {let content = std::fs::read_to_string("404.html").unwrap();let response = format!("HTTP/1.1 404 Not Found\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);} else {//获取客户端的请求路径let request_path = std::str::from_utf8(&buffer).unwrap().lines().next().unwrap();let request_path = request_path.split(" ").nth(1).unwrap();println!("Request path: {}", request_path);//判断请求路径是否为/if request_path == "/" {let content = std::fs::read_to_string("index.html").unwrap();let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);} else {let content = std::fs::read_to_string("404.html").unwrap();let response = format!("HTTP/1.1 404 Not Found\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);}}
}

📄 src/lib.rs

//线程池
use std::sync::{ Arc, Mutex };
use std::sync::mpsc;
use std::thread;//定义一个结构体,表示线程池
#[allow(dead_code)]
pub struct ThreadPool {workers: Vec<Worker>,sender: mpsc::Sender<Job>,
}//使用type关键字定义一个类型别名,表示任务。使用type起类型别名,用于简化代码
//这个类型是依照ThreadPool的excute()方法的参数类型来的
type Job = Box<dyn FnOnce() + Send + 'static>;//为ThreadPool实现方法
impl ThreadPool {// 创建新线程池pub fn new(size: usize) -> ThreadPool {//线程池的大小必须大于0assert!(size > 0);println!("Creating a thread pool of size {}", size);//创建通道let (sender, receiver) = mpsc::channel();//将接收端放入互斥锁中,再放入Arc中,实现共享let receiver = Arc::new(Mutex::new(receiver));//创建线程池let mut workers = Vec::with_capacity(size);//创建工作线程for id in 0..size {workers.push(Worker::new(id, Arc::clone(&receiver)));}//返回线程池ThreadPool { workers, sender }}// 执行任务。这里是参照标准库 thread::spawn()的实现的//对F有约束pub fn execute<F>(&self, f: F) where F: FnOnce() + Send + 'static {//将任务包装成Boxlet job = Box::new(f);self.sender.send(job).unwrap();}
}//定义一个结构体,表示工作线程
#[allow(dead_code)]
struct Worker {id: usize, //工作线程的idthread: thread::JoinHandle<()>, //线程句柄
}//为Worker实现方法
impl Worker {//接收端需要线程安全,所以需要Arc<Mutex<T>>fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {//创建工作线程let thread = thread::spawn(move || {//循环从通道中接收任务,并执行loop {//recv会阻塞线程,直到有数据可读let job = receiver.lock().unwrap().recv().unwrap();println!("Worker {} got a job; executing.", id);//执行任务job();}});//返回工作线程Worker { id, thread }}
}

📄 index.html(放在项目根目录)

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><h1>Hello, Jingtian!</h1></body>
</html>

📄 404.html

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><h1>Oops!</h1><p>The page you are looking for does not exist.</p></body>
</html>

运行服务器
cargo run

然后在浏览器打开 http://127.0.0.1:7878/
在这里插入图片描述

如果是其他路径
在这里插入图片描述

2.7 实现线程池清除的webserver

在之前的用线程池实现的webserver中,每个工作线程中通过loop进行循环,从channel的接收端等待任务,然后执行。
但是在代码中,work采用的是loop循环,没有跳出循环的条件,没有提供一种机制,来通知工作线程结束。
现在我们就来实现线程池对象的正确清除。
通过为ThreadPool实现Drop trait来实现线程池对象清除

修改Worker如下:

struct Worker {id: usize, //工作线程的id//线程句柄,将thread::JoinHandle<()>包装成Option,用于在drop()方法中调用take()方法//Option中有take()方法,可以将Some中的值取出来,同时将Some置为Nonethread: Option<thread::JoinHandle<()>>,
}

Option中有take方法
在这里插入图片描述

完成的代码:
src/lib.rs

//线程池
use std::sync::{ Arc, Mutex };
use std::sync::mpsc;
use std::thread;//定义一个结构体,表示线程池
#[allow(dead_code)]
pub struct ThreadPool {workers: Vec<Worker>,// sender: mpsc::Sender<Job>,sender: mpsc::Sender<Message>,
}//使用type关键字定义一个类型别名,表示任务。使用type起类型别名,用于简化代码
//这个类型是依照ThreadPool的excute()方法的参数类型来的
type Job = Box<dyn FnOnce() + Send + 'static>;//发送结束消息给worker,所有发送job的地方都要修改
enum Message {//两种情况NewJob(Job),Terminate,
}//为ThreadPool实现方法
impl ThreadPool {// 创建新线程池pub fn new(size: usize) -> ThreadPool {//线程池的大小必须大于0assert!(size > 0);println!("Creating a thread pool of size {}", size);//创建通道let (sender, receiver) = mpsc::channel();//将接收端放入互斥锁中,再放入Arc中,实现共享let receiver = Arc::new(Mutex::new(receiver));//创建线程池let mut workers = Vec::with_capacity(size);//创建工作线程for id in 0..size {workers.push(Worker::new(id, Arc::clone(&receiver)));}//返回线程池ThreadPool { workers, sender }}// 执行任务。这里是参照标准库 thread::spawn()的实现的//对F有约束pub fn execute<F>(&self, f: F) where F: FnOnce() + Send + 'static {//将任务包装成Boxlet job = Box::new(f);// self.sender.send(job).unwrap();self.sender.send(Message::NewJob(job)).unwrap();}
}//为ThreadPool实现Drop trait
impl Drop for ThreadPool {//当线程池被销毁时,关闭所有工作线程//实现Drop trait,只需要实现drop()方法即可fn drop(&mut self) {//发送结束消息给workerfor _ in &self.workers {self.sender.send(Message::Terminate).unwrap();}//等待所有工作线程结束for worker in &mut self.workers {println!("Shutting down worker {}", worker.id);//等待工作线程结束if let Some(thread) = worker.thread.take() {thread.join().unwrap();}}}
}//定义一个结构体,表示工作线程
#[allow(dead_code)]
struct Worker {id: usize, //工作线程的id//线程句柄,将thread::JoinHandle<()>包装成Option,用于在drop()方法中调用take()方法//Option中有take()方法,可以将Some中的值取出来,同时将Some置为Nonethread: Option<thread::JoinHandle<()>>,
}//为Worker实现方法
impl Worker {//接收端需要线程安全,所以需要Arc<Mutex<T>>fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Message>>>) -> Worker {//创建工作线程let thread = thread::spawn(move || {//循环从通道中接收任务,并执行loop {//recv会阻塞线程,直到有数据可读// let job = receiver.lock().unwrap().recv().unwrap();let message = receiver.lock().unwrap().recv().unwrap();// println!("Worker {} got a job; executing.", id);//判断消息类型match message {Message::NewJob(job) => {println!("Worker {} got a job; executing.", id);job();}Message::Terminate => {println!("Worker {} was told to terminate.", id);//收到结束消息,退出循环break;}}}});//返回工作线程Worker { id, thread: Some(thread) }}
}

src/main.rs

use std::net::TcpListener;
use std::io::prelude::*;
use std::net::TcpStream;
use myhttpserver4::ThreadPool; //这里myhttpserver3是Cargo.toml中定义的依赖库名称,就是项目的名称fn main() {//创建监听器,监听7878端口let listener = TcpListener::bind("127.0.0.1:7878").unwrap();//创建线程池,线程池大小为4let pool = ThreadPool::new(4);println!("Server running on 127.0.0.1:7878");//使用线程池处理请求//listener.incoming()返回一个迭代器,用于接收客户端的连接请求//take(4)表示只接收4个连接请求,可以根据实际情况调整for stream in listener.incoming().take(4) {let stream = stream.unwrap();pool.execute(|| {handle_connection(stream);});}println!("Shutting down.");
}fn handle_connection(mut stream: TcpStream) {//读取客户端请求,每次读取1024个字节let mut buffer = [0; 1024];stream.read(&mut buffer).unwrap();//打印客户端请求println!("Request: {}", String::from_utf8_lossy(&buffer[..]));//获取客户端地址let client_addr = stream.peer_addr().unwrap();println!("New connection: {}", client_addr);// let response = format!("HTTP/1.1 200 OK\r\n\r\nhello {client_addr}!");//获取客户端的请求方法let request_method = std::str::from_utf8(&buffer).unwrap().lines().next().unwrap();let request_method = request_method.split(" ").nth(0).unwrap();println!("Request method: {}", request_method);//封装一个函数,响应客户端fn response_client(mut stream: TcpStream, response: String) {stream.write_all(response.as_bytes()).unwrap();stream.flush().unwrap();}//判断请求方法是否为GETif request_method != "GET" {let content = std::fs::read_to_string("404.html").unwrap();let response = format!("HTTP/1.1 404 Not Found\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);} else {//获取客户端的请求路径let request_path = std::str::from_utf8(&buffer).unwrap().lines().next().unwrap();let request_path = request_path.split(" ").nth(1).unwrap();println!("Request path: {}", request_path);//判断请求路径是否为/if request_path == "/" {let content = std::fs::read_to_string("index.html").unwrap();let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);} else {let content = std::fs::read_to_string("404.html").unwrap();let response = format!("HTTP/1.1 404 Not Found\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);}}
}

接收4个请求后,服务器就关闭

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

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

相关文章

4 Geotools坐标参考系与转换

在地理信息系统 (GIS) 开发中&#xff0c;坐标参考系统 (Coordinate Reference System, CRS) 是核心概念之一。无论是处理地图投影、坐标转换&#xff0c;还是在 Spring Boot 应用中管理空间数据&#xff0c;理解和正确使用 CRS 都至关重要。本文将围绕 GeoTools 库&#xff0c…

docker start mysql失败,解决方案

文章目录 1.查看端口占用情况2.关闭7767进程3.再次检查4.运行docker start mysql 1.查看端口占用情况 sudo netstat -tanlp | grep :33062.关闭7767进程 sudo kill -9 77673.再次检查 进程已关闭 4.运行docker start mysql 正确启动 备注&#xff1a;可能要关闭防火墙

SQL关键字三分钟入门:DELETE —— 删除数据

在数据库操作中&#xff0c;除了添加和修改记录外&#xff0c;我们有时还需要删除不需要的记录。例如&#xff1a; 清除不再使用的用户账号&#xff1b;删除已完成并归档的订单&#xff1b;移除测试时插入的数据。 这时候就需要用到 SQL 中非常基础但极其重要的关键字 —— D…

electron 全量更新

electron-builder.yml配置更新地址 # 配置自动更新的信息 publish:provider: generic # 更新服务提供者url: http://xxx.xxxx.com/pc/xxx-xx# 更新的地址服务器地址 会自动读取latest.yml 下的版本号比较 检测更新方法autoUpdater.js// src/main/autoUpdater.jsimport { app, d…

《大模型 Agent 应用实战指南》第2章:商业目标与 Agent 能力边界定义

在任何技术项目,特别是像大模型 Agent 这样具有创新性和复杂性的项目启动之初,明确清晰的商业目标是成功的基石。这不仅仅是技术团队的职责,更需要产品、运营、销售甚至高层管理者的深度参与。一个明确的目标能确保所有团队成员步调一致,资源有效分配,并最终衡量项目的成功…

提供稳定可靠的自助共享空间系统,支撑客户无人自助门店运营不错数据,历程感想

以技术产品研发系统为主&#xff0c;为客户提供自助共享空间系统解决方案&#xff0c;适用于共享棋牌室&#xff0c;共享麻将室&#xff0c;共享台球室&#xff0c;共享KTV&#xff0c;共享舞蹈室等场景&#xff0c;以下是其中一位客户真实门店运营数据&#xff0c;第一家店本月…

Golang单例实现

Go语言中&#xff0c;实现单例模式的方式有很多种。单例模式确保一个类只有一个实例&#xff0c;并提供一个全局访问点。Go语言没有类的概念&#xff0c;但是可以通过结构体、函数和包级变量来实现类似的功能。 懒汉实现 type Product interface {DoSomething() }type single…

JVM元空间(Metaspace)详解及其工作流程

JVM元空间(Metaspace)详解与工作流程分析 元空间概述 元空间(Metaspace)是Java虚拟机(JVM)在HotSpot VM 1.8及以后版本中引入的&#xff0c;用于替代永久代(PermGen)的内存区域。它主要存储类的元数据信息&#xff0c;包括&#xff1a; 类的结构信息&#xff08;如方法、字段…

【JAVA】idea中打成jar包后报错错误: 找不到或无法加载主类

排查步骤 首先要排查的是&#xff0c;将jar文件打开&#xff0c;查看里面的内容是否完整是否有META-INF/MANIFEST.MF是否有MANIFEST.MF里面类路径的目录排查路径里面是否有class文件&#xff0c;如主类 com.example.Main 对应的 class 文件应位于 com/example/Main.class 常见…

Fisco Bcos学习 - 开发第一个区块链应用

文章目录 一、前言二、业务场景分析&#xff1a;简易资产管理系统三、智能合约设计与实现3.1 存储结构设计3.2 接口设计3.3 完整合约代码 四、合约编译与Java接口生成五、SDK配置与项目搭建5.1 获取Java工程项目5.2 项目目录结构5.3 引入Web3SDK5.4 证书与配置文件 六、业务开发…

软件设计模式选择、判断解析-1

前言 解析是我个人的理解&#xff0c;相对来说我觉得是能对上定义的逻辑的 目录 一.单选题 1.设计模式的两大主题是(  )  解析&#xff1a;无 2.下列模式中,属于行为型模式的是&#xff08;&#xff09; 解析&#xff1a; 排除A和D&#xff0c;剩下的观察者的“观察”…

【编程基本功】Win11中Git安装配置全攻略,包含Git以及图形化工具TortoiseGit

1 摘要 今天田辛老师给大家带来了一份超实用的博客&#xff0c;手把手教你安装并配置 Git 及其图形化界面 TortoiseGit&#xff0c;从官网下载到最终完成配置&#xff0c;每一个步骤都给大家讲得明明白白&#xff0c;还配有相应的截图&#xff0c;即使是新手小白也能轻松上手&…

细谈QT信号与槽机制

转自个人博客 信号与槽是我个人认为QT中最牛的机制之一&#xff0c;最近没有其他的内容可写&#xff0c;今天就来细细总结一下这个信号与槽机制。 1. 信号与槽机制概述 信号与槽机制可以理解为QT中的一种通信手段&#xff0c;在运行相关代码前&#xff0c;分别声明信号和槽&a…

Docker Swarm 与 Kubernetes 在集群管理上的主要区别

Docker Swarm 和 Kubernetes 是两种流行的容器编排工具&#xff0c;它们都可以用于部署、管理和扩展容器化应用&#xff0c;但在集群管理方面有明显的差异。 下面从多个维度对比它们在集群管理上的主要区别&#xff1a; ✅ 一、总体定位 项目Docker SwarmKubernetes官方支持D…

【StarRocks系列】查询优化

步骤参考官网 分析查询 | StarRocks StarRocks-Profile分析及优化指南 StarRocks-Profile分析及优化指南 - 经验教程 - StarRocks中文社区论坛

软测八股--测试理论 1 测试基础

软件测试&#xff1f; 发现程序中的侧屋执行程序工程 目的&#xff1a;不仅是找出错误&#xff0c;还要分析错误产生原因和错误分布。检查开发如阿健过程出现的bug&#xff0c;使开发人员及时修改。测试只能说明软件中存在错误 目标&#xff1a;尽可能发现多的错误。一个好的…

mfc与vs成功在xp系统所需做的修改

目录 前言一、MFC程序 inet_pton 、CT2A 未声明问题1&#xff09;问题1&#xff1a;inet_pton &#xff1a;undeclared identifier - inet_pton未声明2&#xff09;问题1&#xff1a;CT2A &#xff1a;undeclared identifier - CT2A未声明 二、VS程序 使用事件、委托问题1&…

SpringMVC系列(三)(请求处理的十个实验(上))

0 引言 作者正在学习SpringMVC相关内容&#xff0c;学到了一些知识&#xff0c;希望分享给需要短时间想要了解SpringMVC的读者朋友们&#xff0c;想用通俗的语言讲述其中的知识&#xff0c;希望与诸位共勉&#xff0c;共同进步&#xff01; 本系列会持续更新&#xff01;&…

Python案例练习:函数专题

用函数重新设计文章单词出现次数程序 composition This is my family. We have a father, a mother and two brothers. My father is a doctor. He works in a hospital. My mother is a teacher. She teaches English in a school. My older brother is a student. He stu…

数据驱动 AI 时代:数据库行业的技术跃迁与生态重构

在数据驱动的 AI 战场&#xff0c;真正的决胜武器不是复杂精妙的算法模型&#xff0c;而是深埋在企业核心系统中的高维数据网络&#xff08;图&#xff09;。 时至今日&#xff0c;市场对AI的风向正从“狂飙突进”转向“精耕细作”&#xff0c;就在上周&#xff08;米国时间6月…