HTTP协议详解

概述

        HTTP协议是一种应用非常广泛的应用层协议.我们日常使用的搜索引擎,就是借助HTTP协议来传输数据的.

        当我们在浏览器中输入一个 "网址" (URL) 时, 浏览器就给该网址对应的服务器发送了一个HTTP 请求, 该服务器处理请求后会返回一个HTTP 响应. 这个响应结果被浏览器解析之后, 就展示成我们看到的页面内容. (这个过程中浏览器可能会给服务器发 送多个 HTTP 请求, 服务器会对应返回多个响应, 这些响应里就包含了页面 HTML, CSS, JavaScript, 图片, 字体等信息).
        本篇博客将对HTTP协议的请求和响应进行详细剖析.

抓包工具

        为了了解HTTP协议请求和响应的内部结构,我们需要在网络传输中将它们捕获,这就需要用到抓包工具.笔者使用的是Fiddler.

        Fiddler相当于一个代理,当浏览器通过url访问目标服务器时,不再直接向目标服务器发送请求,而是改为向Fiddler发送,再由Fiddler将请求转发给目标服务器.同样的,当目标服务器处理完请求返回响应时,也不是直接返回给浏览器(客户端),而是先发给Fiddler,再由Fiddler转发给客户端.(这种过程与"中间人攻击"相类似,后者我们会在后面讲到)由此Fiddler就能获得请求和响应的具体内容.图示如下:

 HTTP请求

        我们以访问b站为例,来分析其HTTP请求的报文结构.其报文内容如下:

