写在前面
相信大部分前端工程师在日常工作中经常使用 xhr 或者 fetch 从后端 api 里取数据然后进行二次处理,随后渲染到页面。
所以这个跨域问题也就屡见不鲜,当然在一些成熟的公司有自己的技术基础和储备这种问题都已经被处理掉一般不会遇到或者配置下就完事了,但并不是所有公司都有这个基础服务,所以这个时候就需要前端工程师自己来进行分析和处理。
所以本文就从这个角度来说下如何解决日常跨域问题,让我们更高效的和后端同学沟通,更快的解决问题完成工作目标。
现在网络上关于跨域的解析文章已经非常多了,再重复的说也很难说出花儿来。所以本文主要从发现问题和解决问题的思路出发,通过实际的代码来帮助大家更具象的理解和处理跨域。
当然跨域的解决方式多种多样,但本文主要说用的最多最灵活的(前端工作量最少)- CORS
。
跨域的理解
理解跨域不需要什么逻辑,跨域是浏览器的一种安全限制(同源策略),不允许脚本对其他域的资源进行直接访问,你能发请求但是浏览器会从中阻拦,要想解除这个限制就需要遵循一定的协议和规范办事(按规矩办事儿)就可以了。
什么情况下产生跨域
只要是 AJAX
要请求地址的端口、协议、域名(包括通过 ip 访问)只要其中一个不同就会产生跨域(你拿不到想要的数据)。
CORS介绍(真香)
CORS
是一个 W3C
标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
它允许浏览器向跨源服务器发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
完成 CORS 需要服务器和浏览器进行配合,除了 IE(IE10以下) 浏览器基本上都支持 。
对于前端来说实现 CORS 不需要做太多工作,都是浏览器自动完成的,浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS协议,就可以自由的进行数据交互了。
到这里理论的话就不说了,更详细的请看阮大佬的文章 http://www.ruanyifeng.com/blog/2016/04/cors.html。
下面开始结合代码复现问题和解决问题。
问题以及处理
准备工作
本机使用 node 和 koa2启动一个监听
8100
端口的web服务指定一个接口
/getdata
,并且返回json
数据使用中间件
koa-static
启动静态页面的访问模拟跨域,使用
localhost:8100
访问ip:8100/getdata
接口则产生跨域静态页面内使用 xhr 对接发起请求
node端
/**
* 服务入口
*/
var http = require('http');
var koaStatic = require('koa-static');
var path = require('path');
var koaBody = require('koa-body');
var Koa = require('koa2');
var app = new Koa();
var port = process.env.PORT || '8100';
//解析请求的数据为对象
app.use(koaBody());
//启动静态资源访问
app.use(koaStatic(
path.resolve(__dirname, '../static')
));
//跨域处理
app.use((ctx) => {
//指定一个接口和返回数据
var path =ctx.path;
if(path==='/getdata'){
ctx.body=JSON.stringify({
code:0,
msg:'success',
data:[]
});
}else{
ctx.body='welcome';
}
})
/**
* 启动 web 服务
*/
var server = http.createServer(app.callback());
server.listen(port);
console.log('server start ...... ');
html
<body>
<h2>跨域问题复现和解决</h2>
<h3>使用 get 或者 post 发送数据 </h3>
<h4>要发送的数据 a=1&b=2</h4>
<button type="button" id="btn">发送数据</button>
</body>
<script>
function xhrSend() {
var xhr = new XMLHttpRequest(); //创建对象
xhr.open('POST', 'http://localhost:8100/getdata', true);
xhr.setRequestHeader('content-type','application/x-www-form-urlencoded');
xhr.send('a=1&b=2');//发送数据
xhr.onreadystatechange = function () {
console.log('state change', xhr.readyState);
if (xhr.readyState == 4) {
var obj = JSON.parse(xhr.responseText); //转为 json 格式
console.log(obj);
}
}
}
document.getElementById('btn').addEventListener('click',function (e) {
xhrSend();
});
</script>
接口验证
同源请求时正常拿到数据。
场景
用 post或者 get 请求接口数据,结果控制台报如下错误。
提示我们产生了跨域,需要设置响应头 Access-Control-Allow-Origin
的值,把请求来源的 Origin
加进去。
报错解决
Origin
是什么?
支持 COSR
协议的浏览器会自动在请求头内携带这个头发给服务器,其值就是发起请求域的全写(协议+域名+端口)
服务端设置'Origin'
ctx.set('Access-Control-Allow-Origin',ctx.headers.origin);
Access-Control-Allow-Origin
是什么?
它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
//跨域处理
app.use((ctx) => {
console.log('receive req');
//指定一个接口和返回数据
var path =ctx.path;
if(path==='/getdata'){
//服务端通过 ctx.headers.origin 获取请求中的origin
+ ctx.set('Access-Control-Allow-Origin', ctx.headers.origin);
ctx.body=JSON.stringify({
code:0,
msg:'success',
data:[]
});
}else{
ctx.body='welcome';
}
})
问题解决
响应头里多了个这个 ,表示服务器允许这个地址进行数据请求
可以正确的得到数据
搞清楚个事儿
先回到上面的代码产生跨域的时候,ajax 的请求已经发出去了,而且 status
是200,服务端也收到了请求(我故意 log 了一个 receive data),但是浏览器的响应结果却是空的。
下图中看到,服务端已收到请求
推断:出现这种情况应该是浏览器给禁止了,其实浏览器得到了数据,但是因为跨域并没有把数据交给xhr对象。
既然这样那就抓包再验证下
从上图可以得到我们的猜测是正确的,还是浏览器做了手脚,所有的数据发送都正常进行了,只是最后关头浏览器插手了(安全机制)。
PS - 简单请求
虽然咱们上面只是处理 POST 的跨域,其实 GET、HEAD 是一样的,另外 Content-Type
为 application/x-www-form-urlencoded
,当然也适用于 multipart/form-data、text/plain
(文件上传和发送文本),有兴趣的可以自行修改下代码来进行在验证。
符合下列条件的都属于简单请求,上面的解决办法都适用。
最后
本小节到这里就介绍完了,简单请求的跨域处理你了解了吗?
但是上面的 node 端代码存在一个问题,不知道大家有没有发现?
发现的请留言哦,在后面的小节我会说明,到时候咱们继续聊~
本节源码已上传github
https://github.com/Bigerfe/fe-learn-code-guide/tree/master/src/cors-demo/demo1
扫码访问
理论知识参考
http://www.ruanyifeng.com/blog/2016/04/cors.html
更多精彩、好玩、有用的前端内容,请关注公众号《前端张大胖》