最近工作中遇到了一个canvas绘制image的错误。是由于canvas在使用其他源的图片的时候,造成跨域导致,遇到这种问题一般我们只需要将图片的crossorigin设置下,然后请求响应头也需要设置access-control-allow-origin,即可解决问题。但是我遇到的问题比这要复杂一些,因为在前端和服务端都设置好的情况下,我的问题依然存在,依然报跨域。接下来我将详细解释相关内容。
因为问题产生的根本原因时浏览器的**同源限制(same-origin-policy)**导致,所以我将简要介绍下这个概念。
浏览器安全的根基就是同源限制,何为同源限制呢,当且仅当两个域名满足以下三点,我们认为这两个域名是同源的:
有一点需要注意的是,标准规定的端口相同的才同源,但是浏览器并没有遵循这一项规定,也就是说如果两个网站协议相同,域名相同但是端口不相同,那么浏览器也认为其是同源的,例如http:://www.pengfeixc.com:8000
和http://www.pengfeixc.com:8001
这两个地址只有端口不一样,它们是不同源的,但是由于浏览器并未遵循这一规定,所以浏览器认为它们是同源的,所以它们是可以互相读取cookie的。
同源限制的目的是为了保证互联网的用户安全信息的,如果没有同源限制,不同源的网站可以互相读取cookie的话,那么会很危险的。设想一下,如果网站A把用户信息存储在cookie中,如果没有同源限制的情况下,网站B也可以读取网站A存储的cookie来获取用户信息,如果是比较重要的用户信息,那么后果将不敢想象。
所以出现了CORS(cross origin resource share),即跨域资源共享。在请求非同源资源的时候必须通过CORS方式请求。 关于CORS,我就不在展开了,它就是一种请求非同源资源的方式。可以在https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS 上获取详细信息。
html5中一些html元素增加了crossorigin属性,提供了对cors的支持。例如<audio>
、<img>
、<link>
、<script>
和<video>
都可以通过设置crossorigin
属性来改变普通请求方式为cors请求。以img为例
<!-- 以非跨域请求方式请求/1.jpg -->
<img src="/1.jpg" alt="图片" />
<!-- 以cors请求方式请求http://www.example.com/image/1.jpg -->
<img src="http://www.example.com/image/1.jpg" alt="图片" crossorigin="anonymous" />
crossorigin有如下取值:
anonymous:执行一个cors请求,但是该请求不会发送相关证书,例如cookie。服务器需要相应的设置Access-control-Allow-Origin响应头,图片才是未被污染的。
use-credentials: 执行一个cors请求,该请求会发送相关证书,携带cookie和http基本验证信息。服务器需要相应设置Access-Control-Allow-Origin响应头。
如果没有显示设置crossorigin属性,那么请求会是非跨域请求,如果图片来自非同源地址,那么该图片认为是被污染的。如果我们在canvas上绘制该图片,那么canvas将被认为是污染(tainted)的,被污染的canvas不能使用如下方法:
总结就是我们不能在被污染的canvas上获取图片相关信息,所以要避免这中问题,就需要设置图片的crossorigin属性,当然服务端也需要设置响应头。
我在查阅了很多资料后,image的crossorigin属性也设置了,一般应该是没有问题了,但是浏览器还是报错,图片跨域!!!
整个人快整懵了,崩溃了。后来发现罪魁祸首是浏览器缓存导致的。因为我在css样式中提前引入了非同源地址的图片作为背景图,代码类似如下:
{
background: url("http://www.example.com/1.jpg");
}
背景图的url与当前网站是非同源的,css在请求的时候是以普通请求(非cors方式)请求的,所以请求到的图片是污染的图片,在canvas中直接使用后,肯定是不能获取该图片数据的(getImageData、toBlob和toDataURL方法报错)。虽然我在canvas中使用的图片已经设置过了crossorigin属性但是其地址也是http://www.example.com/1.jpg
,与我上面用到的背景图地址相同,谷歌浏览器为了用户体验,直接使用缓存的图片,也就是背景图,但是这背景图是以非同源方式请求的啊。这下该怎么办。我又不能设置css背景图以cors方式请求,html并没有提供类似的corssorigin属性来设置background image。所以要么我更改背景图片,要么另寻他法。
发现了问题,就要去解决。在查找了大量的资料,最后找到了一个方法,就是在每次请求图片的时候加一个timestamp参数,这样可以强制浏览器不使用缓存的图片。每次在canvas上绘制图片时,重新请求的图片地址加上当前的时间戳参数。
`http:://www.example.com/1.jpg?timestamp=${Date.now()}`
因为每次请求的地址不一样,所以浏览器不可能在从缓存中找到它认为相同的图片了。至此问题终于被解决了😄。
(完)