CORS跨域资源共享
接口的跨域问题
上一篇中编写的GET和POST接口,存在一个很严重的问题:不支持跨域请求。
跨域
域的概念:我们认为协议 + 域名 + 端口号均相同,那么就是同域。
跨域的概念:请求资源的域与该请求指向的资源所在的域不一样。
解决跨域问题
- CORS:主流的解决方案(推荐)
- JSONP:有缺陷的解决方案:只支持GET请求(很少使用)
使用cors中间件解决跨域问题
cors是Express的一个第三方中间件。通过安装和配置cors中间件,可以很方便地
解决跨域问题。
步骤:
- 运行
npm install cors安装中间件 - 使用
const cors = require('cors')导入中间件 - 在路由之前调用
app.use(cors())配置中间件
什么是CORS
CORS (Cross-Origin Resoutce Sharing,跨域资源共享)由一系列HTTP
响应头组成,这些HTTP响应头决定浏览器是否阻止前端JS代码跨域获取资源
浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了CORS相关的HTTP响应头,就可以解除浏览器的跨域访问限制
注意事项
- CORS主要在服务器端进行配置,客户端浏览器无须做任何额外的配置,即可请求那些开启了CORS的借口
- CORS在浏览器中有兼容性。只有支持
XMLHttpRequest Level2的浏览器,才能正常访问开启了CORS的服务端接口(例如:IE10+,Chrome4+,RireFox3.5+)
CORS 响应头部 Access-Control-Allow-
请求头和响应头的概念
请求头
请求头部由关键字/值对组成,每行一对,关键字和值用英文冒号“:”分隔。请求头部通知服务器有关于客户端请求的信息,典型的请求头有:
- User-Agent:产生请求的浏览器类型。
- Accept:客户端可识别的内容类型列表。
- Host:请求的主机名,允许多个域名同处一个IP地址,即虚拟主机。
响应头
响应头讯息里包含了服务器的响应讯息,如http版本,压缩方式,响应文件类型,文件编码等
Access-Control-Allow-Origin
Access-Control-Allow-Origin: <origin>|*
其中,origin参数的值指定了允许访问该资源的外域URL。 如果指定了Access-Control-Allow-Origin字段的值为通配符*,表示允许来自任何域的请求
//下面的字段值只允许来自http://123.com 的请求:
res.setHeader('Access-COntrol-Allow-Origin','http://123.com')
//下面的字段值允许来自允许来自任何域的请求:
Access-Control-Allow-Headers
默认情况下,CORS仅支持客户端向服务器发送如下的9个请求头:
Accept,Accept-Language,Content-Language,DPR,Downlink,
Save-Data,Viewport-Width,Width,
Content-Type (值仅限于text/plain,multipart/form-data,
application/x-www-form-urlencoded三者之一)
如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过Access-Control-Allow-Headers对额外的请求头进行声明,否则这次请求会失败
//允许客户端额外向服务器发送Content-Type请求头和X-Custom-Header请求头
//注意:多个请求头之间使用英文逗号进行分割
res.setHeader('Access-Control-Allow-Headers','Content-Type'
,'X-Custom-Header')
Access-Control-Allow-Methods
默认情况下,CORS仅支持客户端发起GET,POST,HEAD请求。
如果客户端希望通过PUT,DELETE等方式请求服务器的资源,则需要在服务器端,通过Access-Control-Allow-Methods来指明实际请求所允许使用的HTTP方法
//只允许POST、GET、DELETE、HEAD请求方法
res.setHeader('Access-Control-Allow-Methods','POST,GET,DELETE,HEAD')
//允许所有的HTTP请求方法
res.setHeader('Access-Control-Allow-Methods','*')
CORS请求的分类
客户端在请求CORS接口时,根据请求方式和请求头不同,可以将CORS的请求分为
两大类,分别是:
- 简单请求
- 预检请求 简单请求和服务器之间只发生一次请求,预检请求则发生两次
简单请求
同时满足以下两大条件,就属于简单请求
- 请求方式:GET、POST、HEAD三者之一
- HTTP头部信息不超过以下几种字段:无自定义头部字段,Accept,Accept- Language,Content-Language,DPR,Downlink,Save-Data,Viewport-Width,Width,Content-Type (值仅限于text/plain,multipart/form-data,application/x-www-form-urlencoded三者之一)
预检请求
在浏览器与服务器正式通信前,浏览器会先发送OPTION请求进行预检,以
获知服务器是否允许该实际请求,所以这一次的OPTION请求称为"预检请
求",服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数
据。(谷歌浏览器无预检请求)
只要符合以下任何一个条件的请求,都需要进行预检请求 1.请求方式为GET、POST、HEAD之外的请求Method类型 2.请求头包含自定义头部字段 3.向服务器发送了application/json格式的数据
JSONP
概念:浏览器通过<script>标签的src属性,请求服务器上的数据,同时,服务器返回一个
函数的调用。这种请求数据的方式叫做JSONP。
特点:1.JSONP不属于真正的Ajax请求,因为它没有使用XMLHttpRequest这个对象。
2.JSONP只支持GET请求,不支持POST,PUT,DELETE请求
创建JSONP接口的注意事项
如果项目中已经配置了CORS跨域资源共享,为了防止冲突,必须配置CORS中间件之前
声明JSONP接口。否则JSONP接口会被处理成CORS的接口,
//攸县创建JSONP接口【这个借口不会被处理成CROS接口】
app.get('/api/jsonp',(req,res)=>{})
//再配置CROS中间件【后续所有接口,都会被处理成CROS接口】
app.use(cros())
//这是一个开启了CROS的接口
app.get('/api/get',(req,res)=>{})
实现JSONP接口的步骤
- 获取客户端发送过来的回调函数名字
- 得到要通过JSONP形式发送给客户端的数据
- 根据前两步得到的数据,拼接出一个函数调用的字符串
- 把上一步拼接得到的字符串,响应给客户端的
app.get('/api/jsonp',(req,res)=>{
//1. 获取客户端发送过来的回调函数名字
const funcName = req.query.callback
//2. 得到要通过JSONP形式发送给客户端的数据
const data = {name:'张三',age:22}
//3. 根据前两步得到的数据,拼接出一个函数调用的字符串
const scriptStr = ${funcName}(${JSON.stringify(data)})
//4. 把上一步拼接得到的字符串,响应给客户端的<script>标签进行解析执行
res.send(scriptStr)
})
调用$.ajax()函数,提供JSONP的配置选项,从而发起JSONP请求
$(#btnJSONP).on('click',function(){
$.ajax({
method:'GET',
url:'http://127.0.0.1/api/jsonp',
dataType:'jsonp', //表示发起的JSONP请求
success:function(res){
console.log(res)
}
})
})