前端小伙伴在使用AJAX的时候,相信对No Access-Control-Allow-Origin header
这样的报错提示感到很头疼,怎么请求又跨域了。文章的开始,让咱们从一个小故事开始。。。javascript
在开发中,前端的童鞋们每次看到浏览器下面出现一长串红色的跨域报错就会很恼火,不停的念叨着:那个谁谁谁,又没有给我加跨域头;后端小伙伴又会绝不示弱地反击道:不就是Access-Control-Allow-Origin: *
么?已经有了啊!那为何还会报错?确定是你没加好!前端
因而,一场甩锅大战即将开始...java
说实话,每个先后端开发都应该要了解跨域的用法。ios
前端的小伙伴可能会以为跨域问题应该都是后端接口来处理的,可是若是多了解一些HTTP请求响应头的,可以更快的定位问题,更快的解决接口异常,方便排查调试,因此但愿可以耐下心把这篇文章看完。面试
在MDN中,对跨域是这么解释的:ajax
跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不一样源服务器上的指定的资源。当一个资源从与该资源自己所在的服务器不一样的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。express
简单来讲就是当你向不一样“域”的服务器发起网络请求的时候,这个请求就跨域了。这里不一样“域”指的是不一样的协议、域名、端口,有任何一个不一样时,浏览器都视为跨域。咱们在使用postman、fiddler等一些工具模拟发起http请求的时候,不会遇到跨域的状况;当咱们在浏览器中请求不一样域名的时候,虽然请求正常发出了,可是浏览器在请求返回时会进行一系列的校验,判断这次请求是否“合法”;若是不合法,返回结果就被浏览器拦截了。json
咱们在进行POST或其余跨域请求时,会发现只有一个OPTIONS请求,并无咱们想要的请求方法。axios
咱们没有发送OPTIONS请求,那么它是从哪里来的呢?它的名称叫CORS请求预检,首先来看一下官方对它的定义是:后端
HTTP的OPTIONS方法用于获取目的资源所支持的通讯选项。客户端能够对特定的URL使用OPTIONS方法,也能够对整站(经过将 URL 设置为“*”)使用该方法。
选项 | 是否容许 | 备注 |
---|---|---|
Request has body | No | 没有请求体 |
Successful response has body | No | 成功的响应有响应体 |
Safe | Yes | 安全 |
Idempotent | Yes | 密等性,不变性,同一个接口请求多少次都同样 |
Cacheable | No | 不能缓存 |
Allowed in HTML forms | No | 不能在表单里使用 |
根据官网的文档,咱们发现它没有请求体,不能设置data,也不能直接发起OPTIONS请求。简言之,OPTIONS请求是用于请求服务器对于某些接口等资源的支持状况的,包括各类请求方法、头部的支持状况,仅做查询使用。
让咱们详细地看一下OPTIONS请求的真实面目吧,咱们首先构造一个POST请求:
var instance = axios.create({
baseURL: 'http://192.168.0.100:8081'
})
instance({
url: '/post',
method: 'post',
data:{
url: 'xieyufei.com'
}
})
复制代码
能够看到OPTIONS请求头很简单,都没有请求的body,有两个字段Access-Control-Request-Headers
和Access-Control-Request-Method
是新出现的,下面会说到这两个字段的用法;那么何时会触发OPTIONS请求呢,这里涉及到两种CORS请求。
浏览器将CORS请求分红两类:简单请求(simple request)和非简单请求(not-so-simple request),简单请求不会触发CORS预检请求。
只要同时知足如下两大条件,就属于简单请求:
请求方法是如下三种方法之一
HTTP的头信息不超出如下几种字段
所以咱们只要把上面的请求加一个请求头Content-Type
,就能不触发OPTIONS请求。
instance({
url: '/post',
method: 'post',
headers:{
'Content-Type': 'application/x-www-form-urlencoded'
},
data:{
url: 'xieyufei.com'
}
})
复制代码
下面,咱们的重点来了,咱们在进行ajax请求时,通常都会在请求头加一下自定义的数据,所以大多数请求都是非简单请求。非简单请求涉及如下几个请求和响应的头部的字段:
字段名 | 位置 | 用法 | 备注 |
---|---|---|---|
Origin | 请求头 | origin | 代表预检请求或实际请求的源站 |
Access-Control-Request-Method | 请求头 | method | 将实际请求所使用的 HTTP 方法告诉服务器。 |
Access-Control-Request-Headers | 请求头 | field-name[, field-name]* | 将实际请求所携带的头部字段告诉服务器。 |
Access-Control-Allow-Origin | 响应头 | origin or * | 对于不须要携带身份凭证的请求,服务器能够指定该字段的值为通配符,表示容许来自全部域的请求 |
Access-Control-Allow-Methods | 响应头 | method[, method]* | 指明了实际请求所容许使用的 HTTP 方法。 |
Access-Control-Allow-Headers | 响应头 | field-name[, field-name]* | 指明了实际请求中容许携带的头部字段。 |
Access-Control-Allow-Credentials | 响应头 | true | 指定了当浏览器的credentials设置为true时是否容许浏览器读取response的内容 |
Access-Control-Max-Age | 响应头 | delta-seconds | 指定了请求的结果可以被缓存多久 |
在上面的OPTIONS请求中咱们能够发现表格中的三个请求头部都在该次请求中出现了,Access-Control-Request-Method
和Access-Control-Request-Headers
用来询问服务器,下面我要用POST方法和Content-Type头部来请求,你就说你答不答应吧?
在服务器端,咱们能够这么写来容许请求跨域:
const express = require('express')
const app = express()
const PORT = 8081
let allowCrossDomain = function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Methods', 'POST')
res.header('Access-Control-Allow-Headers', 'content-type')
next()
}
app.use(allowCrossDomain)
app.post('/post', (req, res) => {
res.json({
msg: 'hi post'
})
})
app.listen(PORT)
复制代码
这里有咱们后端小伙伴很熟悉的Access-Control-Allow-Origin: *
,用来代表全部的origin都容许跨域,至关于告诉浏览器:
这样咱们就能看到咱们期待已久的POST请求,同时返回的头部信息中带上了CORS的响应头;同时咱们能够看到axios默认的Content-Type
是application/json;charset=UTF-8
,不在仅限的三个值中,所以会触发OPTIONS请求。
除了content-type,咱们还能够在请求头中添加一些本身定义的信息,好比须要传给后台的token之类的。
//浏览器端
instance({
url: '/put',
method: 'put',
headers:{
'X-Custom-Header': 'xieyufei-head'
},
data:{
url: 'xieyufei.com'
}
})
//服务器端
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Methods', 'POST,PUT')
res.header('Access-Control-Allow-Headers', 'content-type, X-Custom-Header')
复制代码
默认状况下,Cookie是不包括在CORS的请求中,但有时候咱们又须要用到Cookie来传输数据,这时候咱们的Access-Control-Allow-Credentials
字段就派上用处了,另外一方面,须要在AJAX请求中打开withCredentials属性;咱们再把代码进行以下改造:
//服务器端
res.header('Access-Control-Allow-Credentials', 'true')
//浏览器端
instance({
url: '/put',
method: 'put',
//新增withCredentials
withCredentials: true,
headers:{
'X-Custom-Header': 'xieyufei-head'
},
data:{
url: 'xieyufei.com'
}
})
复制代码
当咱们满怀期待打开浏览器准备接收Cookie时,却发现又报错了:
通过对错误信息仔细阅读,发现此次报错跟上面的跨域报错不彻底同样,大概的意思是当请求的身份凭证包括的时候,Access-Control-Allow-Origin
不能是通配符'*'(wildcard)。所以咱们大概了解到了错误的缘由是在通配符上面,咱们对代码再进行一下改造:
const cookieParser = require('cookie-parser');
app.use(cookieParser())
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Methods', 'POST')
res.header('Access-Control-Allow-Headers', 'content-type, X-Custom-Header')
res.header('Access-Control-Allow-Credentials', 'true')
app.post('/post', (req, res) => {
console.log(req.cookies, 'cookie')
res.json({
msg: 'hi post'
})
})
复制代码
这时候就能看到咱们想要的Cookie了。
CORS内容其实来讲不是不少,也比较简单,可是考验动手实践能力,面试时通常也会问到,所以经过express搭建服务器来加深对CORS知识的了解。
更多文章请关注个人公众号:前端壹读。