目录
[1. http 协议格式](#1. http 协议格式)
[1.1 http 请求](#1.1 http 请求)
[1.2 http 响应](#1.2 http 响应)
[2. http 请求](#2. http 请求)
[2.1 首行](#2.1 首行)
[2.1.1 URL](#2.1.1 URL)
[2.1.1.1 协议名称](#2.1.1.1 协议名称)
[2.1.1.2 服务器 IP 地址 / 域名](#2.1.1.2 服务器 IP 地址 / 域名)
[2.1.1.3 端口号](#2.1.1.3 端口号)
[2.1.1.4 层次结构的路径](#2.1.1.4 层次结构的路径)
[2.1.1.5 查询字符串 Query String](#2.1.1.5 查询字符串 Query String)
[2.1.1.5.1 URL encode](#2.1.1.5.1 URL encode)
[2.1.2 方法](#2.1.2 方法)
[2.1.2.1 GET 方法](#2.1.2.1 GET 方法)
[2.1.2.2 POST 方法](#2.1.2.2 POST 方法)
[2.1.2.3 GET 和 POST 的区别 【经典面试题 ★★★】](#2.1.2.3 GET 和 POST 的区别 【经典面试题 ★★★】)
[2.2 请求头 header](#2.2 请求头 header)
1. http 协议格式
http 协议是一个 "文本格式的协议", 我们可以通过抓包工具, 分析 http 请求/响应的格式.
1.1 http 请求
http 请求由以下四部分组成:
- 首行(请求方法 + URL + 版本号)
- 请求头(header)(键值对结构)
- 空行(用来标识 header 的结束)
- 正文(body) (有些请求没有正文(GET), 有些请求有正文(POST))
我们通过 fiddler 抓取 http请求 , 观察其具体结构:
其中, 请求头(header) 是键值对结构, 每一行是一个键值对, 键和值之间使用 冒号+空格 分割. 并且其中的键有哪些取值, 对应的值又有哪些取值, 都是由协议约定的.
1.2 http 响应
http 响应也是由四部分组成:
- 首行
- 响应头(header)(键值对结构)
- 空行 (标识 header 的结束)
- 正文(body)
我们通过 fiddler 抓取 http响应 , 观察其具体结构:
响应头(header) 依然是键值对的结构, 但需要注意的是, 响应头中的键值对和请求头中的键值对是有区别的(但是一些重要的键值对可能同时在请求和响应中存在).
2. http 请求
上文说到, http 请求由四个部分组成:
- 首行(请求方法 + URL + 版本号)
- 请求头(header)(键值对结构)
- 空行(用来标识 header 的结束)
- 正文(body) (有些请求没有正文(GET), 有些请求有正文(POST))
接下来, 一起聊一聊请求中, 每个部分的详细情况~~
2.1 首行
2.1.1 URL
URL 是首行中的一个重要组成部分.
URL( Uniform Resource Locators, 唯一资源定位符), 描述了网络上唯一资源的位置.
我们在学习 MySQL - jdbc 时, 创建 DataSource 时, 就需要设置 URL, 设置 user, 设置 password.
这里以 jdbc 时, 指定的 URL 为例, 介绍 URL 的组成部分:
可以观察到, URL 通常由以下几个部分组成:
- 协议名称
- 要访问的服务器的 IP 地址或者域名
- 端口号
- 带有层次结构的路径
- 查询字符串(Query String)
2.1.1.1 协议名称
URL 的开头, 会指定协议名称, 不同的协议, 协议名称是不同的. (这里需要明确, URL 不是 http 专属的概念, 是可以给各种协议提供支持的)
而当我们访问网页时, URL 就遵循 http 的协议进行传输:
2.1.1.2 服务器 IP 地址 / 域名
在 URL 中, 存在要访问的服务器的 IP 地址或者域名. 通过 IP 地址, 来确定要访问服务器的哪一台主机.
注意: **域名和 IP 地址是可以互相转换的.**这个转化的过程是通过 DNS 域名解析系统来完成的(DNS 既是一套服务器系统, 也是一种应用层协议)
有了 IP 地址, 为啥还要有域名呢?? 其实就是方便好记, IP 地址是一连串的数字, 而域名就是一个拼音(字符串的形式).
从某种意义上说, 我们就可以把域名当做为 IP 地址, 把 IP 地址当做域名.
2.1.1.3 端口号
通过 IP 地址可以确定要访问的服务器主机, 而通过 端口号就可以确定访问主机上的哪个应用程序.
但是观察 http 的 URL, 发现, 在 http 的 URL 中, 没有明确指明端口号.
这里需要注意, 没写端口号, 是因为浏览器会自动添加默认的端口号, 而默认添加的端口号是啥, 取决于协议是啥:
- http => 80
- https => 443
注意: URL 中的 IP 地址(域名) 和 端口号, 都是访问的远端服务器的IP 和端口, 而不是浏览器自身的 "客户端IP和端口".
也就是 五元组 中的 目的 IP 和 目的端口.
2.1.1.4 层次结构的路径
我们想访问的是 服务器中某个主机上某个程序管理的某个资源.
通过 IP 确定了哪台主机, 通过 端口号 确定了哪个应用程序, 而通过这个带有层次结构的路径, 就可以确定这个程序中某个具体的资源.
这个资源可以是一个硬盘上的文件, 也可以是虚拟的资源(通过代码生成的数据).
而为何是带有 "层次结构" 的路径呢?? 我们可以把每一层当做一个目录, 我们是一个目录一个目录的, 一层一层的拨开, 去寻找目标资源.
程序员可以根据需要灵活的自定义层次结构, 对资源进行的分类管理.
2.1.1.5 查询字符串 Query String
URL 中, ? 后面的内容就是查询字符串.
查询字符串对要访问的资源进行补充说明, 也是键值对的结构, 键和值之间通过 = 进行分割, 键值对和键值对间使用 & 进行分割. (键值对的内容都是程序员自定义的)
这里举个例子:
还是以老湿去陕科大六餐厅卖熏肉大饼为例, 老湿在餐厅的 18 号档口盘了一个位置.
不仅卖多种陷的熏肉大饼:
猪肉熏肉大饼, 牛肉熏肉大饼, 鸡肉熏肉大饼.
还卖多种粥:
八宝粥, 皮蛋瘦肉粥, 小米粥.
有一天我去老湿的档口买了一份猪肉的熏肉大饼, 那么向老湿发送的 URL 就如下所示:
查询字符串就是对我们要访问的资源, 做出的进一步的说明.
到这里为止, 上面介绍的 URL 是常见的 URL 的情况, 此外, 官方给出的 URL 的完整版如下:
仔细一看, 其实, 比我们上面说的 URL 多了两个信息:
- 登录认证信息
- 片段标识符
登录认证信息, 其实现在已经不会写到 URL 里了(单独的隐藏起来了).
片段标识符, 用来区分当前这个页面的哪个部分, 会出现在文档类页面中.(虽然还有, 但是也比较少见了)
2.1.1.5.1 URL encode
URL encode 就是把数据中的二进制数据取出来, 使用 十六进制 来表示, 每个字节前面加上 %
为什么要进行 URL encode 转义呢?? 因为在 URL 中, 存在一些特殊的字符, 如:
- 用来分割信息 /
- 用来表示端口号 :
- 用来表示查询字符串 ?
- 用来分割查询字符串中的键值对 &
但是, 要知道, query string 中的内容是由程序员自定义的, 万一 query string 中的内容含有以上的特殊字符该怎么办呢??
所以, 就需要对一些数据进行 URL encode 操作, 即转义操作.
转义操作, 不仅仅是对标点符号, 对于中文或者其他非英文系的字符, 也是需要进行转义的. (不能直接把 特殊符号或者非英文字符 放到 URL 中)
对于 encode 和 decode, 都是有一些现成的方法来直接调用完成的.
比如, 前端构造了一个 http 请求, 如果 query string 中涉及到了一些符号或者中文, 就需要调用 JS 的 urlencode 来进行转义.
等服务器收到了这个请求后, 就会调用一个方法来 decode(解转义) 一下.
如果不对 标点符号, 或者非英文字符进行转义, 可能就会使得 URL 解析失败, 进而使得请求失败.
举个例子:
- c++ 中的 + 是一个字符, 为了防止和 URL 本身的字符发生冲突, 就需要对 + 进行转义, 转义为 16 进制来表示;
- 若 query string 中出现中文, 同样是需要进行转义的, 转义成该中文字符的 utf8 编码.
2.1.2 方法
介绍完首行中的 URL 后, 接下来来聊一聊首行中的方法.
大家还记得首行中的方法吧:
首行中的 GET 就是方法.
方法 , 通俗来说, 就是描述这次的请求要干啥.
这么多的 http 方法中, 我们只需掌握 GET 和 POST 方法即可, 其中 GET 是最常用的方法. PUT 和 DELETE 方法了解即可.
GET 和 POST 是最常用的两个方法, 从语义上来说 ,GET 从服务器获取资源, POST 是向服务器方法数据, 但是在开发中, 这两个方法并没有严格的区分. (可以将 GET 当做 POST 使用, 也可以将 POST 当做 GET 使用~)
2.1.2.1 GET 方法
GET 方法的典型应用场景如下:
- 获取 html, 获取 css, 获取 js
我们在浏览器上打开一个网页, 大多数获取的都是一个 html:
但是如果想要在抓包工具上抓到的 css / js, 就需要对网页进行强制刷新(Ctrl + F5)操作:
为什么强制刷新才能抓到 css / js 呢??
这是因为浏览器的缓存机制. 浏览器为了加快页面的访问速度, 将 css / js / 图片 / 音频 ... 这样的静态资源缓存到了硬盘中. 如果不进行缓存, 那么每次访问都需要从远端的服务器中加载数据, 这样是很慢的. 而经过缓存后, 只需要从本地硬盘中加载数据即可.
而强制刷新操作, 可以忽略本地缓存, 所有资源都会重新从服务器获取.
注意: GET 方法是没有 正文(body) 部分的, 如果需要向服务器发送一些数据, 那么这些数据都会放在 query string 中.
2.1.2.2 POST 方法
POST 方法的典型应用场景如下:
- 登录
- 上传
对于 POST 请求来说, 请求是带有正文 body 的, 而正文的内容是就是当前上传的数据的内容.
比如, 我进行一次头像的修改, 这就是一个 POST 请求, 而这个请求中 body 的内容就是要上传的新图片.
(body 中既可以填二进制数据, 也可以填文本数据, 并且 二进制数据可以通过 base64 编码转成文本数据)
2.1.2.3 GET 和 POST 的区别 【经典面试题 ★★★】
首先, 先向面试官抛出结论: GET 和 POST 没有本质上的区别!!
其次, 从使用的习惯上来说, 最主要的区别有以下两点:
- 语义上的区别: GET 语义上是从服务器获取资源. 而 POST 语义上是向服务器发送资源. (但其实这两个可以混着用)
- 携带数据的方式不同: GET 通常是把数据放在查询字符串中(query string). 而 POST 通常是把数据放在正文中(body). (但其实 GET 也可以把数据放在 body中, POST 也可以把数据放在 query string 中).
此外,
- GET 请求通常建议设置成幂等的. 而 POST 则无要求. (仅为建议, 不是强制要求, 实际上, GET 不幂等的情况太常见了~)
- GET 被设计成幂等了, 那么就允许 GET 请求的结果被缓存. 而 POST 可能是不幂等的, 所以 POST 的结果不能被缓存.
幂等, 是计算机中的一个专业术语, 指请求一定时, 那么得到的响应也是一定的(请求不变, 响应也不会变).
举个例子, 比如奶牛, 第一天它吃的是草, 挤出来的是奶. 那么第二天, 它吃的也是草, 那么它挤出来的肯定还是奶. 这就是幂等. 但是, 如果它吃的是草, 但是第二天挤出来的是 祥 , 那就不是幂等了.
此外, 网上还有一些对 GET 和 POST 不同之处的讨论, 但是这些说法有待商榷:
- POST 比 GET 更安全
因为有人认为 GET 会把账户名和密码直接放在 query string 中, 就会显示在浏览器的地址栏上, 这是不安全的行为.
但是, 我的理解是, 如果仅仅这样就是不安全的话, 那 POST 也是不安全的呀, 只要我抓个包, 也能看见你 body 里面的内容.
所以, 保证安全的关键是能够做到 "加密传输", 只要是明文传输, 都谈不上安全.
- GET 的传输数据的长度有限制
其实这就是上古时期的说法了, 以前的 IE 浏览器年代, 对于 URL 的长度是有限制, 所以 query string 中不能放太多的数据, 如果太多就会被截断.
但是, 当前时代, 主流的浏览器对 URL 的长度都已经没有限制了.
- GET 只能传输文本, 而 POST 可以传输二进制
GET 确实只能传输文本(URL 的 query string 中只能放文本), 但是可以把二进制通过 base64 转换成文本.
此外, GET 也是可以带 body 的(将数据放到 body 中), 只是有些 客户端/服务器 不支持.
除了 GET 和 POST 这两个方法以外, 还有 PUT 和 DELETE 方法 可能会用到.
PUT 和 DELETE 在实现 Restful 风格的 api 的时候会用到(Restful 是定义服务器接口时的一种习惯):
- 新增: POST
- 删除: DELETE
- 修改: PUT
- 查询: GET
++END++