HTTP 是什么
HTTP (全称为 "超文本传输协议") 是一种应用非常广泛的 应用层协议.
所谓 " 超文本 " 的含义 , 就是传输的内容不仅仅是文本 ( 比如 html, css 这个就是文本 ), 还可以是一些 其他的资源, 比如图片 , 视频 , 音频等二进制的数据
HTTP 诞生与 1991 年 . 目前已经发展为最主流使用的一种应用层协议 .
HTTP 往往是基于传输层的 TCP 协议实现的 . (HTTP1.0, HTTP1.1, HTTP2.0 均为 TCP, HTTP3 基于 UDP 实现)
目前我们主要使用的还是 HTTP1.1 和 HTTP2.0 . 当前课堂上讨论的 HTTP 以 1.1 版本为主 .
我们平时打开一个网站, 就是通过 HTTP 协议来传输数据的.
当我们在浏览器中输入一个 搜狗搜索的 " 网址 " (URL) 时 , 浏览器就给搜狗的服务器发送了一个 HTTP 请求, 搜狗的服务器返回了一个 HTTP 响应 .
这个响应结果被浏览器解析之后 , 就展示成我们看到的页面内容 . ( 这个过程中浏览器可能会给服务器发送多个 HTTP 请求 , 服务器会对应返回多个响应 , 这些响应里就包含了页面 HTML, CSS, JavaScript, 图片 , 字体等信息).
理解 "应用层协议
我们已经学过 TCP/IP , 已经知道目前数据能从客户端进程经过路径选择跨网络传送到服务器端进程 [ IP+Port ].
可是,仅仅把数据从 A 点传送到 B 点就完了吗?
这就好比,在淘宝上买了一部手机,卖家 [ 客户端 ] 把手机通过顺丰 [ 传送 + 路径选择 ] 送到买家 [ 服务器 ] 手里就完了吗?
当然不是,买家还要使用这款产品,还要在使用之后,给卖家打分评论。
所以,我们把数据从 A 端传送到 B 端, TCP/IP 解决的是顺丰的功能,而两端还要对数据进行加工处理或者使用,所以我们还需要一层协议,不关心通信细节,关心应用细节!
这层协议叫做应用层协议。而应用是有不同的场景的,所以应用层协议是有不同种类的,其中经典协议 之一的HTTP 就是其中的佼佼者 .
再回到我们刚刚说的买手机的例子,顺丰相当于 TCP/IP 的功能,那么买回来的手机都附带了说明书【产品介绍,使用介绍,注意事项等】,而该说明书指导用户该如何使用手机【虽然我们都不看,但是父母辈有部分是有看说明书的习惯的:)】,此时的说明书可以理解为用户层协议
理解 HTTP 协议的工作过程
当我们在浏览器中输入一个 " 网址 ", 此时浏览器就会给对应的服务器( 你想访问网站的服务器 )发送一个 HTTP 请求 . 对方服务器收到这个请求之后, 经过计算处理 , 就会返回一个 HTTP 响应 .
一个网站: 前端 (HTML + CSS + Javascript) + 后端(HTTP服务器)
HTML 描述了网页的结构 (页面上有啥内容)
CSS 描述网页的样式 (内容具体长啥样, 字体,颜色, 背景, 位置, 大小)
Javascript 描述网页的行为 (和用户进行交互)
这三个编程语言都是在浏览器上执行的, 都是在访问服务器的时候, 从服务器下载到自己的浏览器上才能显示执行
其他应用程序都是先下载安装, 才能使用, 而网页是随时用随时下载
使用网页的优势: 服务器随时更新, 用户总能用上新版本
劣势: 性能有限, 很难提供一些复杂的, 重量的操作
HTTP 协议格式
HTTP 是一个文本格式的协议 . 可以通过 Chrome 开发者工具或者 Fiddler 抓包 , 分析 HTTP 请求 / 响应的细节.
抓包工具的使用
- 左侧窗口显示了所有的 HTTP请求/响应, 可以选中某个请求查看详情.
- 右侧上方显示了 HTTP 请求的报文内容. (切换到 Raw 标签页可以看到详细的数据格式)
- 右侧下方显示了 HTTP 响应的报文内容. (切换到 Raw 标签页可以看到详细的数据格式)
- 请求和响应的详细数据, 可以通过右下角的 View in Notepad 通过记事本打开.
可以使用 ctrl + a 全选左侧的抓包结果, delete 键清除所有被选中的结果.
抓包工具的原理
Fiddler 相当于一个 " 代理 ".
浏览器访问 sogou.com 时 , 就会把 HTTP 请求先发给 Fiddler, Fiddler 再把请求转发给 sogou 的服务器 .
当 sogou 服务器返回数据时 , Fiddler 拿到返回数据 , 再把数据交给浏览器 .
因此 Fiddler 对于浏览器和 sogou 服务器之间交互的数据细节 , 都是非常清楚的.
也就是HTTP请求, HTTP响应可以直接通过代理全部获取
抓包结果
以下是一个 HTTP 请求 / 响应 的抓包结果 .
HTTP请求
可以看到HTTP是文本协议 不同于IP, TCP,UDP是二级制协议
- 首行: [方法] + [url] + [版本]
- Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
- Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度;
HTTP响应
- 首行: [版本号] + [状态码] + [状态码解释]
- Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
- Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度; 如果服务器返回了一个html页面, 那么html页面内容就是在body中.
HTTP 响应经常进行压缩 (节省带宽, 因为响应可能比较大)
HTTP响应内容解压之后通常得到的就是HTML + CSS + Javascript 代码
协议格式总结
思考问题 : 为什么 HTTP 报文中要存在 " 空行 "?
因为 HTTP 协议并没有规定报头部分的键值对有多少个. 空行就相当于是 "报头的结束标记", 或者是 "报头和正文之间的分隔符".
HTTP 在传输层依赖 TCP 协议, TCP 是面向字节流的. 如果没有空行, 就会出现 "粘包问题
HTTP 请求 (Request)
认识 URL
URL 基本格式
平时我们俗称的 " 网址 " 其实就是说的 URL (Uniform Resource Locator 统一资源定位符 ).
互联网上的每个文件都有一个唯一的 URL ,它包含的信息指出文件的位置以及浏览器应该怎么处理它 .
出自于RFC 标准文档, 约定这里的细节
https://v.bitedu.vip/personInf/student?userId=10000&classId=100
一个具体的 URL:
https : 协议方案名 . 常见的有 http 和 https, 也有其他的类型 . ( 例如访问 mysql 时用的jdbc:mysql
user:pass : 登陆信息 . 现在的网站进行身份认证一般不再通过 URL 进行了 . 一般都会省略
v.bitedu.vip : 服务器地址 . 此处是一个 " 域名 ", 域名会通过 DNS 系统解析成一个具体的 IP 地址 . (通过 ping 命令可以看到 , v.bitedu.vip 的真实 IP 地址为 118.24.113.28 )
也可以 ip 地址 (外网ip , 本身就是唯一的; 内网ip , 访问局域网中的设备; 环回ip , 访问自己)
端口号 : 端口号用来区分应用程序, 上面的 URL 中端口号被省略了 .
当端口号省略的时候, 浏览器会根据协议类型自动决定使用哪个端口. 例如 http 协议默认使用 80 端口, https 协议默认使用 443 端口 .
/personInf/student : 带层次的文件路径 . 访问服务器上的哪个资源
userId=10000&classId=100 : 查询字符串 (query string). 本质是一个键值对结构 . 键值对之间使用 & 分隔 . 键和值之间使用 = 分隔 . 以 ? 开始
片段标识 : 此 URL 中省略了片段标识 . 片段标识主要用于页面内跳转 . ( 通过不同的片段标识跳转到文档的不同章节 )
都可以省略
关于 query string
query string 中的内容是键值对结构. 其中的 key 和 value 的取值和个数, 完全都是程序猿自己约定的. 我们可以通过这样的方式来自定制传输我们需要的信息给服务器.
虽然认识了 query string 的格式, 但大部分时候是看不出内容和含义的
端口号可以省略
带层次的路径也能省略
查询字符串也可以没有
有和没有, 都可以让后端代码根据情况来处理
关于 URL encode
转换规则: 将需要转码的字符,按指定编码方式(默认使用UTF-8编码)转化为字节流,每个字节按16进制表示, 然后每个字节前面加上一个 %
例如:汉字 “你好”
- UTF-8字节流打印为:
-28 -67 -96 -27 -91 -67
- 对应的16进制表示为:
E4 BD A0 E5 A5 BD
- URLEncode编译后为:
%E4%BD%A0%E5%A5%BD
经过urlencode 之后, 此时query string中 就不会出现特殊含义的符号, 浏览器和服务器才能正确识别
就和写代码不能用关键字作为变量名一样的道理
认识 "方法" (method)
1. GET 方法
GET 是最常用的 HTTP 方法 . 常用于获取服务器上的某个资源.
在浏览器中直接输入 URL, 此时浏览器就会发送出一个 GET 请求 .
使用 Fiddler 观察 GET 请求
打开 Fiddler, 访问 搜狗主页 , 观察抓包结果.
GET 请求的特点
- 首行的第一部分为 GET
- URL 的 query string 可以为空, 也可以不为空.
- header 部分有若干个键值对结构.
- body 部分为空.
2. POST 方法
POST 方法也是一种常见的方法 . 多用于提交用户输入的数据给服务器 ( 例如登陆页面 ).
使用 Fiddler 观察 POST 方法
在比特教务系统的登陆页面 , 输入用户名 , 密码 , 验证码之后 , 点击登陆 , 就可以看到 POST 请求 .
点击这个请求, 查看请求详情
POST 请求的特点
- 首行的第一部分为 POST
- URL 的 query string 一般为空 (也可以不为空)
- header 部分有若干个键值对结构.
- body 部分一般不为空. body 内的数据格式通过 header 中的 Content-Type 指定. body 的长度由 header 中的 Content-Length 指定.
经典面试题: 谈谈 GET 和 POST 的区别
- 语义不同: GET 一般用于获取数据, POST 一般用于提交数据.
- GET 的 body 一般为空, 需要传递的数据通过 query string 传递, POST 的 query string 一般为空, 需要传递的数据通过 body 传递
- GET 请求一般是幂等的, POST 请求一般是不幂等的. (如果多次请求得到的结果一样, 就视为请求是幂等的).
- GET 可以被缓存, POST 不能被缓存. (这一点也是承接幂等性).
常见错误
3. 其他方法(面试问除了get post 还有什么方法)
- PUT 与 POST 相似,只是具有幂等特性,一般用于更新
- DELETE 删除服务器指定资源
- OPTIONS 返回服务器所支持的请求方法
- HEAD 类似于GET,只不过响应体不返回,只返回响应头
- TRACE 回显服务器端收到的请求,测试的时候会用到这个
- CONNECT 预留,暂无使用
认识请求 "报头" (header)
header 的整体的格式也是 " 键值对 " 结构 .
每个键值对占一行 . 键和值之间使用分号分割 .
报头的种类有很多 , 此处仅介绍几个常见的 .
Host
表示服务器主机的地址和端口 .
通常情况下Host里的内容和url里的是一致的, 但是也有例外, 比如如果使用了代理,就不一定一样了
Content-Length
表示 body 中的数据长度 .
长度是多少字节, 如果没有body可以没有, 如果有body这个字段必须有 body从空行开始数,数content-Length这么长 就是body结束了
用以解决粘包问题 (HTTP 基于 TCP) 当浏览器连续给服务器发起多个HTTP请求的时候或者服务器连续返回多个HTTP响应的时候, 服务器或者浏览器如何区分, 从哪到哪是一个完整的HTTP数据呢?
- 使用分隔符
- 使用长度
HTTP两个都要, 如果是GET, 没有body, 使用空行,作为结束符;
如果是POST, 有body,使用长度,区分结尾
Content-Type
表示请求的 body 中的数据格式
在HTTP请求中: 有三种主要情况
表单提交的数据格式. 此时 body 的格式形如:title=test&content=hello 格式和query string 一样
通常用于提交图片/文件
{"username":"123456789","password":"xxxx","code":"jw7l","uuid":"d110a05ccde64b16
a861fa2bddfdcd15"}
如果是响应, 情况更复杂
可能是html : text/html
也可能是 css : text/css
可能是js : application/javascript
可能是json : application/json
图片: image/jpg
纯文本: text/plain
通过content-type 就可以区分出body的数据格式是啥, 尤其是浏览器, 需要根据不同的格式来决定如何处理
User-Agent ( 简称 UA)
表示浏览器 / 操作系统的属性. 形如
其中 Windows NT 10.0 ; Win64 ; x64 表示操作系统信息
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36 表示浏览器信
息 .
Referer
表示这个页面是从哪个页面跳转过来的. 比如我在bing浏览器上打开搜狗
如果直接在浏览器中输入URL, 或者直接通过收藏夹访问页面时是没有 Referer 的 .
Cookie
cookie内容是什么
cookie登录过程
cookie从哪里来
Cookie 中存储了一个字符串 ,
这个数据可能是客户端 ( 网页 ) 自行通过 JS 写入的 ,
也可能来自于服务器 ( 服务器在 HTTP 响应的 header 中通过 Set-Cookie 字段把cookie的键值对, 返回给浏览器, 之后再在本地存储 ).
cookie功能之身份标识
往往可以通过这个字段实现 " 身份标识 " 的功能 .
为了实现身份识别的效果, 不仅仅需要cookie来支持, 在服务器这边也需要一个session机制来支持
后续访问网站的其他页面, 就相当于我去各个科室做检查, 都会在请求的cookie字段中, 带上刚才这里的sessionId(也就是我到了科室, 人家让我先刷就诊卡), 服务器就可以根据sessionId 就知道你当前用户的身份信息了
cookie 的本质是浏览器在本地存储 用户自定义数据的一种关键机制
cookie存储在哪里?
既然是需要存储, 怎么存?
直接存储到硬盘上是不行的, 不能允许网页能够操作你的电脑文件系统, 为了保证用户上网安全, 浏览器会限制网页直接访问硬盘.
浏览器虽然禁止了直接访问硬盘, 但提供了cookie机制, 允许网页往浏览器这边存储一些自定义的键值对, 这些数据通过了浏览器提供的api 写入特定的文件中
所以cookie是间接存储在浏览器所在电脑的硬盘上
cookie的内容到哪里去?
后续再访问这个网站中的各个页面, 就会在请求中带上cookie, 服务器就可以进一步的知道客户端的详细资料了
(刚才去看病, 各个科室刷就诊卡)
cookie在浏览器这边只能算是"暂存"
真正要让这个数据发挥作用, 还得是服务器来使用
认识请求 "正文" (body)
正文中的内容格式和 header 中的 Content-Type 密切相关 . 上面也罗列了三种常见的情况 .
下面可以通过抓包来观察这几种情况:
- application/x-www-form-urlencoded
- multipart/form-data
- application/json