老人已经84天没有打到鱼了,但他仍然选择出海。
通过本文你将学到什么?
- CORS解决跨域的几种模式
- 根据实例为你展示响应首部字段的意义
- 了解跨域的 OPTIONS 请求
相关资料
涉及资料就不往文档里放了,在这里贴出
- 跨源资源共享(CORS)
- OPTIONS
- Access-Control-Allow-Origin
- Access-Control-Allow-Headers
- Access-Control-Allow-Credentials
- Access-Control-Request-Method
跨域环境准备
准备前端服务:http://localhost:8080
准备接口服务:http://localhost:3000
(koa2)
简单请求 的跨域
定义
“简单请求”请求不会触发 CORS 预检请求,若请求满足所有下述条件,则该请求可视为“简单请求”:
-
使用以下方法之一:
- GET
- POST
- HEAD
-
除了被用户代理自动设置的首部字段(例如
Connection
,User-Agent
)和在 Fetch 规范中定义为 禁用首部名称 的其他首部,允许人为设置的字段为Fetch
规范定义的 对 CORS 安全的首部字段集合。该集合为:- Accept
- Accept-Language
- Content-Language
- Content-Type (需要注意额外的限制)
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
-
Content-Type 的值仅限于下列三者之一:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
-
请求中的任意
XMLHttpRequestUpload
对象均没有注册任何事件监听器;XMLHttpRequestUpload
对象可以使用XMLHttpRequest.upload
属性访问。(本文不探讨) -
请求中没有使用
ReadableStream
对象。(本文不探讨)
来一个场景模拟
前端 get
请求
this.$axios.get('http://localhost:3000', {
...
}).then(res => {
console.log(res)
})
复制代码
服务端代码:
const Koa = require("koa");
const debug = require('debug')('corsTest:');
const app = new Koa();
app.use(async (ctx, next) => {
ctx.body = "你好,koa";
next()
});
app.listen(3000);
复制代码
浏览器 ?:
意思:所请求的资源上没有 Access-Control-Allow-Origin 标头
服务端处理办法:
这种场景只需服务端设置响应头 Access-Control-Allow-Origin
const Koa = require("koa");
const debug = require('debug')('corsTest:');
const app = new Koa();
app.use(async (ctx, next)=> {
// 设置服务可支持的 origin,
// * 支持所有 origin
ctx.set('Access-Control-Allow-Origin', '*');
await next();
});
app.use(async (ctx, next) => {
ctx.body = "你好,koa";
next()
});
app.listen(3000);
复制代码
浏览器结果 ? ? :
非简单请求 的跨域
不满足 简单请求 定义条件的都是 非简单请求, 上文定义中已说明。
场景一:非限定的 Content-Type
前端 post
请求
this.$axios.post('http://localhost:3000/create', {
...
}).then(res => {
console.log(res)
})
复制代码
服务端增加一个 post 接口:
const Koa = require("koa");
const debug = require('debug')('corsTest:');
const app = new Koa();
app.use(async (ctx, next)=> {
// 设置服务可支持的 origin
ctx.set('Access-Control-Allow-Origin', '*');
await next();
});
app.use(async (ctx, next) => {
if (ctx.method === 'POST') {
switch(ctx.url) {
case '/create': //增加一个接口
ctx.body = { message: "请求成功"}
break;
default:;
}
}
if (ctx.method === 'GET') {
ctx.body = "你好,koa";
}
next()
})
app.listen(3000);
复制代码
浏览器结果 ? :
ps: 这里有同学会疑问?️,前面不是说到 get 、 post 、 head 这三种类型为简单请求吗?这个 post 请求怎么还跨域了呢?
⚠️注意报错信息:
has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.
意思:在预请求的 Access-Control-Allow-Headers 响应首部中 content-type
的设置是不支持的!
这也应证的 非简单请求的定义中的第三条,由于当前请求 content-type
值包含 application/json
(axios 插件默认设置),不在规定三种范围内。
服务端处理办法:
app.use(async (ctx, next)=> {
// 设置服务可支持的 origin
ctx.set('Access-Control-Allow-Origin', '*');
// 设置服务可支持的请求首部名字
ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With');
await next();
});
复制代码
浏览器 ??:
场景二: 非简单请求 Method
前端发送 DELETE
请求:
this.$axios.delete('http://localhost:3000', {
...
}).then(res => {
console.log(res)
})
复制代码
服务端代码增加:
app.use(async (ctx, next) => {
if (ctx.method === 'POST') {
switch(ctx.url) {
case '/create':
ctx.body = { message: "请求成功"}
break;
default:;
}
}
if (ctx.method === 'GET') {
ctx.body = "你好,koa";
}
if (ctx.method === 'DELETE') { //增加一个 delete 请求
ctx.body = "你好,koa delete";
}
next()
})
复制代码
浏览器结果 ??:
意思:在预请求的 Access-Control-Allow-Methods 响应首部中 DELETE
是不支持的!
这也应证的 非简单请求的定义中的第一条, 请求方法不在 简单请求 规定范围内。
服务端处理办法:
app.use(async (ctx, next)=> {
// 设置服务可支持的 origin
ctx.set('Access-Control-Allow-Origin', '*');
// 设置服务可支持的请求首部方法
ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
await next();
});
复制代码
浏览器 ???:
场景三: 自定义请求首部字段
前端请求:
this.$axios.get('http://localhost:3000', {
headers: {
custom: 11
}
}).then(res => {
console.log(res)
})
复制代码
服务端: 上面代码不做变动
浏览器???:
⚠️报错信息:
CORS policy: Request header field custom is not allowed by Access-Control-Allow-Headers in preflight response.
意思:在预请求的 Access-Control-Allow-Headers 响应首部中 custom
字段是不支持的!
服务端增加处理办法:
app.use(async (ctx, next)=> {
// 设置服务可支持的 origin
ctx.set('Access-Control-Allow-Origin', '*');
// 设置服务可支持的请求首部方法
ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
// 设置服务可支持的请求首部名字, 添加一个 custom
ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With, custom');
await next();
});
复制代码
浏览器 ????:
附带身份凭证请求 的跨域
如果前端想携带 cookie 发送跨域请求
前端请求:
document.cookie = 'str=test'; // 设置cookie
this.$axios.get('http://localhost:3000', {
headers: {
custom: 11
},
withCredentials: true // 增加设置
}).then(res => {
console.log(res)
})
复制代码
服务端: 以上代码不做变动
cookie 状态:
浏览器????:
⚠️报错信息:
CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
意思:当前预请求中包含认证模式,响应首部中 Access-Control-Allow-Origin
的值不能为通配符 ‘*
’, XMLHttpRequest 发起的认证模式请求被 withCredentials
属性控制 !
服务端增加处理办法:
第一步:设置 Access-Control-Allow-Origin 为当前请求 origin
第二步:设置 Access-Control-Allow-Credentials 为 true
app.use(async (ctx, next)=> {
// 第一步
const origin = ctx.header['origin'] || '*';
ctx.set('Access-Control-Allow-Origin', origin);
// 第二步
ctx.set('Access-Control-Allow-Credentials', true)
// 设置服务可支持的请求首部方法
ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
// 设置服务可支持的请求首部名字, 添加一个 custom
ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With, custom');
await next();
});
复制代码
浏览器 ?????:
服务端 ctx 上下文已经收到 cookie:
总结
扩展
跨域之后服务端还能收到请求吗??
结论在前:
- 简单请求的跨域: 服务端能收到请求,并返回正常数据;
- 非简单请求:服务端只能正常收到 OPTIONS 请求;
在和小伙伴讨论这个问题之前,想想自己平时跨域问题出现时,在浏览器里报错红的一批,自然反应就是,请求没有发出去,就算发出去了服务收到我的请求,也没有给我返回数据??
代码验证:
前端代码:
this.$axios.get('http://api.fd***-inc.com', {
...
}).then(res => {
console.log(res)
})
复制代码
浏览器表现:
看响应也是空的:
我们看到这就是一个简单请求的跨域,而且浏览器查看 response 是没有返回数据的。
这个时候我们需要用抓包工具验证一下,(我用的 Charles ):
可以看到接口是正常返回数据。
用了挺长时间总结了一下,到这就结束啦!涉及的内容还是挺多的,不对的地方多多指教??