HTTP基础
本文以HTTP/1.1这一经典版本进行介绍相关基础概念
- 概述
- HTTP报文
- 连接管理
- Web服务器
- HTTP缓存
- Cookie
- CSP
- HTTPS
1 概述
- 定义
- 浏览器中的HTTP
- 发展历史
1.1 定义
HTTP(Hyper Text Transfer Protocol):超文本传输协议,一个简单的请求-响应协议
- 指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应
架构在TCP之上,默认为
80端口
相当于数据传输的信使
Web内容都是存储在Web服务器上的,使用HTTP协议的Web服务器也称为HTTP服务器
我们常用的Web浏览器是HTTP客户端
的一种
Web客户端向Web服务器发送HTTP请求
,服务器会将一个HTTP响应
并携带相关数据反馈给客户端
1.2 浏览器中的HTTP
浏览器中F12 -> Network选项 -> 右击
数据选择options选项选择列表展示的信息
百度百科
维基百科
可以得到数据传输的一些基本信息:例如方法、状态码、协议、方案等等
现在大部分网站使用HTTP/1.1与HTTP/2,部分在使用HTTP/3
HTTP/3架构在UDP上的协议,目前还未普及
单击
数据文件
Headers
- 常规:请求地址、方法、状态码、远程地址、引用站点策略等信息
- 响应头:协议、状态码、内容格式、长连接、日期、服务、缓存控制等信息
- 请求头:方法、文件名、协议、地址、用户代理等信息
相关字符串参数
不同的协议携带是数据不大相同
1.3 发展历史
HTTP/0.9:只接收GET方法,不支持请求头
- 请求:
GET
/index.html - 响应:<html></html>
- 仅支持
html格式
,不支持长连接
,每次通信都会关闭连接
HTTP/1.0
:基本成型
,支持富文本、header、状态码、缓存等
- 支持多种文件格式,图片、视频、二进制文件等
- 支持更多方法:GET、POST、HEAD
- 增加
标头信息
、状态码、缓存、权限等功能,不支持长连接
HTTP/1.1
:使用了20年
的主流标准
,支持连接复用、分块发送
- 新增方法:PUT、DELETE、OPTIONS、PATCH等,同时新增了一些缓存字段
- 请求头引入range字段,支持
断点续传
- 允许响应
数据分块
,利于传输大文件 - 管道机制:一个TCP连接可以发送多个请求
支持长连接
:一个TCP默认不关闭,可以被多个请求复用强制要求Host头,让互联网主机托管成为可能
队头阻塞:长连接时,一个连接中同一时刻只能处理一个请求,当前请求没有结束之前,其他请求只能处于阻塞状态
SPDY:HTTP/2前身
QUIC:第三代协议,基于UDP
实现TCP+HTTP/2并优化
HTTP/2
:第二代协议,多路复用、头部压缩、服务器推送等
不再使用
ASCII编码传输
,改为二进制帧
(Frame)形式,可以拆分合并多路复用
:有了二进制帧,对于同一个域,客户端只需要与服务器建立一个连接即可完成通信需求,利用一个连接来发送多个请求。每一条路都被称为一个stream(流)废弃了管道
- 使用专用算法压缩头部,减少数据传输量
- 允许服务器主动向客户推送数据
- 头部字段全部改为小写;引入
伪头部
,出现在头部字段之前,以冒号
开头 - 要求加密通信
HTTP/3:QUIC更名为HTTP/3
- HTTP/2采用二进制分帧进行多路复用,通常只使用
一个TCP连接
进行传输,在丢包或网络中断的情况下后面的所有数据都被阻塞 - HTTP/1.1可以开启多个TCP连接,任何一个TCP出现问题都不会影响其他TCP连接
HTTP/3将底层依赖的TCP改为UDP,在传输数据时不需要建立连接,可以同时发送多个数据包
缺点就是没有确认机制来保证对方一定能收到数据
协议版本 | 解决的核心问题 | 解决方式 |
---|---|---|
0.9 | HTML文件传输 | 确立了客户端请求、服务端响应的通信流程 |
1.0 | 不同类型文件传输 | 设立头部字段 |
1.1 | 创建/断开TCP连接开销大 | 建立长连接进行复用 |
2 | 并发数有限 | 二进制分帧 |
3 | TCP丢包阻塞 | 采用UDP协议 |
2 HTTP报文
HTTP报文是简单的格式化数据块,每条报文都包含一条来自客户端的请求
或来自服务器的响应
- 请求与响应报文
- 方法
- 状态码
- 首部
- 数据协商
2.1 请求与响应报文
三部分组成:起始行、首部、主体
- 起始行:对报文进行的描述
- 首部:相关属性
- 主体:数据
请求报文:请求行、消息报头、请求正文
//请求行 GET /text/hello.txt HTTP/1.1 //消息报头 Accept:text/* Host:www.demo.com
响应报文:状态行、消息报头、响应正文
//响应行 HTTP/1.1 200 OK //消息报头 Content-type:text/plain Content-length:13 ... //响应正文 Hello World!
- 一组HTTP首部总是应该以一个
空行结束
2.2 方法
用于告知服务器要做什么
方法 | 描述 | 是否包含主体 |
---|---|---|
GET | 从服务器获取一份文档 | 否 |
HEAD | 只从服务器获取文档的首部 | 否 |
POST | 向服务器发送需要处理的数据 | 是 |
PUT | 将请求的主体部分存储在服务器 | 是 |
TRACE | 对可能经过代理服务器传送到服务器上的报文进行追踪 | 否 |
OPTIONS | 决定可以在服务器上执行哪些方法 | 否 |
DELETE | 从服务器上删除一份文档 | 否 |
除了这些方法外,服务器可能还会实现一些自己的请求方法
HEAD:不要数据只要首部,可以用于对资源类型检查
PUT:与GET方法正好相反,用于向服务器写入文档
PUT之前一般要求用户登录
扩展方法
- LOCK:允许用户锁定资源
- MKCOL:允许用户创建资源
- COPY:便于在服务器上复制资源
- MOVE:在服务器上移动资源
2.3 状态码
方法用于告知服务器要做什么,状态码则是告知客户端发生了什么
整体范围 | 已定义范围 | 分类 |
---|---|---|
1** | 100~101 | 信息提示继续操作 |
2** | 200~206 | 成功 |
3** | 300~307 | 重定向需要进一步操作完成请求 |
4** | 400~417 | 客户端错误请求错误或无法完成请求 |
5** | 500~505 | 服务器错误处理请求发生错误 |
常见状态码
- 100:Continue 继续该请求
- 101:切换协议
- 200:Ok,成功
- 301:永久跳转,走缓存,必须收到清除
- 302:临时跳转,不走缓存
- 305:Use Proxy,必须通过代理访问资源
- 401:
未授权
,需要用户名和密码 - 404:Not Found,服务器未找到对应资源
- 500:服务器遇到一个妨碍它为请求服务的错误
- 505:服务器不支持请求的
协议版本
2.4 首部
请求和响应报文中的附加信息
HTTP规范定义了几种首部字段,应用程序也可以随意发明自己所用的首部
- 通用首部:两者都可用
- 请求首部:有关请求的信息
- 响应首部:有关响应的信息
- 实体首部:描述主体的长度和内容
- 扩展首部:未定义的新首部
通用首部
- Connection:指定请求/响应连接的选项
- Date:报文创建时间
- Via:报文经过的中间节点(代理、网关)
- Cache-Control:缓存提示
...
请求首部
- Client-IP:客户端IP地址
- From:客户端E-mail地址
- Host:请求服务器主机号和端口号
- Referer:请求URI文档的URL
- User-Agent:请求发起的应用程序名
Accept首部:将客户端的喜好和能力告知服务器
Accept-Charset:想要的字符集类型
条件请求首部:请求的限制
Range:请求资源的指定范围
安全请求首部
Authorization:对其自身进行认证的数据
Cookie:向服务器传送一个令牌代理请求首部
Max-Forward:请求转发给代理或网关的最大次数
响应首部
- Age:响应持续时间
- Server:服务器应用程序软件的名称及版本
协商首部:资源有多种表示法,服务器与客户端需要进行协商
Accept-Range:服务器可接收的范围类型
安全响应首部:基本质询
Proxy-Authenticate:代理对客户端的质询
Set-Cookie:在客户端设置令牌
实体首部
内容首部:实体内容的特定信息
Content-Base:解析主机中相对URL时使用的基础URL
Content-Length:主体的长度或尺寸
...实体缓存首部:如何或什么时候进行缓存
Exprires:实体不再有效,要从原始的源端再次获取次实体的日期和时间
Last-Modified:实体最后一次被修改的日期和时间
这里并没有把所有首部字段全部列出,仅供参考
2.5 数据协商
请求协商 | 描述 | 响应协商 |
---|---|---|
Accept | 告知服务器发送何种媒体类型 | Content-Type |
Accept-Language | 告知服务器发送何种语言 | Content-Language |
Accept-Charset | 告知服务器发送何种字符集 | Content-Type |
Accept-Encoding | 告知服务器采用何种编码/压缩 | Content-Encoding |
3 连接管理
HTTP连接实际上就是TCP连接及其使用,TCP为HTTP提供了一条可靠的比特传输管道
- TCP连接
- 套接字
- 持久连接
3.1 TCP连接
TCP的数据时通过IP分组的小数据块来发送的,HTTP-TCP-IP
HTTPS就是在HTTP和TCP之间插入
一个TLS或SSL密码加密层
TCP连接同过四个值进行识别
源IP地址、源端口号、目的IP地址、目的端口号
这四个值决定了一条
唯一
的TCP连接
- 打开wireshark抓包工具开始抓包
- 打开cmd ping想要的网站 - 得到IP地址
浏览中访问IP,加载完成后暂停抓包并过滤
可以清晰的看到TCP三次握手的过程
三次握手
客户端->服务端:SYN请求连接
序列号Seq为0
客户端<-服务器:SYN请求连接,ACK应答
序列号Seq为0,确认应答号ACK为1
客户端->服务端:ACK应答
序列号Seq为1,确认应答号ACK为1
四次挥手的包不是很好抓,在这里不再赘述
与建立连接不同的是服务端会先确认应答
等待缓存区清空后再请求断开
连接性能
HTTP的事务架构在TCP之上,通过提高TCP的性能来提高HTTP的连接性能
- 并行连接:通过多条TCP连接发起并发的HTTP请求
- 持久连接:重用TCP连接,以消除连接及关闭时延
- 管道化连接:通过共享的TCP连接发起
并发
的HTTP请求 - 复用的连接:交替传送请求和响应报文
3.2 套接字
套接字编程:操作TCP连接的工具
//常见API //创建一个新的、未命名、未关联的套接字 s = socket(<parmeters>) //向套接字赋予一个本地端口号和接口 bind(s,<local IP:port>) //创建一条连接本地套接字和远程主机及端口的连接 connet(s,<remote IP:port>) //标识一个本地套接字,使其可以合法接受连接 listen(s,...) //等待某人建立一条到本地端口的连接 s2 = accept(s) //尝试从套接字向缓冲区读取n个字节 n = read(s,buffer,n) //尝试从缓冲区向套接字写入n个字节 n = write(s,buffer,n) //完全关闭TCP连接 close(s) //只关闭TCP连接的输入或输出端 shutdown(s,<side>) //读取某个内部套接字配置选项的值 getsockopt(s,...) //修改某个内部套接字配置选项的值 setsockopt(s,...)
3.3 持久连接
也称为长连接
Connection:Keep-Alive/close
(开启/关闭)Keep-Alive:max=5,timeout=120
HTTP事务数量5,将打开状态保持到连接空闲了120s之后
- HTTP2只需要建立一个TCP长连接(同域)
示例
const http = require('http')
const fs = require('fs')
http.createServer((req, res) => {
if (req.url === '/') {
const html = fs.readFileSync('index.html', 'utf-8')
res.writeHead(200, {
'Content-type': 'text/html',
// 'Connection': 'close',
})
res.end(html)
} else {
const img = fs.readFileSync('huawei.jpg')
res.writeHead(200, {
'Content-type': 'image/jpg',
// 'Connection': 'keep-alive',
// 'Keep-Alive': "max=5",
// 'Connection': 'close',
})
res.end(img)
}
}).listen(8000)
console.log("server OK", "http://127.0.0.1:8000");
index.html添加几个img即可
打开调试窗口
Chrome支持六个长连接并发请求,在六个请求都返回后继续复用这些连接
关闭长连接
不会对连接进行复用,会重新进行连接
HTTP/1.1支持多个连接并发请求,如果采用HTTP2只需要1个长连接
跑所有请求
4 Web服务器
Web服务器实现了HTTP和相关的TCP连接处理,表示Web服务器的软件或者提供Web页面的特定设备或计算机
负责管理Web服务器提供的资源,以及对Web服务器的配置、控制及扩展方面的管理
- 流程
- 跨域
4.1 流程
- 建立连接:接收或拒绝一个客户端连接
- 接收请求:从网络中读取一条HTTP请求报文
- 处理请求:对请求报文进行解释,并采取行动
- 访问资源:访问报文中指定的资源
- 构建响应:创建带有正确首部的HTTP响应报文
- 发送响应:将响应回送给客户端
- 记录事务处理过程:记录在日志文件中
接受客户端连接
- 处理新连接:从TCP连接中将TP地址解析出来,可以任意关闭连接
- 客户端主机名识别:启用Apache主机查找功能
- 使用ident协议确定HTTP的客户端用户名
接收请求报文
- 解析请求行,查找请求方法、指定的资源标识符URI以及版本号
- 单线程/多线程Web服务器进行不同方式的请求服务
处理请求
对资源的映射及访问
- docroot:Web内容根目录
- 对Web内容进行访问或控制
构建响应
Web服务器识别出了资源,就执行请求方法中描述的动作,并返回响应报文
- 有时会返回重定向响应而不是成功的报文
- 资源被临时/永久搬离,服务器负载均衡
发送响应
记录日志
4.2 跨域
由于同源策略
的限制,浏览器向服务器发送请求后,如果协议、域名、端口
有一个不同那么数据会被浏览器拦截下来。
添加请求头的方式
//html <body> <button id="cors">请求数据</button> <script> const oBtn = document.getElementById('cors') oBtn.addEventListener('click', () => { const xhr = new XMLHttpRequest() xhr.open('GET', 'http://localhost:8000') xhr.send() }) </script> </body> //要请求的数据 [ { "id": 101, "name": "张三", "age": 18 } ]
//服务1放html页面 模拟客户端 5000端口 const http = require('http') const fs = require('fs') http.createServer((req, res) => { html = fs.readFileSync('index.html', 'utf-8') res.writeHead(200, { 'Content-type': 'text/html' }) res.end(html) }).listen(5000) console.log("Server1 Ok", "http://localhost:5000"); //服务2放数据 模拟服务器 8000端口 const http = require('http') const fs = require('fs') http.createServer((req, res) => { console.log(req.url + "请求了数据"); data = fs.readFileSync('user.json', 'utf-8') res.writeHead(200, { 'Content-type': 'application/json' }) res.end(data) }).listen(8000) console.log('Server2 Ok', 'http://localhost:8000');
启动两个服务,在5000端口去访问数据
可以看到出现了跨域的错误在服务2的头部加上允许跨域的字段
//允许所有源的GET POST HEAD 方法进行跨域 'Access-Control-Allow-Origin': '*' //设置允许其他请求方法可以跨域 'Access-Control-Allow-Methods':'PUT,DELETE'
这样就可以正常的获取到8000端口上的数据
预请求
:使用OPTIONS方法发起一个预检请求,第二次才是真正的异步请求//发起预请求的间隔时间 "Access-Control-Max-Age":"60" //设置预请求中可以携带的自定义头部字段 'Access-Control-Allow-Headers':'Test-Cors'
JSONP
使用script标签访问资源可以跨域的特性<script src="http://localhost:8000"></script>
5 HTTP缓存
Web缓存是可以自动保存常见副本的HTTP设备
Web请求抵达缓存时,如果本地有"已缓存的"副本,可以从本地存储设备
提取文档而不是从原始服务器中提取
- 描述
- 副本新鲜与控制缓存
5.1 描述
优点
- 减少冗余的数据传输
减少网络瓶颈的问题
不需要更多的带宽就能够更快地加载页面
降低对原始服务器的要求
服务器可以更快的响应,避免过载的出现
降低距离时延
较远的地方加载页面会更慢一些
处理步骤
- 接收:缓存从网络中读取抵达的请求报文
- 解析:缓存对报文进行解析,提取出URL和各种首部
- 查询:缓存查看是否有本地副本可用
- 新鲜度检测:缓存查看已缓存副本是否足够新鲜,如果不是,就询问服务器是否有任何更新
- 创建响应:缓存会用新的首部和已缓存的主体来构建一条响应报文
- 发送:缓存通过网络将响应发回给客户端
- 日志:缓存可选地创建一个日志文件条目来描述这个事务
5.2 副本新鲜与缓存控制
首部字段 | 描述 |
---|---|
Cache-Control | 缓存控制 |
Expires | 缓存到期时间 |
If-Modified-Since | 指定日期,之后文档被修改则执行请求的方法 |
If-None-Match | 服务器为文档提供Etag,标签变化执行方法 |
Last-Modified配合If-Modified/UnModified-Since使用
ETag配合If-None-Match与If-Match使用
Cache-Control | 描述 |
---|---|
pulic | 任何地方都可以缓存 |
private | 只限客户端甚至某些字段可以缓存 |
no-cache | 去服务端验证才能缓存 |
no-store | 禁止缓存 |
no-transform | 禁止代理服务器改动返回内容 |
max-age | 缓存后的新鲜秒数 |
s-maxage | 仅在代理服务器中生效 |
max-stale | 发起端允许缓存提供过期的文件 |
//在服务2的响应首部设置max-age
'Cache-Control': 'max-age=120'
每请求一次
数据会被缓存并保持两分钟新鲜,size属性标识从缓存获取
这样当服务端数据变化时客户端的请求不能及时响应
验证头:不走本地缓存,发送请求时带上验证头,验证决定是否走缓存,基于no-cache
const etag = req.headers['if-none-match'] if (etag === 'v1.0') { res.writeHead(304, { }) res.end('000') } else { res.writeHead(200, { 'Content-type': 'application/json', 'Access-Control-Allow-Origin': '*', 'Cache-Control': 'max-age=3000,no-cache', 'ETag': 'v1.0' }) res.end(data) }
no-cache表示需要进行验证决定是否走缓存,在服务端设置ETag
- 第一次的请求会
客户端
将If-None-Match设置为ETag的值 在第二次请求时
服务端验证
请求头中的值与ETag中的值决定是否返回304进行重定向让客户端继续走缓存ajax请求有时会返回200但是实际上还是走的缓存,服务端修改数据不修改ETag并不会让客户端变化
6 Cookie
Cookie可用于识别用户并实现持久会话
服务端设置
'Set-Cookie':['user=root','psw:123;max-age=20']
可以通过max-age与expries设置过期时间,也可以使用其他字段进行相关设置
- domain:浏览器只向指定域中的服务器主机名发送cookie
- path:为服务器上特定的文档分配cookie
- secure:只有在HTTP使用SSL安全连接时才会发送cookie
httpOnly:只在http请求中使用,浏览器不能用
document.cookie
获取'Set-Cookie': ['user=root', 'psw=123;httpOnly']
7 CSP
CSP(Content Security Policy):内容安全策略
限制资源加载
的一种方式,可以避免一些XSS攻击
//禁止src资源 'Content-Security-Policy':'default-src; report-uri /reprot' //添加三个script标签 <script src="http://localhost:8000"></script> <script src="https://www.baidu.com"></script> <script> console.log(123); </script>
report-uri:记录相关错误信息
可以看到三个资源均未加载
'Content-Security-Policy':'default-src http: https:; report-uri /reprot'
需要src中的http https加载
- default-src \'self\':src中允许本地资源的加载
CSP还有很多限制策略,这里不进行详细描述
CSP文档(MDN)
8 HTTPS
HTTPS就是在HTTP和TCP之间插入一个TLS或SSL密码加密层
HTTP是明文传输,HTTPS通过握手
进行加密
- 采用公钥加密体制(非对称加密)
- 客户端请求服务器获取
证书公钥
- 客户端(SSL/TLS)解析证书(无效弹出警告)
- 有效客户端则生成生成
随机值
- 客户端用
公钥
对称加密随机值
生成密钥
- 客户端将
密钥
发送给服务端 - 服务端用
私钥
解密密钥
得到随机值
- 服务端使用
随机值
对称加密数据
发送给客户端 客户端再用
随机值
解密得到数据
整个过程第三方即使监听到了数据,也束手无策
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。