背景
项目中使用<img>
标签加载图片(图片资源来自外部AWS、Pinterest等)可以正常使用图片,但在canvas中使用时,<img>
加载过的图片就会报跨域问题。
问题分析
1.通过<img>
标签加载过的图片,浏览器会将其缓存起来;
2.当通过JavaScript代码再次访问同一个图片并且设置 crossOrigin
的跨域属性为 anonymous
,浏览器就不会再发起新的请求,而是直接访问缓存的图片。但是由于设置了crossorigin
,也就意味着它将要以 CORS 的方式请求,但缓存中的图片显然不是的,所以浏览器直接就拒绝了,连网络请求都没有发起。
3.如果在Chrome devtools中勾选disable cache,js是可以正常请求到图片的,这也进一步验证了第2点。
一些尝试
通过上面的分析,总的说来就是同一个图片资源的crossorigin属性不同,而缓存没有更新导致的,那把crossorigin设置成相同的是不是就OK了?
1.都不设置跨域,<img>不变,canvas中的crossorigin = "undefined"
尽管没有CORS授权也可以在 canvas 中使用图像, 但这样做就会污染(taints)画布。 只要 canvas 被污染, 就不能再从画布中提取数据, 也就是说不能再调用 toBlob(), toDataURL() 和 getImageData() 等方法, 否则会抛出安全错误(security error).
这实际上是为了保护用户的个人信息,避免未经许可就从远程web站点加载用户的图像信息,造成隐私泄漏。 --- 跨域图片资源权限(CORS enabled image)
2.都设置 crossorigin = "anonymous":
可行,需要服务端支持,设置 Access-Control-Allow-Origin: *
解决方案
毕竟这些跨域的图片都是外部资源,也不能直接设置支持跨域,于是我们决定加一个代理服务器。
一个图片原始的url是
https://litb-us-original.s3.amazonaws.com/*****.jpg
经过代理后的url变成
http://pc-cors.elitb.com/proxy?url=https://litb-us-original.s3.amazonaws.com/*****.jpg
const request = require('request');
const fs = require('fs');
const express = require('express');
const router = express.Router();
router.get('/proxy', async (req, res) => {
const data = req.query;
const dir = `${__dirname}/images`;
let url = '', fileName = '';
// 做一些url及文件名的解析,过程略
//...
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Credentials', true);
request(url).pipe(fs.createWriteStream(dir + filename)).on('close', () => {
res.sendFile(dir + filename, () => {
fs.unlinkSync(dir + filename)
})
});
})
stream
在js代码中,如果我们读取数据或者是打印数据,一般是var 或 let 一个变量,再打印出来,但是这样的方法需要开辟一个新的内存,来保存这个变量,当我们在网页上读取内存较大的文件时(比如视频、图片等),写入写出会极大的占用内存,这时候就需要在node.js中提供给我们的流——stream,stream可以边读边写,这样就可以更好的不占用太多内存。
stream分为四种:readable、writable、duplex、transform
pipe在stream中很常用,用法可以总结为:
readStream.pipe(writeStream);
const fs = require('fs');
const readStream = fs.createReadStream('./data/input.txt');
const writeStream = fs.createWriteStream('./data/output.txt');
readStream.pipe(writeStream);
一些失败的方案
const request = require('request');
const express = require('express');
const router = express.Router();
router.get('/proxy', async (req, res) => {
const data = req.query;
let url = '',;
// 做一些url的解析,过程略
//...
request(url, (err, response, data) => {
const contentType = response?.headers['content-type'];
res.setHeader('Content-Type', contentType);
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Credentials', true);
res.send(data);
})
})