1.初识http协议
http 协议是一个 应用层协议。同时它在 **传输层 **是基于 TCP协议 的
(http1和http2 是基于 TCP 的,http3则是基于 UDP 的,并且现在使用最多的还是 http1.1 这个版本)
只不过 TCP 协议是关注 网络中 两个节点 之间的数据传输,更注重 可靠性。
http 协议 则是注重 对传输的数据 怎么样 具体的使用。
2.http协议格式
协议格式就是用来描述 数据具体的组织形式 。
在之前我们了解了UDP、TCP、IP协议,知道它们都是属于 "二进制" 的协议,所以我们经常需要理解二进制bit位。但是 http协议不同, http协议是一个文本格式的协议,所以它对于人更加的 "友好"。
1.抓包工具
在介绍 http协议 之前我们需要知道一个东西,叫"抓包工具"。通过它我们就能更好的来理解 http 协议的格式。
-
什么叫抓包工具?
我们可以理解为一个拥有 代理 功能的第三方应用程序。
举个例子:实体服装店 和 服装生产厂
在生活中有一些小的服装实体店,它们进货往往并不是直接从厂商那里拿。而是通过一个 "档口" 来拿货。也就是说 实体店和服装生产厂 之间的通信需要经过 这个档口来完成。
这个 "档口" 就类似于一个 抓包工具。它可以获取到双方通信的具体信息。
在这里我们就是用 fiddler,这是一个专门用来抓 http 的抓包工具,在官网上就能下载。
这就是 fiddler 应用程序的界面。这里大致分为了两个部分:
左边是当前抓到的 http 和 https 的数据报。右边则是用来显示 http/https数据报的具体内容。
同时右边也分为了两个大块,上面是 请求(request);下面是 响应(response)。
2.http的请求格式
这里我们就通过 fiddler 抓取的 搜狗首页 的数据报来进行介绍。
我们会将上面的 http 请求 分成 四个部分。
1.请求行
第一行我们就称为 请求行,它也分成了三个部分,每个部分之间使用 空格 分割。
- Method:请求的方法(get、post、put等,现在基本上只会使用前两种),描述了这个请求想干啥。
- URL:描述了这个网络资源具体在哪儿。
- Version:http协议版本号(图上就是http/1.1版本,这也是当下最流行的版本)。
2.请求头
它包含了许多行,每一行都是一个键值对的结构。
在上图中除了第一行(请求行)外,下面有字符的都是请求头的数据。
里面具体的内容信息后面进行介绍。
3.空行
这个就相当于 请求头 的结束标志。这里面啥都没有。
4.请求正文(body)
这一部分,它可以有,也可以没有。
(上图中就是一个没有 body 的http请求)
这里我们就可以找一个有 body 的 http请求:
这里我们就可以很明显的看到 body 这个部分有具体的数据。
3.http的响应格式
这里我们还是使用 搜狗首页 来进行介绍。
同样的这里也是分为了 四个部分。
1.响应行
也是上面的第一行,并且它也是分为了 三个 部分,中间使用 空格 分割。
- Version: http响应的协议版本号。
- 状态码:描述了当前http响应的具体状态(成功还是失败)。
- 对状态码的描述:对前面的状态码的具体描述,一个详细的信息,类似于解释说明。
2.响应头
这里的响应头也是有许多行。和 body 一样使用空行分割。
也是使用的 键值对 的结构。
3.空行
响应头的结束标志,其中没有具体内容。
4.响应正文(body)
服务器 给 浏览器 返回的具体数据。
这里会有许多不同的格式,其中 html 是最常见的格式。
4.http请求详细介绍
这里主要就是对上面 http请求数据格式 的一些具体介绍。
1.URL
URL 含义是 "网络上唯一资源的地址符"。它描述了一个资源在网络中的具体位置。
URL的结构大致如下:
-
协议方案名:这个描述了当前这个 URL 是给哪种协议使用的(http:// https:// jdbc:mysql://)。
-
登录信息:这个主要是以前用来描述用户名和密码的,现在基本没有使用了(都省略了)。
-
服务器地址:这个很好理解,其实就是 IP 地址,但是这里往往并不是直接写 IP 地址,而是 域名。
-
服务器端口号:描述了主机上的哪个端口,这个现在也大都省略,使用默认的端口(http默认80,https默认443)
-
带层次的文件路径:描述了这个网络资源具体是啥。比如 快递的订单号就能找到具体的快递。
-
查询字符串:这个是 客户端/浏览器 给 服务器 传递的 自定义 信息。就相当于对获取的资源有了进一步的要求。本质上就是一个字符串结构,具体的则由程序员来规定。并且查询字符串与文件路径之间使用 ?分割。每个键值对之间则由 & 来进行分割,键值对的结果为:key = value。
-
片段标识符:描述了要访问当前 html 页面上的哪个子部分,使浏览器滚动到该位置(也不经常见到)。
2.URL encode/decode
当 查询字符串 中有特殊字符时,我们就需要先进行 转义,这个转义的过程就叫 urlencode,而将 转义 后的字符还原回来的过程就是 urldecode。
具体例子:
在这里我们可以看到,如果百度的搜索框输入 c++,点击搜索之后,在 url 中就有一个键值对:wd=c%2B%2B。
其中 wd 就表示当前搜索框里的关键词。那么由此可以推出 后面 的 %2B%2B 就是 ++。也就是说 + 等于 %2B。
现在我们分析一下 + 为何是 %2B:
我们首先查看一下 + 号的 ASCII 码值,是 43(十进制)。
然后将其转换为 二进制 数为 :0010 1011。
接着我们将每四位二进制分成一部分,并将其转化为 16进制数:2B。
最后在前面添加上一个 % 号,就成了 %2B。
所以转换规则就是如上所示:将其的ASCII码值使用 16进制表示,并在前面添加上%。
3.http请求(方法)
在上面的介绍中我们已经知道了有 get 和 post 方法。其实除了这些外,还有许多其他的方法,比如 put、options等。但是现在基本都是使用 get 和 post 这两种方法了,所以其他的可以不做详细考虑。
1. get 和 post 有什么区别?(重要)
首先 get 和 post 没有本质区别,get 能使用的场景 post 可以进行替换,同样 post 的使用场景 get 也可以替换。
但是 get 和 post 有一些细节上的区别:
1.get 和 post 的语义不同,get 通常表示从服务器获取数据,post 通常表示上传数据到服务器。
2.通常情况下,get 是没有 body 的,数据是通过 QueryString 来传输。
通常情况下,post 是没有 QueryString 的,它的数据是通过 body 来传输。
但这不是强制的,我们可以手动的给 get 加上 body,也可以给 post 加上 QueryString。
3.get一般是幂等的,post一般不是幂等的。
幂等:相同的数据输入,得到的输出结果是一致的。
不幂等:相同的数据输入,得到的输入却是不一样的。
4.get一般可以被缓存,post一般不能被缓存。
这也是和 幂等不幂等 有关,幂等的才能被缓存,不幂等的不能被缓存。
通过能不能缓存就可以减小服务器的开销。
2.补充说明(get 和 post 错误的说法)
-
- get 传输数据有上限,post 没有上限。这是错误的,因为 http 标准并没有说 get 请求的 URL 有上限,post 请求的 body 有上限。出现这种说法的原因是由于以前的浏览器在实现时并没有完全遵守 http 协议标准来。
-
- get 传输数据 不安全,post 安全。这也是错误的,因为传输数据安不安全取决于是否对数据进行了加密。如果都是明文传输,那都不安全。
4.http请求头(header)
我们知道在 header 里有许多的 键值对,并且里面的键值对所表示的含义也不同。下面就来介绍一部分:
1.host
这个表示主机的 IP 地址和 端口号。
2.Content-Length
这个表示 body 中数据的长度。单位:字节
1. Unicode和GBK编码
GBK:一个汉字占2个字节,一个英文字符占1个字节。
UTF-8:
在 java标准中,内码 使用的是 UTF-16编码方式(现在也变成了 变长编码,可能是 2 个字节 或 4个字节),占2个字节(char类型)。
UTF-8也是一种 变长编码:它可以是 1、2、3、4 种字节。
如果在 UTF-8 中如果使用的是 1个字节,那么返回就是 0-127 ,这正好是 ASCII 码 的范围。
所以 UTF-8涵盖了 ASCII码,故 一个 汉字 使用 UTF-8 的方式,它可能是 2、3、4 个字节(现在记录的大部分常用字都只占到了 3 个字节)。
所以,我们在使用一些代码验证 UTF-8编码 的使用,都是 3 个字节。
总结:UTF-8 还是可以说 中文占3个字节,英文字符肯定占 1 个字节
3.Content-Type
这个表示 body 中数据的组织格式。
有三个常见的值:
-
application/x-www-form-urlencoded :form表单中提交数据的格式。
name=zhangsan&id=123
-
multipart/form-data :form表单中提交图片/文件的数据格式。
-
application/json :json数据格式。
{ "userName" : "张三", "userId" : "123", }
补充:粘包问题
我们在学习 网络原理 时,就介绍过 粘包问题,知道这是由于 面向字节流 而产生的。在这里我们知道了 http 协议是基于 TCP 协议的。而 TCP协议 就是 面向字节流的。这时如果在 应用层 不做处理,就会出现粘包问题。但是当介绍了 Content-Length 和 空行,之后。我们就可以发现 http 协议就基本给我们解决了这个问题。
为什么?
假如是 get 方法产生的 http 数据报,这里我们就可以通过 空行 来明确 数据报与数据报之间的界限。
假如是 post 方法产生的 http 数据报,这里我们可以通过 Content-Length 来明确每个数据报的body的范围。
4.User-Agent
这是用来描述 发送请求方 的 操作系统信息 和 浏览器信息。(就相当于 自报家门)
(当然由于现在的浏览器差别不大,所以 User-Agent 就主要用来区分请求的发送方是 电脑 还是 手机。)
5. Referer
这个就比较好理解,它是用来描述我们当前这个页面是由哪里跳转过来的(它并不一定有)。
6. Cookie(重要)
首先我们要知道,浏览器为了保障我们电脑的安全,所以它并不允许页面上的 JS 代码直接访问我们主机上的 "文件系统"
虽然这样安全了,但是同样也带来了麻烦。那就是有时候我们也需要 浏览器 将一些信息 给缓存到本地文件中。这样再次访问同一个网站的其他页面时,就不需要重复的进行身份认证了。
举个例子:
在我们需要向 gtee(码云)上提交代码时,在第一次访问就需要进行 用户登录操作,这就相当于身份认证。当我们登录成功,也就是完成认证之后。再次访问里面的其他页面时,比如:仓库、代码片段、个人设置等。就不需要重复进行登录操作。
所以就有了这样一个解决办法:单独给浏览器分配了一个小房间(也就是cookie),在这个房间里浏览器就可以进行访问。
总结:Cookie就是浏览器给页面提供了一个能持久化存储数据的机制。
Cookie 的组织形式?
- Cookie是按照域名来进行组织的,也就是一个域名对应一个小房间。
- 每个小房间里又是使用 键值对 的结构来进行组织数据。
Cookie的数据从哪儿来?
这个当然就是从服务器返回回来的。当服务器完成了身份认证之后,就会返回一个身份给浏览器缓存起来。
如图:
7.Session(重要)
在上面我们知道了 Cookie 就是用来存放用户的一些相关信息的。
但是这里就会发现出现一些问题,那就是 Cookie 是存放在 浏览器 本地的,也就是说 它 可以手动删除(不安全)。并且加上Cookie 里面也存放不了太多的东西(容量小)。
所以我们就干脆在 服务器 上也开个小房间(也就是 Session),重要的信息都放在 服务器上。同时每个 Session 都有一个 id(会话id),所以我们只需要 Cookie 上保存所对应的 Session 的 id 就行了。
在访问的请求中带上 Session 的 id 即可。
5.http响应详细介绍
在上面的内容中我们详细介绍了 http 请求 中的一些具体内容,接下来我们就来介绍一下 http 响应 中的具体内容。
1.状态码
我们知道在 响应行 中有三个部分:协议版本号 、状态码、状态码描述。
接下来我们就详细介绍一些状态码及其表示的含义:
1. 200 OK
表示浏览器很顺利的拿到了想要的数据。
2. 404 Not Fount
表示所访问的资源不存在。
3. 403 Forbidden
这个表示没有权限访问这个资源。
4. 405 Method Not Allowed
这个表示方法(method)不允许。
比如:使用 get 访问 只支持 post 的服务器 就会出现这种情况。
5. 500 Internal Server Error
这个表示服务器自己出错了。也就是服务器 出现 BUG 了。
6. 504 Gateway Timeout
这个表示服务器太繁忙了。它现在没时间处理这个请求。
7. 302 Move temporarily
这个是 重定向,出现了这个 状态码,就表示马山会重定向到其他的页面。
它通常与 Location 这个响应头来配和。
Location 就表示 重定向 的目的地。
8 . 301 Moved Permanently
这个和 302 差不多,只不过这个 301 是 永久重定向。
9. 状态码总结
1 开头,通常表示指定客户端相应的某些动作。
2 开头,通常表示成功。
3 开头,通常表示重定向。
4 开头,通常表示客户端出现错误。
5 开头,通常表示服务器出现错误。
10. 418 (特殊的状态码)
描述:I am a teapot 我是一个茶壶。
这就是一个 彩蛋。没有什么实际意义。
2. http响应头(header)
这个里面基本和 http请求头 里的含义一样。
1. Content-Type(和 请求 有所不同)
这个和 请求 里的取值有很大不同。
- text/html :body中的数据格式是 html。
- text/css :body中的数据格式是 css。
- application/JavaScript :body中的数据格式是 JavaScript。
- application/json :body中的数据格式是 json。
3. http请求的构建
总体分为两种方式:html/JS(form表单、Ajax) 和 java(socket)
1. 使用form表单(会"页面跳转")
构建一个 get 请求:
<body>
<form action="https://www.sogou.com/index.html" method="get">
<input type="text" name="username">
<input type="password" name="password">
<input type="submit" value="提交">
</form>
</body>
抓包结果:
构建一个 post 请求:
<body>
<form action="https://www.sogou.com/index.html" method="post">
<input type="text" name="username">
<input type="password" name="password">
<input type="submit" value="提交">
</form>
</body>
抓包结果:
1. 同步和异步(重要)
同步又分为:阻塞式的等 和 非阻塞式的等
举例说明:
-
同步阻塞式等待:
小明周末叫小刚一起去打球,小刚答应了。小明来到小刚楼下叫他,小刚说"马上就来"。这时小明是调用者,小刚是被调用者。并且小明就一直在楼下等,不做其他的事。也就是说调用者会主动获取被调用者的结果。
-
同步非阻塞式等待:
这个就是,小明先来小刚的楼下叫了他一声。然后就去做其他的事,过了一段时间之后,就又来到小刚的楼下叫小刚快来。也就是说调用者不会一直等被调用者的结果,而是每隔一段时间再来尝试主动获取结果。
-
异步等待:
还是上面这个例子,在这里小明只会在第一次来到小刚的楼下时,叫他一声。然后就去做其他的事,知道小刚出来之后,小刚再主动联系小明,他下来了。小明才会再次来到楼下。也就是说调用者在第一次调用被调用者后,就会去做其他的事。直到被调用者有了结果,再来通知调用者。
-
总结:方式2和方式3都可以在等待的过程中做其他的工作。但是相比于方式3,方式2需要反复查询结果,所以开销更大。Ajax就是使用的 异步等待 的方式。Scanner就是使用的 同步阻塞等待。
2. 使用 Ajax (不会出现页面跳转)
<body>
<script src="jQuery.js"></script>
<script>
// $ 也是一个变量名,同时它也是 jQuery中最核心的对象,API都是通过$来调用
// ajax方法的参数是一个 js对象。
$.ajax({
// 这是一个get类型的请求
type:'get',
url:'https://www.sogou.com/index.html',
success:function(body){
//这里是一个回调函数。
//请求成功就会执行这里。
console.log(body);
},
error:function(body){
// 失败后就会执行这里。
console.log('失败');
},
});
</script>
</body>
注意:这里就会出现一个问题:浏览器 不允许 ajax 跨域访问。
3. 使用 java (socket)
这个我们并不常用,所以这里就介绍一下原理。
其实就是通过 字符串 拼接,构造一个符合 http请求格式 的字符串,然后基于 TCP socket 来进行写入即可。
4. https
在上面我们具体了解了 http协议。接下来我们就来了解下 https协议。
首先,http 和 https 基本相同,不同之处就是 https 引入了加密层。
-
为什么要加密?
http 使用的是 明文 传输,这样就使得请求或者响应很容易被 篡改 。就会变得不安全。
所以 https 里面就引入了 加密层(称为 SSL/TLS),它主要的目的就是 防篡改。
SSL是以前的叫法,升级之后现在的叫法就变成了 TLS。不过基本就是一个东西。
1. SSL
在 SSL 中,加密的方式有两种:对称加密 和 非对称加密。
接下来就详细介绍一下:
1. 对称加密
这个就是 客户端 和 服务器 同时 持有一个 密钥。然后相互传输的 明文 就可以通过 密钥 进行加密生成 密文,然后传输的就是 密文 ,接收方收到 密文 之后,就又可以 通过 密钥 来进行 解析 ,得到 明文 。
感觉上面的过程很美好,其实这里有个问题:如何保证 客户端 和 服务器 的 密钥 一致?
有两种方式:
- 所有的客户端的密钥都是同一个(不行,这样黑客自己随便启动一个客户端,就能拿到这个密钥了)。
- 每个客户端的密钥都不同(又有了一个问题:生成密钥的一方怎么将这个密钥告知另一方)。
解决办法:就是对密钥也进行加密。
总结:对称加密有一个问题,无法对密钥进行加密。
2. 非对称加密
- 所以在这里就引入了 非对称加密,这个主要就是对 对称密钥 进行加密。**
(非对称加密 的开销 太大,所以这里就只是使用 它 传输 对称密钥,最终还是通过 对称密钥 来进行加密数据内容。)
对称加密 有两个 密钥:公钥 和 私钥。
一般情况下 公钥 进行 加密 ,私钥 用来 解密。所以我们在 传输 时,传输的是 公钥,也不怕被 黑客 给拿到。
在上述的过程中,我们感觉已经很美好了。但是其中还有一个 致命缺陷。就是 黑客可以 自己生成一个 公钥 和 私钥 ,然后 篡改 服务器 的公钥 ,将自己的公钥发送给 客户端。这时客户端就会以为这是 服务器 的 公钥,使用其对 对称密钥 进行加密 ,然后返回。黑客入侵的设备拿到返回时 密文 之后,就可以使用 自己 的私钥进行解密。就能得到 对称密钥 了。最后黑客再使用 服务器 的公钥 对 对称密钥 进行加密,将其结果密文 返回给 服务器。最终就神不知鬼不觉的拿到 对称密钥。 — 中间人攻击
3. 中间人攻击
从上面的分析中,我们就可以发现上述的过程还是存在缺陷,如果出现 "中间人攻击",那么黑客一样可以拿到我们的 对称密钥。
这么解决?
答:通过一个 公信机构 来证明 公钥 的出生身份。
这样就变成了,在服务器上线的时候就需要先在 公信机构 里获取一张 证书,证书里包含了一些相关的身份证明,其中 公钥 就包含在里面。
然后在传输的过程中,并不是只传输一个 公钥,而是将证书整个传输过去。
当客户端收到证书后,它就可以通过 公信机构 来证明 这个 证书 的真实性。从而能及时发觉到 "中间人攻击"。
- 1.通过证书本身的格式规范来进行判断。
- 2.通过向公信机构求证来判断。
当然这里也有一个要注意的点,那就是 难道需要每次 客户端 与 服务器 连接的时候,都要访问 公信机构的 服务器 吗?
答:当然不是,因为我们的操作系统上本身就内嵌了一些公信机构中的信息,所以只需要在本地就能完成身份验证。
黑客既然可以伪造证书,那会不会伪造 公信机构 呢?
答:这种情况是极小概率中的极小概率,公信机构 就相当于现实中的 公安局。这样做的风险极大,成本极高。