GET https://www.bilibili.com/ HTTP/1.1
Host: www.bilibili.com
Connection: keep-alive
sec-ch-ua: "Chromium";v="106", "Google Chrome";v="106", "Not;A=Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 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
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: buvid3=152B4688-F43C-6172-B8DF-61B5DE70DC2E58315infoc; b_nut=1663515558; i-wanna-go-back=-1; _uuid=64610ED32-9104F-6F8C-738C-D8B8362FC33158096infoc; buvid_fp_plain=undefined; DedeUserID=50995319; DedeUserID__ckMd5=6bc75f917182600b; rpdid=|(YYRmkR~uu0J'uYYJlulY|J; b_ut=5; hit-dyn-v2=1; nostalgia_conf=-1; LIVE_BUVID=AUTO3916635924052537; buvid4=C17D2DE1-EEC3-43EB-7CE9-0A2D4E82930059373-022091823-XlbirWgeNG0gDovnUOJ7%2FQ%3D%3D; CURRENT_QUALITY=0; fingerprint=264712f80d69491b690a39a41450f419; buvid_fp=264712f80d69491b690a39a41450f419; PVID=1; theme_style=light; SESSDATA=fecc8ea6%2C1681793335%2Cc60d0%2Aa1; bili_jct=401e4187351be13ca903a41e2bd06dde; sid=5ip2fnmo; CURRENT_FNVAL=4048; b_lsid=BB1618DF_183F42EC91E; innersign=0; bp_video_offset_50995319=719031590583271400

        首行

                HTTP请求中的第一行称为"首行",其由三部分组成,每部分由空格间隔开来.

GET https://www.bilibili.com/ HTTP/1.1

                第一部分"GET",我们称之为HTTP的方法(method).除GET之外,HTTP协议还内置了很多其他的方法.如"POST" "PUT" "HEAD"等等.但其中使用最普遍的还是"GET",其次是"POST".方法的作用在于明确一个HTTP请求的目的.但实际上,"GET"和"POST"并无本质上的区别.这部分会在后面详细讲到.

                第二部分"https://www.bilibili.com/"是客户端要请求的服务器的域名.相当于服务器在网络上的位置.

                第三部分"HTTP/1.1"是HTTP协议的版本号.不同的版本号,所支持的方法种类有所不同.比如"LINK"方法就只有1.0的HTTP协议版本才支持.1.1版本下不支持"LINK"方法.

        响应报头(Header)

                以下是响应报头部分:

                响应报头是键值对的结构,通常来说每一行是一个键值对.每个键与值之间用冒号+一个空格分隔.

Host: www.bilibili.com
Connection: keep-alive
sec-ch-ua: "Chromium";v="106", "Google Chrome";v="106", "Not;A=Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 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
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: buvid3=152B4688-F43C-6172-B8DF-61B5DE70DC2E58315infoc; b_nut=1663515558; i-wanna-go-back=-1; _uuid=64610ED32-9104F-6F8C-738C-D8B8362FC33158096infoc; buvid_fp_plain=undefined; DedeUserID=50995319; DedeUserID__ckMd5=6bc75f917182600b; rpdid=|(YYRmkR~uu0J'uYYJlulY|J; b_ut=5; hit-dyn-v2=1; nostalgia_conf=-1; LIVE_BUVID=AUTO3916635924052537; buvid4=C17D2DE1-EEC3-43EB-7CE9-0A2D4E82930059373-022091823-XlbirWgeNG0gDovnUOJ7%2FQ%3D%3D; CURRENT_QUALITY=0; fingerprint=264712f80d69491b690a39a41450f419; buvid_fp=264712f80d69491b690a39a41450f419; PVID=1; theme_style=light; SESSDATA=fecc8ea6%2C1681793335%2Cc60d0%2Aa1; bili_jct=401e4187351be13ca903a41e2bd06dde; sid=5ip2fnmo; CURRENT_FNVAL=4048; b_lsid=BB1618DF_183F42EC91E; innersign=0; bp_video_offset_50995319=719031590583271400

                 Host

                        Host表示访问的服务器的网络地址是什么.这个信息在URL中同样是有体现的.

                 Content-Length 和 Content-Type

                        这两个属性在上述的请求报文中并没有出现.这是因为这两个属性是要在请求报文中有body部分的时候才会有的(通常方法为"POST").如下报文中就体现出了这两个属性:

POST https://beacons.gcp.gvt2.com/domainreliability/upload HTTP/1.1
Host: beacons.gcp.gvt2.com
Connection: keep-alive
Content-Length: 637
Content-Type: application/json; charset=utf-8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

{"entries":[{"failure_data":{"custom_error":"net::ERR_ABORTED"},"http_response_code":200,"network_changed":false,"protocol":"SPDY","request_age_ms":1118624,"request_elapsed_ms":71,"sample_rate":1.0,"server_ip":"","status":"aborted","url":"https://beacons.gcp.gvt2.com/domainreliability/upload","was_proxied":false},{"failure_data":{"custom_error":"net::ERR_ABORTED"},"http_response_code":200,"network_changed":false,"protocol":"HTTP","request_age_ms":61713,"request_elapsed_ms":323,"sample_rate":1.0,"server_ip":"","status":"aborted","url":"https://beacons.gcp.gvt2.com/domainreliability/upload","was_proxied":true}],"reporter":"chrome"}

                        在这个"POST"请求的报头中,我们就可以看到有Content-Length和Content-Type这两个属性.其中Content-Length体现的是body部分的长度,这个属性的作用在于显示的指出了body在哪结束.如果没有Content-Length的话,则需要规定分隔符(如换行).而Content-Type则体现了body内的数据格式.上图中的数据格式是大家熟悉的json.

                  User-Agent(UA)

                          回到访问b站的数据请求报文.我们可以看到这样一行:

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36

                          这对键值对展示了客户端所使用的操作系统和浏览器的版本.其中"Windows NT 10.0; Win64; x64"表明了客户端是win10的64位系统."Chrome/106.0.0.0"则表明了客户端所用的浏览器版本."AppleWebKit/537.36"则表明了客户端所用的浏览器内核.

                          简单来说,UA的作用是告诉服务器我们在用一台什么样的设备访问它,从而根据客户端版本的新老返回不同的页面,服务更广泛的用户.但是随着网页兼容性的增强,UA现在的作用主要是区分访问设备是PC端还是移动端(根据操作系统内核判断),而不再是上述所说的了.

                   Referer

                           这个键值对表示了当前页面是从哪个页面跳转而来.并不是每个HTTP请求中都有Referer这一项,上述访问b站的HTTP请求就没有.我们抓一个有的,截取如下:

Referer: http://fiddler2.com/client/TELE/5.0.20211.51073

                            这是启动Fiddler时,截取自Fiddler自己发送的HTTP请求中的Referer.

                            Referer在实际中主要用于广告计费.很多公司或企业会在各大浏览器投放自己品牌的广告,支付一定量的广告费来换取知名度.而计算广告费就需要使用Referer.以百度为例,每当客户在百度的搜索界面点击了某一个广告,百度自己会记录该次点击.而广告商会根据Referer所对应的搜索引擎,同样进行计数(因为广告商可能同时在很多不同的浏览器上投放广告).最后将两个计数的数值进行比较,从而结算广告费.

                             早期的"运营商劫持",就有一种是通过篡改Referer来达到非法盈利的目的.但是这种行为在HTTPS协议问世后得到了限制.这里不再展开.

                    Cookie

                                Cookie是浏览器在本地存储数据的一种机制.通常来说,一个用户的相关数据信息都是在服务器端存储的.但是有些时候出于便捷考虑,我们希望也能在浏览器端这边也能存储一些数据.最典型的,就是用户的身份信息.

                                然而,浏览器为了保证用户的信息安全.在代码层面做出了诸多限制,最典型的就是禁止浏览器页面代码读取用户的本地磁盘.这是很有必要的,否则但凡点开一个恶意的页面,就可能导致本地磁盘数据的丢失.

                                为了平衡便捷与安全,浏览器做了某种程度上的"退让".不再是百分之百禁止使用磁盘,而是允许在限制条件下访问部分数据.此外,浏览器还限制了页面代码对获得的数据的存储方式----只能以键值对形式存储.所存储的这些键值对,我们就称之为Cookie.

                                我们可以直接在浏览器页面上找到Cookie.以Chrome浏览器为例,其页面左上方有一个锁头标志.

                                点击后会出现如下页面: 

                                 从这我们就能看到当前网页中的所有Cookie了.

                                 这其中,"名称"是键值对的"键".而"内容"是键值对的"值".键值对的含义通常只有开发者才知道.

                                我们假如把用户的磁盘的内容比作一个大房间.那么Cookie所占的部分可以当成一个小鞋柜.浏览器页面无法操作除了这个小鞋柜以外的磁盘资源.更重要的是,我们还依照域名将小鞋柜划分成不同的格子,不同域名下的Cookie是独立开来的,不能互相访问与修改.

                                我们已经明确了Cookie是浏览器允许页面在本地持久化存储数据的一种机制.我们还需要明确的是Cookie的内容从哪里来,又到哪里去.

                                由于用户的数据通常是存储在服务器上的,所以我们很自然的想到.Cookie的数据也是来源于服务器.当用户第一次访问某个服务器时,服务器在返回响应时,响应报文中会有"Set-Cookie"这样的键值对.这就是服务器在把某些数据传输给浏览器,浏览器会将"Set-Cookie"中的键值对以Cookie的形式存储在本地.下次在访问这个服务器时就可以直接把Cookie中的内容带上,从而能更容易的完成身份验证等工作.

                                我们举一个具体的例子来阐明这个过程:

                                以访问码云为例,我们先在码云的登陆界面删除掉所有的Cookie.然后登陆码云,所获得的HTTP请求是这样的:

                                 我们观察可以发现上述HTTP报头中并没有Cookie这一项.

                                 然后再来看看服务器发回的响应(仅截取部分):

                                 可以看到有Set-Cookie这样的键值对.这就是在把数据传输给服务器.

                                 当我们再次登录时,HTTP请求中就会带上上述服务器传过来的键值对了.如下:

                                  对比键值对内容可以发现.Cookie中的键值对与Set-Cookie中的键值对的内容是完全一致的.这就阐明了Cookie机制的来龙去脉.

空行

        空行,如其名.就是简单的空白行而已.其作用是将HTTP请求和响应的报头和正文部分间隔开来.否则报头的边界没有明确的定义,无法解析.

正文(Body)

        首先需要明确的一点是,并不是所有HTTP的请求都有正文.通常以POST方法开头的HTTP请求有正文部分.试举一例:

POST https://beacons.gcp.gvt2.com/domainreliability/upload HTTP/1.1
Host: beacons.gcp.gvt2.com
Connection: keep-alive
Content-Length: 637
Content-Type: application/json; charset=utf-8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

{"entries":[{"failure_data":{"custom_error":"net::ERR_ABORTED"},"http_response_code":200,"network_changed":false,"protocol":"SPDY","request_age_ms":1118624,"request_elapsed_ms":71,"sample_rate":1.0,"server_ip":"","status":"aborted","url":"https://beacons.gcp.gvt2.com/domainreliability/upload","was_proxied":false},{"failure_data":{"custom_error":"net::ERR_ABORTED"},"http_response_code":200,"network_changed":false,"protocol":"HTTP","request_age_ms":61713,"request_elapsed_ms":323,"sample_rate":1.0,"server_ip":"","status":"aborted","url":"https://beacons.gcp.gvt2.com/domainreliability/upload","was_proxied":true}],"reporter":"chrome"}

        此处可以注意到"Accept-Language"这个键值对下空了一行.然后又有新的内容,这部分内容就是正文(Body).此处body的内容是一些json格式的数据.但body中不仅可以是json,还可以是css,js,HTML等等.HTTP请求中body的内容一般是一些简单键值对,而响应中body的内容就多种多样,可以是一个完整的前端页面,也可以是一些简单数据.

请求的构造

        最简单也最基础的方式是直接在浏览器中输入URL,这就构建出了GET请求.而基于代码构造HTTP请求主要有两种方式:1.基于form标签 2.基于ajax

        form标签     

        使用form标签构造简单HTTP的请求的代码如下: 

<form action="https://www.sogou.com" method="get">
        <input type="text" name="username">
        <input type="submit">
</form>

        对于form标签,action属性表示该HTTP请求要访问的服务器域名.而method属性则表示构造出的HTTP请求所用的方法是什么.此处构造的是GET方法.

        在form标签内部中,第一个input的作用是构造query string参数.第二个input的作用是提交HTTP请求至服务器.如图:

         当我们点击提交按钮后,页面会跳转至搜狗主页.

        观察url一栏我们可以看见: 

https://www.sogou.com/?username=123

        "?"往后就是query string的内容.此处只有一组参数(因为我们代码中只写了一组).其中username就是我们第一个input标签name属性中的值.而"123"则是我们上面输入的内容.

        实际上,我们可以在submit按钮之前加入任意个input标签.每个标签最后都会成为query string中的一组参数.他们之间用"&"间隔开来.比如这样:

         此外需要注意的是,form标签仅支持GET和POST方法.对于其他HTTP协议下的方法,form标签是不支持的.这就造成了一定的局限性.

        ajax

                ajax全称"asynchronous javascript and xml".表面上看起来只能传输xml格式的数据,但实际上ajax能传输其他任何常见格式的数据.

                "asynchronous"意为异步.异步的含义是,当客户端给服务器发送请求时,无需自己获取结果,只需等待服务器返回响应即可.

                ajax是浏览器给JS提供的一个和服务器交互数据的机制.为此,浏览器提供了一组特定的API,称之为XMLHttpRequest.但是XMLHttpRequest使用起来非常不方便,所以,JS有很多第三方库对XMLHttpRequest进行了封装.其中最典型的就是jQuery.

                我们通常有两种方法将引入jQuery库.

                第一.直接在script标签中引入网络路径.

                 这种方法虽然简单,但是有一个缺陷.这个缺陷在于jQuery库是存储在国外的服务器上的.有些时候出于某些特殊原因,有可能访问不到jQuery,这就存在隐患.

                第二.将jQuery库整体拷贝到本地.我们可以在本地创建一个JS文件,然后将jQuery中的所有内容直接拷贝进去.这样就可以在本地直接访问,不存在网络访问不到的问题.

                 这里需要注意的是,此处的jQuery代码是经过特殊压缩处理的.所以只有一行.

                引入jQuery库之后,我们就可以使用ajax构造HTTP请求了.构造形式如下:

   <script src="jquery.min.js"></script>
   <script>
        $.ajax({
            type:'GET',
            url:'http://www.baidu.com',
            //data:'data对应的是正文(body)中的内容,此处没有'
            success:function(response){
                //此处的函数参数response表示HTTP响应的 正文部分
                //而此处的function则是一个回调函数 该函数什么时候被调用取决于
                //服务器什么时候返回响应
            }
        });
   </script>

        如上就是通过ajax构造HTTP请求的方法.但是如果仅仅只是这样写的话,是无法访问到百度主页的.会报出如下错误,我们可以在控制台中看到:

         这个报错是ajax中的典型问题----"跨域"问题.

         我们可以注意到当前网页的域名为:

file:///C:/Users/86136/Desktop/http/test.html

         这是一个本地路径.而ajax访问的是百度的服务器,这种行为称为"跨域访问".而浏览器为了保证网络安全,禁止了ajax的跨域访问.要想访问百度的服务器,需要百度允许才行.         

响应

        首行

                同样以b站返回的响应为例.其响应中的首行如下:

HTTP/1.1 200 OK

                与请求中的首行类似,响应中的首行被空格分成了三个部分.

                "HTTP/1.1".这部分与请求中一致,表示HTTP协议的版本号

                "200".表示状态码

                "OK".表示状态码描述.

                其中状态码和状态码描述是一组的.而"200 OK"是最常见的一组,表示访问成功.

                除此之外,还有一些其他的状态码和状态码描述.罗列如下:

                "404 Not Found".表示"没有找到资源".当我们输入的URL所对应的资源不存在时,就会返回这组数据.

                "403 Forbidden".表示"没有权限访问".通常出现于在没有登录的情况下访问某个需要登录的网页.

                "504 Gateway Timeout".表示"访问超时".通常出现于服务器负载比较大或者被墙的时候.这种情况下,服务器处理单条请求的时候消耗的时间会很长,就会出现超时.

                除了上述罗列的三种状态码与描述之外,还有很多其他的.但限于篇幅,不展开阐述.

        响应报头

                与请求中的报头一致,参上.

        空行

                与请求中的空行格式与作用都一致,参上.

        响应正文

                上面提到响应正文可以是一个完整的前端页面.这里展示b站服务器发回的HTTP响应中的正文部分:

                 此处只是局部,实际上还有JS代码部分等等.限于篇幅不再展示.

  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
HTTP 协议(Hypertext Transfer Protocol)是一种用于传输超文本的应用层协议,是万维网数据传输的基础。它采用客户端-服务器模式,客户端发起请求,服务器返回响应。下面对 HTTP 协议进行详细解析。 HTTP 协议以简洁的请求-响应模型为基础。客户端发送请求报文给服务器,报文包含请求方法、URL、协议版本等信息。服务器收到请求后,根据请求内容进行相应处理,并返回响应报文给客户端。响应报文包含协议版本、状态码、响应头和响应体等信息。客户端接收到响应后,根据状态码判断请求是否成功,并解析响应内容。 HTTP 协议的特点主要包括:无状态、可靠性差、传输效率低。无状态指的是服务器不会保存任何客户端的状态信息,每次请求都是单独的。可靠性差是因为 HTTP 使用 TCP 进行数据传输,TCP 协议本身也有一定的不可靠性。传输效率低是由于 HTTP 建立连接的开销较大,并且每次请求都需要重新建立连接。 HTTP 协议的工作流程如下:客户端发送一条请求到服务器服务器接收并解析请求,处理请求并生成响应,将响应发送给客户端,客户端接收并解析响应。 HTTP 协议的主要优点包括:易于使用、灵活性强、便于扩展。易于使用指的是 HTTP 的语法规则简单明了,易于理解和实现。灵活性强指的是可以通过设置请求头、传递参数等方式来定制请求。便于扩展指的是可以根据需要添加新的功能或特性。 总之,HTTP 协议作为互联网应用最常用的协议之一,它的设计简洁、易于使用,为用户提供了方便、快速的网络通信方式。同时,由于协议本身的一些限制,HTTP 协议的传输效率相对较低,因此在一些对效率要求较高的场景下,可能需要使用其他协议替代。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值