node代理解决图片跨域问题

背景

项目中使用<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);
    })
})
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容