如何在Serverless下配置Unsplash的缓存

刚好在修改陈旧项目的时候遇到了这个需求,需要对国外知名图站Unsplash做个缓存,加速图片的读取。

这个缓存最简单的方法就是用反代,但是反代图站很吃服务器的流量,而且前提是你要有一个服务器。这里我提供一个纯Serverless的方案,仅供参考。

该方案也可用于其他类似的情况。

CDN

任何一家支持指定回源Host的CDN都可以用来做这类图站缓存,我这里选用的是七牛云,整个方案是七牛云 + 腾讯云函数。

这个方案不是完全免费的,七牛HTTPS流量和腾讯云函数出网流量都不在免费额度内。

在流量不太吃紧的情况下可以考虑这个方案,更贴合国内场景,如果有其他需求或者流量这一块不怎么够用,那么可以考虑后面我提到的Cloudflare方案。

七牛这边按照常规设置CDN即可,需要注意的是HTTPS一定要打开,而且回源Host一定要指定成源站,也就是images.unsplash.com,不要用自己的域名回源。

考虑到图片基本上不会有什么变化,图片这个东西缓存可以调到最大,比如一年。觉得费用撑得住可以开图片瘦身。

重定向

除了图片的缓存外,我们还需要找个办法来解决source.unsplash.com提供的API的调用问题。这里面的所有API最终都会以302跳转到images.unsplash.com。

在浏览器环境下302这一步是被浏览器完全接管的,fetch可以拦截302但是取不到302后的url。

我们可以通过node环境来解决这个问题,在Serverless下首选的就是云函数,下面的例子只适用于腾讯云函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// Copyright (c) 2020 BackRunner
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT

'use strict';

// lib
const request = require('request');

// const
const CDN_HOST = '';

function sendRequest(url) {
return new Promise((resolve, reject) => {
const options = {
url: 'https://source.unsplash.com' + url,
followRedirect: false,
headers: {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/82.0.4051.0 Safari/537.36 Edg/82.0.425.3",
"Host": "source.unsplash.com"
}
};
request(options, function(err, response, body) {
if (err) {
resolve(packResponse('Request error'), true);
return;
}
if (response.statusCode == 302) {
resolve(packResponse(response.headers.location.replace('images.unsplash.com', CDN_HOST)));
} else {
resolve(packResponse('Not 302.'), true);
}
});
});
}

function packResponse(data, err=false) {
let ret = {};
if (err) {
ret.code = 500;
ret.error = data;
return ret;
}
ret.code = 200;
ret.data = data;
return ret;
}

exports.main_handler = async (event, context, callback) => {
if (!event.queryString.url) {
return packResponse('Url cannot be empty', true);
}
let ret = await sendRequest(event.queryString.url);
return ret;
};

这个代码复制到腾讯云函数中,设置好你的CDN_HOST,就可以使用了。由于腾讯云函数每一次发布API网关域名都会变动,我这里没有直接按照source.unsplash.com那些API的结构来安排这个接口的URL,你用?url=传入url参数。

Cloudflare

上述的方法是需要付费 + 需要备案,不够友好。有一个更好的办法可以做到这个事情 —— 全部用Cloudflare。

Cloudflare提供了一个每天10万次免费调用次数的Cloudflare Workers,搭建这个缓存需要使用2个Worker。

第一个Worker可以用WorkersProxy搭建一个反代代理images.unsplash.com,由于Worker本身就是运行在各个边缘节点的,已经可以起到一个加速作用,如果你不追求极致的速度没有必要再挂另外的CDN。

需要注意,由于images.unsplash.com的响应头中cache-control被设置为了max-age=0,它不会被代理缓存,

第二个Worker用于重定向images.unsplash.com到第一个Worker的代理上,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// Copyright (c) 2020 BackRunner
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT

addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})

function okResponse(data) {
return makeResponse("success", 200, data);
}

function errorResponse(data) {
return makeResponse("error", 500, data);
}

function makeResponse(status, code, data) {
return new Response(JSON.stringify({status: status, code: code, data: data}), {
status: 200,
headers: {
"Allow-Access-Origin-Control": "*",
"Content-Type": "application/json;charset=UTF-8"
}
});
}

/**
* Respond to the request
* @param {Request} request
*/

async function handleRequest(request) {
let url = request.url.replace(WORKER_HOST, "source.unsplash.com");
if (url.replace("https://source.unsplash.com/", "").length < 1) {
return errorResponse('Url cannot be empty');
}
let response = await fetch(url);
if (response.url.indexOf('images.unsplash.com') == -1) {
return errorResponse("Cannot apply redirect.");
}
return okResponse(response.url.replace('images.unsplash.com', CDN_HOST));
}

你需要在页面内设置CDN_HOST和WORKER_HOST两个环境变量,以完成重定向请求。

具体调用的方式和直接调用source.unsplash.com一样,只是域名切换为Worker的域名,总体上会有加速。