重新认识下HTTP本质

364 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第16天,点击查看活动详情

前言

在之前的篇章中我们介绍了同源策略以及跨域等知识,而这些知识主要是基于我们熟知的HTTP,我们只有认清HTTP的本质,在解决网络问题时才能得心应手。

当浏览器点击发送请求时,我们会觉得很神奇,甚至有的朋友十分抵触浏览器这个黑盒,一度认为HTTP既熟悉又陌生,我们敬畏知识,但是我们也可以从别的方面去认识它。

HTTP网络我们应遵守其规范,从本质上去理解规范。

规范我认为是官方制定的标准,我们需要去遵守,而本质是让我们更容易去认识新事物。

HTTP的本质

在浅谈http之前,我需要阐述一个观点:从本质来看,HTTP无非就是一串字符串,服务器接收到这个东西时也是字符串。

通过引入node的net模块,我们可以搭建一个简单的服务器。

const net = require('net')
const server = net.createServer()
server.listen(8080)

server.on('connection',socket=>{
    socket.on("data", chunk=>{
        console.log('chunk:',chunk) 
        //	请求过来的二进制buffer:(chunk: <Buffer 47 45 54 20 2f 66 61 76 69 63 6f 6e 2e 69 63 6f 20 48 54 54 50 2f 31 2e 31 0d 0a 48 6f 73 74 3a 20 6c 6f 63 61 6c 68 6f 73 74 3a 38 30 38 30 0d 0a 43 ... 540 more bytes>)
        
        console.log('chunk toString',chunk.toString())
		//转换编码格式,效果如下:
		//chunk toString GET /favicon.ico HTTP/1.1
		//Host: localhost:8080
		//Connection: keep-alive
		//sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"
		//sec-ch-ua-mobile: ?0
		//User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.84 Safari/537.36
		//sec-ch-ua-platform: "macOS"
		//Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
		//Sec-Fetch-Site: same-origin
		//Sec-Fetch-Mode: no-cors
		//Sec-Fetch-Dest: image
		//Referer: http://localhost:8080/
		//Accept-Encoding: gzip, deflate, br
		//Accept-Language: zh-CN,zh;q=0.9

        socket.end()
    })
})


无论是net模块还是http模块,都需要调用end方法,不然不会响应给客户端,示例代码之所以请求两次是因为浏览器多了一次自动请求网站图标favicon.ico的行为。

通过服务器的打印我们可以得知我们收到的结果无非是一串字符串,至于如何操作就由我们自行决定,在node模块里如果不进行数据转换,它收到的数据是一串Buffer,需要调用toString转换为utf-8格式的,这样就是我们熟悉的字符串格式。

响应给客户端的数据,我们可以在end之前调用write方法。需要注意,write的数据我们不能随便写,需要符合规范,这就涉及到http报文,报文作为http传输的一种规范,我们需要去遵守。

规范为什么需要遵守? 假如我说吃饭用“cf”表示,吃饱了用“cbl”表示,那么这就是我制定的规范,你们需要遵守,如果用了别的单词,我完全是不能理解你要表达的意思。而浏览器这些厂家就是规范的实现者,它们在内部的实现上遵守规范,不符合规范的他们可能就不处理了。

const net = require('net')
const server = net.createServer()
server.listen(8080)

server.on('connection',socket=>{
    socket.on("data", chunk=>{
    
    //书写自定义的报文格式,本质就是字符串!
    socket.write(`HTTP/1.1 200 OK

<h1>h1</h1>`)

    socket.end()
    })
})

截屏2022-06-12 下午11.25.59.png

我们再增加一下响应内容和响应体:

const net = require('net')

const server = net.createServer()

server.listen(8080)

server.on('connection', socket => {
    socket.on("data", chunk => {
        socket.write(`HTTP/1.0 200 KO
Set-Cookie:asd

<h1>h1</h1>`)

        console.log(chunk.toString())
        socket.end()
    })
})

截屏2022-06-12 下午11.29.02.png

我们发现了我们自定义的响应头展示在浏览器了,而浏览器也通过该字段把cookie设置在了当前页面上。 因为报文需要遵守一定的格式,对于换行符要求严格,如果代码格式化的话空格会影响到报文的正确格式。

至此我们对请求响应有了大概的认识,http层面我们是用字符串在服务端与客户端进行交流的,至于浏览器如何处理那就是浏览器的事情了,浏览器本身是规范的遵守者,所以其内部实现都会严格遵守规范。

HTTP报文小实验

我们可以用curl工具复制别的网站的响应报文,然后在服务器响应这样的报文。

截屏2022-06-12 下午11.30.24.png

const net = require('net')
const server = net.createServer()
server.listen(8080)

server.on('connection',socket=>{
socket.on("data", chunk=>{
socket.write(`HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Connection: keep-alive
Content-Length: 2381
Content-Type: text/html
Date: Sun, 27 Mar 2022 04:23:43 GMT
Etag: "588604eb-94d"
Last-Modified: Mon, 23 Jan 2017 13:28:11 GMT
Pragma: no-cache
Server: bfe/1.0.8.18
Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/

<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn"></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=http://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');</script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使用百度前必读</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a>&nbsp;京ICP证030173号&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>`)

socket.end()
})
})


截屏2022-06-12 下午11.32.39.png

我们来尝试传输自己的规范,我们管自己的规范叫做http4.0,成功响应码233,响应消息KO。(既然传送的报文是一串字符串,为什么就不可以传输自定义,数据肯定能到达终端,至于如何对数据进行处理,那就是浏览器内部自行处理的。)

const net = require('net')
const server = net.createServer()
server.listen(8080)

server.on('connection',socket=>{
socket.on("data", chunk=>{
socket.write(`HTTP/4.0 233 KO
Content-Type:application/json

<h1>h1</h1>`)
socket.end()
})
})


postman收到的响应:

截屏2022-06-12 下午11.35.15.png

curl工具获取响应报文:

截屏2022-06-12 下午11.36.36.png

然而浏览器还是可以正确处理:

截屏2022-06-12 下午11.37.20.png

我们可以看到,在浏览器通过content-type的值把响应的数据展示为json文本。而我们的HTTP/4.0并没有生效,这是因为浏览器内部根本不存在对该协议的判断,只会认为我们写错报文做了容错处理,改成目前通用的1.1版本协议。

通过几个小实验我们需要清楚的认识协议的本质,浏览器作为规范的践行者,它会根据我们的响应报文做相应的处理。而规范是官方指定的,至少在我认为,我们应该去熟记常见的规范,这样方便我们去理解浏览器对不同的响应做出的不同处理行为。

小结

HTTP的本质就是一串面向字节流的字符串,当我们认清他的本质时,可以结合其标准规范和报文格式去解决常见的网络问题。