rust实践-使用tcp链接监听web请求,进行http解析

rust实践-使⽤tcp链接监听web请求,进⾏http解析
⽬标
使⽤rust构建⼀个单线程处理http请求的web服务器。
1. 学习有关TCP和HTTP两个协议的知识。
2. 侦听套接字上的TCP连接。
3. 掌握http协议
动⼿
cargo 构建项⽬
我们不希望只构建⼀个⼩玩具的rs代码,⽽是采⽤⽣产⽅式来构建我们的任何rs项⽬,这需要我们⽤到之前讲述过的cargo⼯具。执⾏⼀下指令构建本次实践的项⽬
cargo new webBean
回顾之前⽂章讲过的内容
1、l是项⽬描述信息
2、src是源代码放置的⽬录
使⽤net⽹路库监听tcp链接
补充知识:
Web服务器中涉及的两个主要协议是超⽂本传输协议 (HTTP)和传输控制协议 (TCP)。两种协议都是请求-响应协议,这意味着客户端发起请求,服务器监听请求并向客户端提供响应。
概述:
1. tcp是传输层协议,⽤来实现端到端的数据传输。
2. http是应⽤层协议,当服务器端接收到请求端的数据时,⽤户应⽤层通过解析数据识别是什么应⽤请求(http,icmp,ftp)等再进
⾏业务处理,应⽤层协议实际上也是⼀种逻辑业务:⽐如nginx解析http进⾏转发,或者通常使⽤的http 框架(⽐如go的beego、java的spring mvc等)都进⾏了封装,⽤户只需要处理“真正的业务”请求。
创建服务器,监听tcp链接:
use std::net::TcpListener;
fn main(){
let listener = TcpListener::bind("localhost:9999").unwrap();
for stream in listener.incoming(){
let stream = stream.unwrap();
println!("connection established!");
}
}
核⼼知识点:
1. 使⽤use 引⼊rust提供的net包,使⽤TcpListener的bind函数来请求分配⼀个监听会话,该bind函数返回⼀个Result<T, E>,指⽰绑
定可能失败。正常来说,返回失败时,我们服务器应该是⽆效的,需要退出。那么常见的失败原因有端⼝被占⽤、或者⾮管理员端⼝申请(⽐如80等)。
2. unwrap是rust语⾔中,主要⽤于Option或Result的打开其包装的结果,在⽣产中通常处理Result⽽不是直接使⽤unwrap,在这⾥
只是演⽰,不做深⼊解析。
3. 使⽤let 将 TcpListener监听的返回值绑定到"变量" listener,这个绑定是不可变的,所以实际上⼜是⼀个常量。注意第7⾏的 let
stream,这⾥的stream虽然与循坏的stream重名了,但实际上是通过let绑定了⼀个新值,是⼀个新的"变量"。
4. 服务器建⽴监听后,可以通过Incoming函数来接收客户端链接的请求流,for循环将依次处理每个连接并产⽣⼀系列流供我们处理。
5. 我们对每个流进⾏unwrap处理,如果有错误即终⽌程序,⽣产环境中我们还是通过对Result进⾏错误处理,⽽不是直接终⽌程序。读取请求
我们编写函数handle_connection来处理服务器接收到请求流:
重写后
use std::net::TcpStream;
use std::net::TcpListener;
use std::io::prelude::*;
fn main(){
let listener = TcpListener::bind("127.0.0.1:9999").unwrap();
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;1024];
println!("request : {}",String::from_utf8_lossy(&buffer[..]));
}
核⼼知识点:
1、引⼊了io包,通过ad函数,我们将流的数据写⼊到在栈上分配好的buffer缓冲区,buffer⼤⼩为1024字节,⼤⼩⾜以处理我们的这个demo请求,buffer需要存储我们的数据流,因此需要⽤mut表⽰可变性,这⾥的缓存⼤⼩1024字节 主要是满⾜⽰例演⽰,如果数据量⼤,我们需要对buffer进⾏管理,传输流并不是⼀次性传输的,⽹络编程中时需要解决由⼩包、⼤包所引发的⼀系列问题,在这⾥暂不详细描述。
2、String::from_utf8_lossy 函数获取⼀个 &[u8] 并产⽣⼀个 String。函数名的 “lossy” 部分来源于当其遇到⽆效的 UTF-8 序列时的⾏为:它使⽤ �,U+FFFD REPLACEMENT CHARACTER,来代替⽆效序列。你可能会在缓冲区的剩余部分看到这些替代字符,因为他们没有被请求数据填满。
3、u8表⽰⽆符号的8bit,我们知道,⼀字节等于8bit,⽽这⾥采⽤[]数组来表⽰字符串,&表⽰引⽤,对c++使⽤者来说,并不陌⽣。
使⽤cargo run运⾏我们的项⽬:
$ cargo run
Compiling webBean v0.1.0 (file:///projects/webBean)
Finished dev [unoptimized + debuginfo] target(s)in0.42 secs
Running `target/debug/webBean`
Request: GET / HTTP/1.1
Host: 127.0.0.1:7878
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101
Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
������������������������������������
浏览器访问,以下为服务器接收到的流
request : GET / HTTP/1.1
Host: 10.86.168.45:9999
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/90.0.4430.212 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.9 Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: XSRF-TOKEN=4e49b69a-04ae-4f95-b0a4-ca0be37eb43e;JSESSIONID=FA53C000048E241DCE62614648599B6D
http协议解析
http是⼀种⽂本协议,其请求格式如下:
Method Request-URI HTTP-Version CRLF
headers CRLF
message-body
Request
对照我们前⽂服务器的输出内容:
1、Method 表⽰我们请求使⽤的⽅法,⽐如 Get、Post,⽽我们通过浏览器访问时,客户端请求使⽤的是Get ⽅法。
2、Request-URI,URI(Uniform Resource Identifier)统⼀资源标识符,⽤于标识我们所请求的资源。我们客户端访问的是 / 资源。
3、HTTP-Version 表⽰http 协议的版本,可以看到我们客户端使⽤的是1.1
4、最后⾏结束采⽤CRLF 回车换⾏符 \r\n
5、我们请求的第⼆⾏内容 Host: 开始表⽰的是协议头,请求没有body
Response
有请求,那必然得回复,http的回复协议格式如下:
HTTP-Version Status-Code Reason-Phrase CRLF
headers CRLF
message-body
1、Status-Code:请求状态码,⽐如常见的 200表⽰成功、500表⽰服务器错误等
2、Reason-Phrase:状态描述,⽐如描述200时, 其内容为 ok
接下来改进我们的服务端程序,让其返回200给客户端, 表⽰请求成功。
let response ="HTTP/1.1 200 OK\r\n\r\n";
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
1、按照http的返回协议,我们需要定义返回的内容,暂时不返回body
HTTP/1.1 200 OK\r\n\r\n
2、定义了response变量存储我们的成功消息,调⽤as_bytes()函数将字符串转成字节,调⽤stream的write函数,将我们的字节数据传回给链接端,write函数的参数为&[u8]。
3、调⽤stream的flush函数,flush会阻塞程序直到将所有数据发送到链接端。
4、TcpStream包含⼀个内部缓冲区来最⼩化对底层操作系统的调⽤。
可以看到我们的请求返回了200 的成功状态。
返回我们的第⼀个html资源
总结⼀下前⽂学到的内容:我们搭建了⼀个http服务器,⽤于接收客户端的资源请求,同时响应客户端的请求时,服务端返回200的请求状态,虽然没有返回任何的body内容,但我们已经迈出了⼀⼤步,即实现了web应⽤交互流程。
接下来我们请求⼀个html资源,让我们的web应⽤更具⾁感。
html(HyperText Markup Language)超⽂本标记语⾔,是当前浏览器的语⾔标准了,当浏览器解析html时是在构建我们的冲浪环境,通过浏览器打开f12,你可以看到整个真实的世界,接下来我们将成为这个世界的创造者之⼀。
1、先创建⼀个web资源⽬录,添加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>
⽬录路径:web是独⽴的资源,区别于我们的web⼯程⽬录
2、改写handle_connection将html的内容作为response的body放回给请求端
let contents = fs::read_to_string("/usr/yangbin/workdir/rsproject/web/hello.html").unwrap();
let response = format!("HTTP/1.1 200 ok \r\nContent-Length: {} \r\n\r\n{} \r\n",
contents.len(),
contents
);
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
需要引⼊fs包
use std::fs;
使⽤fs函数read_to_string 读取hello.html的内容,采⽤format格式组装response。
需要注意的是response包采⽤\r\n进⾏分⾏,⽽body 需要两个\r\n 即空多⼀⾏。
Content-Length 表⽰body的⼤⼩,在这⾥即是hello.html的内容⼤⼩。
3、选择性响应
在完成读取html⽂件内容进⾏返回后,实际上还需要对请求做出判断,⽽不像现在这样⽆条件的返回。改写handle_connection,对请求的内容进⾏判断
fn handle_connection(mut stream: TcpStream){
let mut buffer =[0;1024];
let get = b"GET / HTTP/1.1 /r/n";
if buffer.starts_with(get){
let contents = fs::read_to_string("/usr/yangbin/workdir/rsproject/web/hello.html").unwrap();
let response = format!(
"HTTP/1.1 200 ok \r\nContent-Length: {} \r\n\r\n{} \r\n",
acceptlanguage
contents.len(),
contents
);
stream.write(response.as_bytes()).unwrap();
stream.flush().unwrap();
}
}
⾸先,定义变量get,因为buffer存储的是⼆进制内容,所以使⽤b来转换字符串"GET / HTTP/1.1 \r\n", 这个格式在讲解request时提到的,表⽰http协议的请求内容,当然了,uri / 可以换成 hello.
当客户端请求,符合get时,服务端才将hello.html的内容返回给客户端。
如果buffer中的内容不以get内容为开始,说明客户端请求的是其他的内容。通常在⽣产环境,http请求的内容取决于服务端提供的服务,⽽如果都通过if else 来匹配请求内容的话,逻辑块会冗长难维护,⽽现在成熟的web框架,⽐如java的 spring mvc 、go的beego等都会提供请求路由,我们这⾥不做扩展,只提供思路,⽐如当请求内容不存在时,服务端可以返回404以及提⽰页,那么两个请求的匹配只在于状态码和返回的内容,所以我们可以简单的封装下:

本文发布于:2024-09-23 04:19:20,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/1/371643.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:请求   内容   协议   客户端   返回   需要
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议