跨域那些事

6,189 阅读6分钟

最近在做一个音乐webapp的时候,遇到这样一个需求:提取歌曲图片的主题色,然后应用到全局
一开始的思路是把图片绘入到canvas中利用getImageData()获取图片的像素数据,分析这些数据得出最接近图片的颜色。接着问题来了,如果在canvas绘入跨域资源,canvas将受到污染,无法调用方法(因为数据都是在QQ音乐官网抓取的)。说到这,我们就说说前端跨域的那些事。
对项目感兴趣的点这里👉项目地址

什么是跨域

一般来说,当一个请求url的协议、域名、端口三者之间任意一个与当前页面地址不同即为跨域。最常见的就是在一个域名下的网页中,调用另一个域名中的资源。
当浏览器报这样的错的时候,就是跨域请求出问题了

为啥要跨域

主要是为了安全,浏览器采用同源策略,对js进行限制,防止恶意用户获取非法数据,同时还防止了大部分XSS攻击(就是向用户界面注入js脚本)。
浏览器的两种同源策略会造成跨域问题:

  • DOM同源策略。禁止对不同源的页面的DOM进行操作,主要包括iframe、canvas之类的。不同源的iframe禁止数据交互的,含有不同源数据的canvas会受到污染而无法进行操作。
  • XmlHttpRequest同源策略。简单来说就禁止不同源的AJAX请求,主要用来防止CSRF攻击。

跨域的时候浏览器为啥会报错

这是因为W3C推出的了一个标准----"跨域资源共享"(Cross-origin resource sharing),简称CORS。该标准定义了跨域访问资源时服务器和浏览器怎么通信。通俗讲就是浏览器在发现跨域请求的时候会附加一些头信息和服务器进行沟通,来确定跨域请求通不通过。现在除IE10以下的浏览器都支持这个标准
浏览器会把跨域请求分成两类:简单和非简单请求。

简单请求

简单请求有以下特征:

  1. 请求方法是以下之一
    • GET
    • POST
    • HEAD
  2. 头信息是以下字段之一
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type //该字段类型不能是application/json

同时满足以上两点的就是简单请求,其他就是非简单请求了。
当浏览器把跨域请求识别为简单请求的时候,就会在头信息里附加上一个Origin字段,该字段会把这次请求的来源(协议、域名、端口)带给服务器,服务器就会检查这个请求的来源。
要是服务器同意了这个来源呢,在正常回复浏览器的同时,就也附加上几条字段作为回礼:

  • Access-Control-Allow-Origin // 这条写着服务同意的来源,或者一个代表所有来源的 “*”
  • Access-Control-Allow-Credentials // 这条写着浏览器可以发Cookie过来了
    总的来说得到服务器的认可了,这样浏览器就能正常收到回应了

要是不同意,服务器就正常返回数据,啥也不附加,浏览器见不到Access-Control-Allow-Origin会不高兴的,然后就不给你返回的数据了,再然后就是报错,这个错就是上面那这样的(就是提取颜主题色的时候😡)。 而且状态码还是各种各样的,甚至有可能是200😫

非简单请求

这种不简单的请求,比如PUT或DELETE请求,还有 Content-Type字段类型为
application/json的。浏览器会严格一点,在发跨域请求前,会发个“预检”请求看看服务器的态度先,这个预检请求比较特殊,请求方式叫OPTIONS,头信息里不光有Origin字段还有这俩:

  • Access-Control-Request-Method // 这条是告诉服务器等会的跨域请求是啥方式
  • Access-Control-Request-Headers // 这条是浏览器跨域请求的时候要额外附加的信息

服务器收到预检请求提交过来的信息后,也会严格一点,不仅检查来源,还检查请求方式和头信息字段。
要是服务器同意了,就在正常的HTTP回应中附加上Access-Control-Allow-Origin字段,也同样写着服务同意的来源。
这就代表这拿到服务器的认可了,毕竟是经历过严格检查的,接下来的每次跨域请求都会正常进行。
要是不同意,服务器也是啥都不附加地正常回应,这个时候浏览器看不见Access-Control-Allow-Origin可是会生气的,连跨域请求都懒得发,直接报错。(这种情况还没碰到,就不上图啦)。
所以说报错是浏览器搞得鬼。

如何解决跨域问题

日常开发中会经常碰到跨域的问题,我们来看看常见的解决方法:

1.JSONP

像img、script等标签是没有跨域限制的,于是乎程序猿们就想到一个办法,动态创建script标签,通过src属性来进行跨域请求的来源

function fun(data) {
    console.log(data);
};
var body = document.getElementsByTagName('body')[0];
var script = document.gerElement('script');
script.type = 'text/javasctipt';
script.src = 'http://example.com?jsonp=cb';
body.appendChild(script);

返回的js脚本会直接执行,这样我们想要的数据就传了进来。
这种方法所有浏览器都兼容,前端可以很轻松的做到跨域请求,但也有一些缺点:

  • 只能通过GET方式请求,一方面是参数长度有限制,二是安全性比较差;
  • 后端需要知道前端的cb是什么样的结构,主要在参数和回调名;
  • 后端需要进行参数和cb的拼接然后才能执行;

本文的需求是要把图片绘入到canvas里,这个方法就行不通了,看下一种

2.服务器代理

服务器不像浏览器那样有跨域限制,可以让服务器去请求跨域资源然后再返回给客户端,就拿canvas操作跨域图片来说,客户端把跨域的url传给服务器,请求到图片后再传回客户端,就可以解决开头说到的那个问题了(上代码啦~)
图片是以二进制流的方式在http协议中传输,所以一定要注意编码格式,否则就返回一堆不知道是啥的东西啦
首先在后台起一个express服务,这里的get请求用的是npm中的https包

apiRoutes.get('/image', function (req, res) {
    const Url = (req.query)['0'];
    https.get(Url, function (response) {
        response.setEncoding('binary');  //二进制binary
        var type = response.headers["content-type"];
        let Data = '';
        response.on('data', function (data) {    //加载到内存
            Data += data;
        }).on('end', function () {          //加载完
            res.writeHead(200, { 'Access-Control-Allow-Origin': '*', "Content-Type": type });   //设置头,允许跨域
            res.end(new Buffer(Data, 'binary'));
        })
    })
});
app.use('/api', apiRoutes)

然后把跨域的图片url提交到这个路由上,就可以‘伪装’成同源图片啦

http://example.com/api/image?0=(跨域的图片url)

—————————————— 更新 11.17 ————————————————

关于主题色的提取在这里👉主题色提取

以上便是这次的文章分享了,欢迎留言相互学习~