一、应用层协议
我们已经学过 TCP/IP
, 已然知道数据能从客户端进程经过路径选择跨网络传送到服务器端进程。
我们还需要知道的是,我们把数据从 A
端传送到 B
端, TCP/IP 解决的是顺丰的功能,而两端还要对数据进行加工处理或者使用,所以我们还需要一层协议,不关心通信细节,关心应用细节!这层协议叫做应用层协议。而应用是有不同的场景的,因此应用层协议经常是需要"自定义协议"的,通常情况下可以基于一些设计好的协议进行定制,HTTP
之所以应用特别广,主要原因就是可定制性特别强。
二、HTTP 报文格式
学习 HTTP 协议最主要的就是认识它的报文格式。我们可以通过抓包来获取到 HTTP 的报文格式,下面以浏览器访问百度时的请求响应为例进行抓包:
抓包原理:
抓包工具 相当于一个 "代理"。浏览器访问 www.baidu.com 时, 就会把 HTTP 请求先发给 抓包工具,抓包工具 再把请求转发给 百度 的服务器。当 百度 服务器返回数据时,抓包工具 拿到返回数据,再把数据交给浏览器。因此 抓包工具 对于浏览器和 百度 服务器之间交互的数据细节,都是非常清楚的。
HTTP 请求 抓包结果:
注解:
首行
: [方法] + [url] + [版本]Header
: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔空行
: 遇到空行表示Header部分结束Body
: 空行后面的内容都是Body。Body允许为空字符串,如果Body存在,则在Header中会有一个Content-Length属性来标识Body的长度
HTTP 响应 抓包结果:
注解:
首行
: [版本号] + [状态码] + [状态码解释]Header
: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔空行
: 遇到空行表示Header部分结束Body
: 空行后面的内容都是Body。Body允许为空字符串,如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度
HTTP 协议格式总结:
空行的作用:
HTTP 协议并没有规定报头部分的键值对有多少个,空行就相当于是 "报头的结束标记",或者是 "报头和正文之间的分隔符"。
三、HTTP 请求详解
1、首行
(1)请求方法:
请求方法 | 描述 |
---|---|
GET | 从服务器获取资源 |
POST | 向指定服务器资源提交数据进行处理请求(例如提交表单或上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。 |
HEAD | 类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头。 |
PUT | 从客户端向服务器传送的数据取代指定的文档的内容。 |
DELETE | 请求服务器删除指定的页面。 |
CONNECT | HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。 |
OPTIONS | 允许客户端查看服务器的性能。 |
TRACE | 回显服务器收到的请求,主要用于测试或诊断。 |
PATCH | 是对 PUT 方法的补充,用来对已知资源进行局部更新。 |
HTTP的请求方法,描述了这个HTTP请求想干什么。HTTP协议有很多的请求方法,不同的方法表示不同的语义。其中 GET
方法(表示从服务器获取资源)和 POST
方法(表示向服务器提交数据资源,登录、上传文件等场景下)是常用到的方法,而 GET 又是最常用的方法,至于其他方法我们很少会用得上。
因此有这样一句话:天下HTTP请求分十斗,GET请求独占八斗,POST请求占一斗,其他方法请求共占一斗。
GET 和 POST 的区别
虽然 GET
和 POST
表示不同语义,但实际上 HTTP 的语义只是一种建议,在实际使用的时候不一定非要遵守。因此 GET 和 POST 没有本质上的区别,使用GET的场景,替换换成POST也没有问题,使用POST的场景替换成GET也可以。但是二者在使用习惯上存在区别:
- 使用 GET 请求传参到后台时,传递的参数则显示在地址栏,安全性低,且参数的长度也有限制(2048字符);POST 请求则是将传递的参数放在 request body 中,不会在地址栏显示,安全性比 GET 请求高,且参数没有长度限制。(重要区别)
- GET习惯上用来表示 获取数据,POST 用来表示提交数据。
- GET一般没有 body,需要携带的数据通常放到URL中,POST一般有 body,将需要携带的数据放到 body 中。
- GET请求可以被浏览器收藏,POST请求不可以。(当我们收藏一个链接时,浏览器会记录该链接的 URL,GET 请求可以被浏览器收藏,因为请求参数包含在 URL 中,而 POST 请求不能被浏览器直接收藏,因为请求数据位于请求体中,无法完整保存在收藏夹中)
- GET请求通常设计出幂等的,POST 请求一般是不幂等的。(如果多次请求得到的结果一样, 就视为请 求是幂等的)
- 承接上述幂等性的前提下,GET 可以被缓存,POST 不能被缓存。
(2)URL
URL(Uniform Resource Locator)是统一资源定位符的缩写。它是用于标识和定位互联网上的资源(如网页、图像、视频等)的字符串格式。
例如:
java
https://cn.bing.com/videos/search?q=abcde&form=Z9LH1
- 协议:常见的有 http 和 https,也有其他的类型。(例如访问 mysql 时用的jdbc:mysql )
- 登陆信息(认证):现在的网站进行身份认证一般不再通过 URL 进行了,一般都会省略
- 服务器地址:此处是一个 "域名",域名会通过 DNS 系统解析成一个具体的 IP 地址(通过 ping 命令可以看到域名真实 IP 地址 )
- 端口号(可选):指定了与服务器建立连接时要使用的端口。当端口号省略的时候,浏览器会根据协议类型自动决定使用哪个端口。例如 http 协议默认使用 80 端口,https 协议默认使用 443 端口
- 带层次的文件路径:表示要访问的资源在服务器上的路径或文件信息
- 查询字符串(query string)(可选):对请求资源进行细节上的补充。本质是一个键值对结构,键值对之间使用& 分隔,键和值之间使用 = 分隔
- 片段标识(可选):片段标识主要用于页面内跳转
(3) 版本号
在HTTP协议的首行中还包含另一个字段,叫做"版本号"。
版本号是指明正在使用的HTTP协议版本的一部分。常见的HTTP协议版本包括
HTTP/1.0
、HTTP/1.1
、HTTP/2
、HTTP/3
等。它用于指示客户端和服务器之间所使用的协议版本,以确保双方能够正确解析和处理请求或响应。当前最主流的的版本是HTTP/1.1
,绝大部分互联网上的网站用的都是 HTTP/1.1 版本。
2、请求报头 Header
通过观察上面的抓包情况,可以看到,请求头是由键值对组成的。Header中的键值对大多都是HTTP协议规定的,当然也可以添加自定义键值对,这也是HTTP定制性强的体现。对于报头中的每一个键值结构都有自己的含义,下面介绍几种比较常见的键值结构:
(1)Host:表示服务器主机的地址和端口。
大多数情况下,Host中的值和URL中的域名是一致的。那么为什么还要存在这样一个键值结构呢?这就要谈到不一样的情况了,当我们访问服务器不是直接访问,而是通过代理访问,此时的Host和URL中的域名就不一样了。
(2)Content-Length:表示 body 中的数据长度(字节)。
Content-Length依赖于Body,如果一个请求没有Body,Header中就没有这个属性。
(3)Content-Type:表示请求的 body 中的数据格式。
Content-Type
同样依赖于 Body
,如果一个请求没有Body,Header 中就没有这个属性。
作为请求,Content-Type 使用最多的是下面 两种 格式:
1、
Content-Type: application/json;charset=UTF-8
这里表示数据是按照json格式组织的,并指定里数据的字符集为UTF-8
此时的body格式形如:
java
{"username":"xxxxx","password":"xxxxxxxx","uuid":"xxxxxxxxx","status":0}
2、
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
这里表示数据是按照表单提交的数据格式组织的,并指定里数据的字符集为UTF-8
此时的body格式和 query string
格式相同,形如:
java
username=tz&password=xxxxxxx&uuid=xxxxxxx&status=0
(4)User-Agent (简称 UA):表示浏览器/操作系统的属性
下面是我使用本机访问百度时的UA:
早期,由于同一时期的浏览器种类繁多,功能参差不齐,UA可以在请求中告诉服务器,当前的客户端种类,服务器可以根据客户端种类推送不同的页面。随着时间的推移,浏览器之间的差异越来越小,UA存在的价值也就大打折扣了。虽然它还可以用来区分用户使用的是手机、平板还是PC,但是"响应式布局"相比之下是一种更好的方案。
(5)Referer:表示这个页面是从哪个页面跳转过来的。
下面是我在百度主页访问百度贴吧时的抓包情况:
注意: 如果直接在浏览器中输入URL,或者直接通过收藏夹访问页面时是没有 Referer 的。(属于直接访问,没有经过跳转)
(6)Cookie 机制(重点)
上面是一段 Cookie 信息,虽然看起来"有亿点"扎眼,但也可以看的出 Cookie
也是由键值对组成,键和值之间使用=
分割,键值对之间使用;分割。至于这里的键值对都是程序眼自定义的数据,不同的网站有不同的键值对,也就有不同的含义和用途。
Cookie机制:是浏览器在本地存储用户自定义数据的一种关键机制。
浏览器自身也是要存储一些数据的,比如最典型的就是用户的身份信息。但是为了保证用户的上网安全,浏览器会禁止网页能够直接访问硬盘。因此引出了Cookie机制,允许网页通过浏览器提供的 API ,写入特定的文件中。
每个网站都存有自己的Cookie,Cookie是按照域名为维度进行存储的,通常同一个网站的主页、结果页、等子页面共享同一份Cookie。不同的网站则是各自享有各自的Cookie。
Cookie从哪里来?
Cookie从服务器来。当用户首次访问一个网站时,网站的服务器会在响应中包含一个Set-Cookie头部,该头部包含了一条或多条Cookie信息。浏览器接收到这些Cookie信息后,会使用Cookie机制,将其保存在用户的计算机或移动设备上。
Cookie到哪里去?
Cookie在浏览器只是暂存,真正发挥作用得由服务器来使用。当用户在浏览器中发起新的请求访问同一网站时,浏览器会自动将该网站关联的Cookie信息添加到请求的Cookie头部中,并发送给服务器。这样,服务器就能够根据Cookie中的信息对用户进行个性化处理。
Cookie有什么用?
Cookie是本地浏览器存储数据的机制。最典型的应用就是存储当前用户登录的身份标识,也称为令牌(token)。当然它存储的也不一定是身份标识信息,可以存储任何字符串数据。(Cookie存储空间有限,一般只有几k,所以不会存储较大的数据)
3、正文Body
正文中的内容格式和 header 中的 Content-Type 密切相关,这里就不过多赘述了,大家可以多抓包熟悉一下。
三、HTTP响应详解
对于HTTP响应来说,它的报文格式的绝大多数属性都和HTTP请求相同,下面我就秉承着"求同存异"的原则向大家介绍:
1、状态码与状态码描述
状态码:表示访问一个页面的结果。由三位数字组成,分为不同的类别,每个类别有特定的含义。
状态码描述:是对状态码的文字描述,用于更直观地说明该状态码所表示的含义。
但是对于HTTP提供的状态码种类是非常繁多的,我们这里不可能一一介绍,下面是几个常见的状态码及描述:
状态码 | 描述 | 含义 |
---|---|---|
200 | OK | 请求成功 |
404 | Not Found | 要访问的资源不存在 |
403 | Forbidden | 访问被拒绝(没有权限) |
500 | Internal Server Error | 服务器内部错误 |
504 | Gateway Timeout | 服务器访问超时(服务器迟迟未响应) |
301 | Moved Permanently | 临时重定向 |
302 | Moved Temporarily (Found) | 永久重定向 |
关于重定向: 重定向就是访问旧的地址,被自动引导到新的地址上。
虽然HTTP提供的状态码繁多,但我们可以根据状态码共性进行划分:
状态码 | 类别 | 含义 |
---|---|---|
1XX | Informational | 接收的请求正在处理 |
2XX | Success | 请求正常处理完毕 |
3XX | Redirection | 需要进行附加操作以完成请求 |
4XX | Client Error | 服务器无法处理请求 |
5XX | Server Error | 服务器处理请求出错 |
2、怎样构造HTTP请求?
这里介绍 五种 常用的HTTP请求构造方式:
(1)直接在浏览器地址栏输入URL构造出一个GET请求。
(2)HTML中的一些特殊标签也会触发GET请求。例如link
、script
、img
、a
(3)使用 form表单 可以触发GET和POST请求。
action:
构造的 HTTP 请求的 URL 是什么.method:
构造的 HTTP 请求的 方法 是 GET 还是 POST (form 只支持 GET 和 POST)
(4)使用AJAX可以构造任意HTTP请求。
AJAX 全称 Asynchronous JavaScript and XML 是一种用于在网页上进行异步数据交互的技术,也是目前最主流的前后端交互方式。
关于同步和异步我们可以这样理解:同步就是请求的发起者亲自拿到请求的结果;异步就是请求的发起者并不关心结果,当被请求的这一方计算出结果后,把结果推送给发起者。所以此时理解同步和异步,关键是看,结果是自己取还是别人送。
使用AJAX构造HTTP请求:
由于 JS 提供的原生 AJAX API 很难用,这里我们使用 jQuery
中提供的 AJAX API 进行操作:
javascript
$.ajax({
url:"https://www.baidu.com",// http请求中的url
type:"post",// 请求的方法,还可以是get、put、delete等
contentType:"text/plain",// body的数据类型,可以是application/json等
// 这是一个处理响应的回调函数
success: function(body) {
// 处理响应的代码
console.log(body);
}
});
上述过程中的回调函数会在服务器返回一个正确的响应的时候自动执行,这个过程就是"异步"的。
具体来说,在页面的JS中,把请求发送出去后就可以继续执行后续的代码了,直到正确的响应回来之后,浏览器会把这个响应给success回调函数,执行并处理响应的逻辑。
相比之下,ajax的功能更加丰富,但是也存在一个非常重要的问题------跨域问题
。
如果我们直接在本地执行上述代码,浏览器会报错:
虽然报错了,但这并不是代码的bug,而是浏览器引入的一种保护机制,为了防止a网站的页面请求b网站的数据。
此时运行的AJAX代码运行的页面的域名是null(因为是本地的HTML文件)请求的目标域名是www.baidu.com,当前二者域名不同,即使服务器返回响应,浏览器还是不能处理。
(5)使用现成工具构造HTTP请求,如Postman。
这种现成的工具有很多,大家可以根据自己的口味,自行选择,这里就不在过多介绍了。