HTTP 状态码
HTTP 常见的状态码
- 200 - 请求成功
- 301 - 请求的资源已被永久转移到其他 URL
- 404 - 请求的资源不存在
- 500 - 内部服务器错误
HTTP 状态码分类
- 1** - 请求正在处理
- 2** - 请求成功
- 3** - 重定向,需要进一步操作完成请求
- 4** - 客户端错误
- 5** - 服务器错误
跨域
什么是跨域
在浏览器中,有重要的一个安全策略,叫 同源策略
其中 源 = 协议 + 主机 + 端口, 两个源相同,称之为同源,不同则称之为跨源或跨域
同源策略 是指,当前页面的源和页面运行过程中加载的源不一致时,浏览器会处于安全考虑,对跨域的资源访问进行一些限制。 同源策略对 ajax 的跨域限制最为严重,默认情况下它不允许 ajax 访问跨域资源
| 源1 | 源2 | 是否同源 |
|---|---|---|
| www.baidu.com | www.baidu.com/news | ✅ |
| www.baidu.com | www.baidu.com/news | ✅ |
| http://localhost:5000 | http://localhost:7000 | ❌ |
| http://localhost:5000 | http://127.0.0.1:5000 | ❌ |
| www.baidu.com | baidu.com | ❌ |
常见的跨域解决方法
- 代理 - 常用
- CORS - 常用
- JSONP
代理
一般在前端开发中,跨域的问题大多是通过代理解决的
代理的适用场景:生产环境没有发生跨域,开发环境发生跨域
所有只要在开发环境中使用代理即可解决跨域,这种代理又称为开发代理
例如 :
// vue 的开发服务器代理配置
// vue.config.js
module.exports = {
devServer: { // 配置开发服务器
proxy: { // 配置代理
"/api": { // 若请求路径以 /api 开头
target: "http://dev.taobao.com", // 将其转发到 http://dev.taobao.com
},
},
},
};
JSONP
JSONP 是当遇到跨域资源时,不使用 ajax,而使用 script 元素去请求服务器,由于服务器不会阻止 script 元素的请求,服务器拿到请求后,响应 JS 代码,这段代码实际上是一个函数调用,调用的这个函数是客户端准备好的函数,并将数据作为参数传到函数中,这样就变向的把数据传给客户端。但是 JSONP 只支持 get 请求
CORS
CORS 是基于** http1.1** 的一种跨域解决方案,全称是 Cross-Origin Resource Sharing 跨域资源共享 。
它的大概原理是:当浏览器需要跨域访问服务器的资源时,需要获得服务器的允许
而一个请求可能会包含很多信息,从而对服务器造成不同的影响
针对不同请求,CORS 分为以下两种情况:
- 简单请求
- 需要预检的请求
- 附带身份凭证的请求
简单请求
浏览器会直接请求服务器,然后检查返回内容头部的 Access-Control-Allow-Origin,如果允许当前域,则正常载入并展示返回内容,否则拒绝载入
例子:
假如站点 https://foo.example 的网页应用想要访问 https://bar.other 的资源
浏览器发送的请求标头字段 Origin 表示当前网页的源
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example
服务器返回的响应标头字段 Access-Control-Allow-Origion 代表允许哪些源访问, 值为 * 表示允许任意外源访问
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
同时满足以下条件,浏览器会认为它是一个简单请求:
只用了 GET、HEAD、POST 方法
包含一些标头,常见的标头如下:
AcceptAccept-LangugeContent-LanguageContent-TypeRange
其中 Content-Type 仅限以下标头之一:
text/plainmultipart/form-dataapplication/x-www-form-urlencoded
如果以上三个条件同时满足,浏览器会判定为简单请求
例子:
//简单请求
fetch('https://www.taobao.com');
fetch('https://www.taobao.com', {
method: 'post'
});
fetch('https://www.taobao.com', {
headers: {
'conte-type': 'text/plain'
}
});
//不满足要求 非简单请求
fetch('https://www.taobao.com', {
method: 'put'
});
fetch('https://www.taobao.com', {
headers: {
a: 1
}
});
fetch('https://www.taobao.com', {
headers: {
'conte-type': 'application/json'
}
});
预检的请求
浏览器会发出一个预检,发出一个 OPTION 方法,询问服务器是否来自当前域访问该资源,服务器返回允许访问当前域的 HTTP 方法及允许的头部,浏览器进行判断,是否发送真正的请求
如果当前请求不是简单请求,浏览器会按照以下流程进行:
- 浏览器发送预检请求,询问服务器是否允许
- 服务器允许
- 浏览器发送真实请求
- 服务器完成真实响应
例子:
在 http://my.com/index.html 中有以下代码发生跨域
//需要预检的请求
fetch('http://crossdomain.com/api/user', {
// post 请求
method: 'post',
// 设置标头
headers: {
a: 1,
b: 2,
'content-type': 'application/json'
},
//请求体
body: JSON.stringify({
name: '张三',
age: 22
})
})
浏览器发现它不是一个简单请求,会按照一些流程进行
- 浏览器发生预检请求,询问服务器是否允许
预检请求有以下特征:
- 请求方法为
OPTIONS - 没有请求体
- 请求头包含
Origin:请求的源Access-Control-Request-Method:后续真实请求的请求方法Access-Control-Request-Headers:后续真实请求的请求头
OPTIONS /api/user HTTP/1.1
Host: crossdomain.com
...
Origin: http://my.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: a, b, content-type
- 服务器允许
服务器收到预检请求后,检查预检请求包含的信息,如果允许该请求,需要响应以下的信息格式
其中这些标头的含义:
Access-Control-Allow-Origin: 表示允许的源Access-Control-Allow-Methods: 表示允许后续真实请求的请求方法Access-Control-Allow-Headers: 表示允许后续真实请求的请求头Access-Control-Max-Age: 告诉浏览器,在多少秒内,对于同样的请求源、方法、头,都不需要再发送预检请求
HTTP/1.1 200 OK
Date: Tue, 21 Apr 2020 08:03:35 GMT
...
Access-Control-Allow-Origin: http://my.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: a, b, content-type
Access-Control-Max-Age: 86400
...
- 浏览器发送真实请求
POST /api/user HTTP/1.1
Host: crossdomain.com
Connection: keep-alive
...
Referer: http://my.com/index.html
Origin: http://my.com
{"name": "xiaoming", "age": 18 }
- 服务器响应真实请求
HTTP/1.1 200 OK
Date: Tue, 21 Apr 2020 08:03:35 GMT
...
Access-Control-Allow-Origin: http://my.com
...
添加用户成功
附带身份凭证的请求
默认情况下 ajax 的跨域请求不会携带 cookie,这样以来一些权限相关的操作就无法进行
但是可以进行一些简单的配置来实现附带 cookie
// xhr
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
// fetch api
fetch(url, {
credentials: 'include',
});
这样配置,这个请求不管是简单请求还是预检请求,都会在请求头中添加一个 cookie 字段
而服务器的响应也必须要告知客户端:允许这样的凭证
告知方式:只需在 响应头中添加:Access-Control-Allow-Credentials: true
如果没有明确告知,默认为拒绝,而且 对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为 *
HTTP 缓存策略
HTTP 缓存 是 HTTP 优化中一种简单高效的优化方式,缓存是一种保存资源副本并在下次请求时直接使用该副本,当 web 缓存发现请求的资源已经被储存,会拦截该请求,然后直接使用副本,而不会去源服务器重新下载
HTTP 缓存 主要通过请求和响应头中对应的 Header 信息,来控制缓存的策略
根据是否需要重新向服务器发起请求,可分为两种请求:
- 强缓存(本地缓存)
- 弱缓存(协商缓存)
强缓存
当命中强缓存时,客户端不会请求服务器,会直接读取缓存的资源,并返回状态码 200
强缓存由响应头中 Expires、Cache-Control、Pragma 控制
-
Expires:值为服务器返回的过期时间,浏览器再次加载资源时,如果在这个过期时间内,则命中强缓存。(HTTP1.0的属性,缺点是客户端和服务器时间不一致会导致命中误差)
-
Cache-Control:HTTP1.1属性,优先级更高,以下为常用属性
- no-store: 禁用缓存
- no-cache:不使用强缓存,每次需向服务器验证缓存是否失效
- private/public:private指的单个用户,public可以被任何中间人、CDN等缓存
- max-age=:max-age是距离请求发起的时间的秒数
- must-revalidate:在缓存过期前可以使用,过期后必须向服务器验证
-
Pragma
- no-cache:效果和cache-control等no-cache一致。 优先级Pragma > Cache-Control > Expires
协商缓存
客户端向服务器发送请求,服务器会根据这个请求的请求头的一些参数来判断是否命中协商缓存,如果命中,则返回 304 状态码并带上新的响应头通知客户端从缓存获取中读取数据
协商缓存,响应头中有两个字段标记规则
-
Last-Modified / If-Modified-Since
- Last-Modified是浏览器第一个请求资源,服务器响应头字段,是资源文件最后一次更改时间(精确到秒)。
- 下一次发送请求时,请求头里的If-Modified-Since就是之前的Last-Modified
- 服务器更加最后修改时间判断命中,如果命中,http为304且不返回资源、不返回last-modify
-
Etag / If-None-Match:Etag 的校验优先级高于 Last-Modified
- Etag是加载资源时,服务器返回的响应头字段,是对资源的唯一标记,值是hash码。
- 浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到请求头里的If-None-Match里
- 服务器接受到If-None-Match的值后,会拿来跟该资源文件的Etag值做比较,如果相同,则表示资源文件没有发生改变,命中协商缓存。
用户行为对强缓存和协商缓存的影响
| 用户操作 | expires/cache-control | last-modified/etag |
|---|---|---|
| 地址栏回车、页面点击链接跳转、打开新窗口、前进后退 | 有效 | 有效 |
| F5刷新 | 无效 | 有效 |
| Ctrl+F5 强制刷新 | 无效 | 无效 |
HTTPS 的加密过程
HTTPS 是加密的网络传输协议
加密过程:
- 客户端向服务器发送请求。
- 服务器响应请求,并携带证书(服务器保存有CA证书,里面包含非对称加密的公钥及其他信息)。
- 客户端验证则证书,是否过期、证书中域名和真实域名一样、是否是可信任的颁发机构等,如果不通。过,会发送一个警告给用户,是否继续通讯,如果没问题,客户端会从证书中取出公钥A然后生成一个随机数key,使用公钥加密。
- 将加密后的key发送给服务器
- 服务器使用私钥B解密获得key。此时客户端和服务器建立连接,key只有双方才知道,为后面的对称加密通讯做准备。
- 服务器使用key加密数据发送给客户端
- 客户端使用key解密获得真正的数据
TCP 的三次握手和四次挥手
关键词:
- seq (Sequence number):序列号
- ack (Acknowledge numer):确认号,上一次seq的确认号,一般是seq+1
- SYN (Synchronization):用于建立连接,同步序列号
- ACK (Acknowledgement):对已接收的数据进行确认
- FIN (Finish):表示没有数据要发送,即将关闭连接
三次握手流程
- 客户端向服务器发送连接请求报文,报文包含SYN=1,序列号seq=x,此时,客户端进入 SYN-SENT 同步已发送状态
- 服务器收到连接请求报文,如果同意连接,则发送确认报文ACK=1,SYN=1,确认号ack=x+1,序列号seq=y,此时,服务器进入 SYN-RCVD 同步已接收状态
- 客户端收到确认报文后,还要在向服务器给出确认,确认报文ACK=1,确认号ack=y+1,自己的序列号seq=x+1,此时,服务器和客户端都进入 ESTABLISHED 已建立连接状态
四次挥手流程
- 客户端向服务器发送连接释放报文,报文包含FIN=1,序列号seqe=u,此时,客户端进入 FIN-WAIT1 终止等待1状态
- 服务器收到连接是否报文后,发送确认报文ACK=1,确认号ack=u+1,序列号seq=v,此时,服务器进入 CLOSE-WAIT 关闭等待状态
- 客户端收到服务器的确认后,进入 FIN-WAIT2 终止等待2状态,等待服务器发送连接释放报文,服务器最后的数据发送完成后,就向客户端发送释放报文 FIN=1,ACK=1,确认号ack=u+1,序列号seq=w,此时,服务器进入 LAST-ACk 最后确认状态
- 客户端收到服务器的连接释放报文后,返回确认报文 ACK=1,确认号ack=w+1,序列号u+1,此时,客户端进入 TIME-WAIT 时间等待状态