浅沫记忆 发表于 2021-12-9 17:59:54

彻底搞懂HTTP协议 - 天天造轮子

你将Get的技能
文章转载:乐字节



[*]如何调戏百度服务器 - 用自己手写的HTTP协议
[*]如何调戏Chrome浏览器 - 用自己手写的HTTP协议
[*]了解HTTP协议与TCP协议的关系
[*]了解HTTP协议的发明的动机
[*]一个字一个字编写一个HTTP报文



Why
据说前端界有一个到非常有名的面试题叫做



输入一个url发生了什么


如果简单回答大概可以分为



[*] DNS解析
[*] TCP连接
[*] 发送HTTP请求
[*] 服务器 HTTP 应答
[*] 浏览器解析渲染
[*] 连接结束


等几个阶段,当然如果详细拆分每个阶段还会在再次被细分,所谓细节决定成败,你谈的细节越多就说明你的知识越系统。唬住唬不住就看你了,不过这确实是一道非常好的考题。
今天然叔只想谈谈其中的一环。
就是如何在TCP协议之上发送HTTP请求,以及如何应答HTTP请求。

游戏规则
实践才是检验真理的唯一标准。
那么怎么才能验证我们确实可以实现了HTTP协议呢?
我们知道HTTP协议分为Request 和 Response两部分
首先我们会使用 TCP 协议封装 HTTP协议. 通过以下两个标准验证实验是否成功。



[*] 【Request请求】的验证标准:   可以收到百度的应答
[*] 【Response响应】有效的标准:可以让Chrome浏览器访问



Where - 网络基础与TCP/IP
第一步我们要先找到我们应该在哪做这个游戏,要不然是不是就是神仙打架。
我们知道网络其实就是使用最少两根导线,将多个网络节点连接起来交换数据。
https://s8.51cto.com/images/blog/202112/07185657_61af3df9ad75283162.webp
https://s7.51cto.com/images/blog/202112/07185657_61af3df9b411674459.webp
可想而知,两个儿子还要打架,那么成千上万的计算机如果要保证他们不打架和平相处,就需要复杂的协议支撑。
https://s9.51cto.com/images/blog/202112/07185657_61af3df9725b987369.webp
在计算机世界中如果一个复杂问题通常的解决方式就是分层解决
https://s4.51cto.com/images/blog/202112/07185657_61af3df97e1fe43519.webp
https://s7.51cto.com/images/blog/202112/07185657_61af3df98926a48708.webp
其实这个就是OSI参考模型,而实际我们现在的互联网世界是就是这个理论模型的落地叫做TCP/IP协议
https://s7.51cto.com/images/blog/202112/07185657_61af3df97effc24246.webp
https://s7.51cto.com/images/blog/202112/07185657_61af3df9a04f088598.webp

What - TCP与HTTP是什么
什么是TCP通讯?
其实传输层有两种通讯方式分别是TCP和UDP。
两种协议都能够传输数据,区别主要是要不要提前建立连接 TCP就是需要建立连接的一个,好处在于通讯方式比较可靠。所以我们说TCP不丢包。
https://s3.51cto.com/images/blog/202112/07185657_61af3df9b36d989146.webp
但是UDP也不是没有用武之地,就比如说玩游戏 ,一技能没作用我再按一次就行了,所以延时小比可靠连接更重要,所以早期的游戏很多都看上了UDP协议。
https://s4.51cto.com/images/blog/202112/07185657_61af3df9ae82f23243.webp
对于一门高级编程语言来讲无论是(C++ , Java, JS)一般都是可以基于叫做socket的东西完成数据传输的。
TCP通讯程序
下面我们来个Node小例子。
Client
var net = require("net");var client = net.connect(3000, () => {console.log("连接到服务器!");});let n = 3;const interval = setInterval(() => {const msg = "Time " + new Date().getTime();console.log("客户端发送: " + msg);client.write(msg);if (n-- === 0) {    client.end();    clearInterval(interval);}}, 500);client.on("end", function () {console.log("断开与服务器的连接");});复制代码
Server
var net = require("net");var server = net.createServer((connection) => {console.log("client connected");connection.on("data", (data) => {    console.log("Server接收: " + data.toString());});connection.on("end", function () {    console.log("客户端关闭连接");});connection.end("Hello I am \r\n");});server.listen(3000, function () {console.log("server is listening at 3000");});复制代码https://s4.51cto.com/images/blog/202112/07185657_61af3df9c2c4899791.webp
为什么需要HTTP协议
既然上面我们已经知道了通过TCP可以收发数据,假设我们想做一个类似论坛BBS这样的需求怎么做。
https://s4.51cto.com/images/blog/202112/07185657_61af3df9cde7247965.webp
我们大体上可以把BBS服务器比作一个存储文本、图片、甚至声音、视频的图书馆。
用户如果想借书或者是还书都应该正确的填写借书单。这样才能保证存取有序。
https://s3.51cto.com/images/blog/202112/07185657_61af3df9df8e436570.webp
https://s6.51cto.com/images/blog/202112/07185657_61af3df9deac473917.webp
显然这种功能TCP协议并没有规定,TCP只是提供了交换数据的可能,相当于打开了借书小窗口。真正要完成借书还书还需要设计一个借书单。其实这个借书单就是HTTP协议。


