使用Cloudflare Workers搭建gravatar镜像服务
搭建一个Gravatar镜像站可以有效减少访问延迟,特别是对于那些位于Gravatar服务器较远地区的用户。使用Cloudflare Workers来实现这个目标是非常合适的,因为Workers可以在全球分布的边缘节点上执行代码,从而显著降低图片加载时间。下面是如何使用Cloudflare Workers创建一个简单的Gravatar镜像站的步骤。
步骤 1: 创建 Cloudflare Worker
首先,你需要在Cloudflare控制面板中创建一个新的Worker脚本。
步骤 2: 编写 Worker 脚本
接下来,在Worker脚本中编写代码以处理请求并代理到Gravatar服务。以下是一个基本示例:
示例代码
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
| addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)) })
async function handleRequest(request) { const url = new URL(request.url); const emailHash = url.searchParams.get('email') || ''; const size = url.searchParams.get('size') || '80';
if (!emailHash) { return new Response('No email hash provided.', { status: 400 }); }
const gravatarUrl = `https://secure.gravatar.com/avatar/${emailHash}?s=${size}&d=identicon`;
const response = await fetch(gravatarUrl);
return new Response(response.body, { status: response.status, headers: { ...response.headers, 'Cache-Control': 'public, max-age=31536000, immutable' } }); }
|
这段代码会根据请求中的email
和size
参数构建Gravatar URL,并将请求代理到Gravatar服务。它还设置了适当的缓存头,以便图像可以在用户的浏览器和其他CDN节点上长期缓存。
步骤 3: 部署 Worker
保存并部署你的Worker脚本。你可以通过Cloudflare控制面板或者使用Wrangler CLI工具来完成这一步。
步骤 4: 配置 DNS 和 CNAME
确保你有一个域名指向Cloudflare,并且为该域名配置了CNAME记录,指向你刚才创建的Worker。例如,如果你希望镜像站在gravatar.yourdomain.com
下运行,则需要设置一个CNAME记录,将其指向your-worker-name.workers.dev
。
步骤 5: 测试你的镜像站
现在,你可以通过访问类似https://gravatar.yourdomain.com/?email=hash&size=80
这样的URL来测试你的Gravatar镜像站是否正常工作。
可选增强功能
- HTTPS 强制:确保所有的请求都是通过HTTPS进行的,以保护数据传输的安全。
- 默认头像支持:除了
identicon
外,Gravatar还支持其他类型的默认头像(如monsterid
、wavatar
等)。你可以添加额外的查询参数来支持这些选项。
- 图片格式转换:如果需要,可以通过修改Worker脚本来支持不同的图片格式(如WebP),以提高页面加载性能。
- 错误处理:改进错误处理逻辑,比如当Gravatar返回非200状态码时提供备用图片或自定义错误消息。
通过上述步骤,你可以快速地利用Cloudflare Workers搭建一个高效、低延迟的Gravatar镜像站。这不仅有助于提升网站性能,还可以改善用户体验。
优化思路
gravatar的请求参数并非一成不变,通过解析请求参数构建访问url的形式,很容易失效,因此,我考虑修改为直接透传参数的形式,即:
优化代码
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
| addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)) })
async function handleRequest(request) { const url = new URL(request.url); const gravatarUrl = new URL(url.pathname + url.search, 'https://secure.gravatar.com');
const cache = caches.default; let response = await cache.match(gravatarUrl);
if (!response) { response = await fetch(gravatarUrl, { cf: { cacheEverything: true, cacheTtl: 60 * 60 * 24 * 365, bypassCacheOnCookie: false, ttlOverride: 60 * 60 * 24 * 365 } });
cache.put(gravatarUrl, response.clone()); }
return response; }
|
在这个脚本中并没有对请求中的查询参数进行任何解析或修改,而是直接使用了原始的pathname和search部分来构建新的Gravatar URL。这保证了所有查询参数都能被完整地传递给Gravatar服务。这种方法不仅简化了开发过程,还充分利用了Cloudflare的边缘网络优势。
PS:workers的默认域名workers.dev
在国内无法直接访问,需要设置自定义域名。
20241214更新
使用过程中发现,上面的脚本可以直接访问gravatar.com的首页,为了避免不必要的麻烦,对代码进行一下优化,访问首页的请求直接返回500错误。
优化代码
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
| addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)) })
async function handleRequest(request) { const url = new URL(request.url);
if (url.pathname === '/') { return new Response('Home page access not allowed.', { status: 500 }); }
const gravatarUrl = new URL(url.pathname + url.search, 'https://secure.gravatar.com');
const cache = caches.default; let response = await cache.match(gravatarUrl);
if (!response) { response = await fetch(gravatarUrl, { cf: { cacheEverything: true, cacheTtl: 60 * 60 * 24 * 365, bypassCacheOnCookie: false, ttlOverride: 60 * 60 * 24 * 365 } });
cache.put(gravatarUrl, response.clone()); }
return response; }
|
继续优化
用KV存储代替浏览器缓存,用于保存图片,减少源站请求次数,缓存时间1年。就不考虑换头像了,服务自用,不再公开链接。
优化后的代码
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
| addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)); });
const GRAVATAR_KV = globalThis.gravatar;
const ONE_YEAR_IN_SECONDS = 60 * 60 * 24 * 365;
async function handleRequest(request) { const url = new URL(request.url);
if (url.pathname === '/') { return new Response('Home page access not allowed.', { status: 500 }); }
const gravatarUrl = new URL(url.pathname + url.search, 'https://secure.gravatar.com');
const gravatarHash = gravatarUrl.pathname.split('/').pop();
const { metadata, value } = await GRAVATAR_KV.getWithMetadata(gravatarHash, 'arrayBuffer');
let contentType; let kvResponse = value;
if (kvResponse) { contentType = metadata?.contentType || 'application/octet-stream'; } else { const response = await fetch(gravatarUrl);
contentType = response.headers.get('content-type') || 'application/octet-stream'; const buffer = await response.arrayBuffer(); await GRAVATAR_KV.put(gravatarHash, buffer, { metadata: { contentType }, expirationTtl: ONE_YEAR_IN_SECONDS });
kvResponse = buffer; }
const headers = new Headers(); headers.set('Content-Type', contentType); return new Response(kvResponse, { headers }); }
|