超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。


下面我们先简单浏览一下HTTP协议你看看是否非常像一个借书单。
https://s9.51cto.com/images/blog/202112/07185657_61af3df9e6d7194156.webp
HTTP协议规则
下面我们来细致讲解HTTP协议
要想看到HTTP报文长什么样子,可以使用curl命令
https://s5.51cto.com/images/blog/202112/07185657_61af3df9efc8633273.webp
其实HTTP报文就是一个文本,这里面使用分隔符比如空格、回车、换行符来区分他的不同部分。
https://s7.51cto.com/images/blog/202112/07185658_61af3dfa02fe079105.webp
解析HTTP报文
下面我们着手去用代码解析一个HTTP报文。
第一步 拆分请求行、头部、请求体



[*]请求行: 就是第一行 -第一个回车符和换行符前的字符都是请求行
[*]头部: 请求行之后一直到遇到一个空行 -- 其实就是遇到两个连续的回车符和换行符
[*]请求体: 剩下的部分


private parse(): void {    this.httpMessage = {} as HttpMessage;    const messages = this.message.split('\r\n');    const = messages;    const headers = messages.slice(1, -2);    const = messages.slice(-1);    this.parseHead(head);    this.parseHeaders(headers);    this.parseBody(body);}复制代码第二步 解析请求行
请求结构就是: 请求方法 + 【空格】+ URL+【空格】+ 版本号
private parseHead(headStr) {    const = headStr.split(' ');    this.httpMessage.method = method;    this.httpMessage.url = url;    this.httpMessage.version = version;}复制代码
第三步 解析头部
头部的结构:
KEY_A : VALUE
KEY_A : VALUE
KEY_C : VALUE
function parseHeaders(headers) {    this.httpMessage.headers = {};    for (let i = 0; i < headers.length; i++) {      const header = headers;      const = header.split(":");      key = key.toLocaleLowerCase();      value = value.trim();      this.httpMessage.headers = value;    }}复制代码
请求体
请求体就是剩下的部分无需解析
拼装HTTP响应
拼装的过程其实就是将整个过程反过来进行
function format() {const head = `${this.version} ${this.status} ${this.message}`;let headers = '';for (let key in this.headers) {const value = this.headers;headers += `${key.toLocaleLowerCase()}: ${value}\r\n`;}const combineData = .join('\r\n');return combineData;}复制代码

实现HTTP爬虫访问百度首页
下面利用刚才写好的HTTP函数拼装一个报文调戏一下【 百度 】
const net = require("net");const createFormater = require("./http/formater");const formater = createFormater("request");const req = {method: "GET",url: "/",version: "HTTP/1.1",headers: { "user-agent": "curl/7.71.1", accept: "*/*" },body: "",};console.log(formater.format(req))const client = net.connect(80, "www.baidu.com", () => {console.log("连接到服务器!");client.write(formater.format(req));});client.on("data", function (data) {console.log(data.toString());client.end();});client.on("end", function () {console.log("断开与服务器的连接");});复制代码
大家注意这段程序并没有用http协议 ,而只是向百度发送了一个tcp请求,使用的报文也是刚才我自己实现的。结果百度服务器真的应答了。调戏成功,说明我们的HTTP协议实现的不错。
https://s9.51cto.com/images/blog/202112/07185658_61af3dfa0b23653897.webp

实现能被Chrome访问的HTTP服务器
下面我们再来试试这个程序是否能够经受住chrome的考验
const net = require("net");const createFormater = require("./http/formater");const formater = createFormater("response");const res = {version: "HTTP/1.1",status: "200",message: "OK",headers: {    date: "Sat, 04 Dec 2021 14",    connection: "keep-alive",    "keep-alive": "timeout=5",    // "content-length": "19",},body: "<h1> Hello HTTP<h1>",};const server = net.createServer(function (connection) {console.log("client connected");connection.on("data", (data) => {    console.log(data.toString());});connection.on("end", function () {    console.log("客户端关闭连接");});connection.end(formater.format(res));});server.listen(3000, function () {console.log("server is listening");});复制代码https://s9.51cto.com/images/blog/202112/07185658_61af3dfa37d6d9641.webp
服务器发送请求后向浏览器发送了自己组装的应答,浏览器正确渲染的页面。这个实验也可以认为是成功的。

总结回顾
目前虽然简单的实现了HTTP协议但是还很初级,后续还会补充



[*]图片、视频、音频数据
[*]cookie-session鉴权
[*]缓存实现
[*]分包上传
[*]管线化
[*]Http2.0
[*]Https 与RSA证书


我们都会一一实现
文章转载:乐字节


https://blog.51cto.com/u_15320542/4762687
页: [1]
查看完整版本: 彻底搞懂HTTP协议 - 天天造轮子