利用 EdgeOne 边缘函数写入 Cloudflare IP GeoLocation 回源请求头以支持 WP-Statistics GeoIP
(标题很长,但这已经是我能想到的最短的叫法了)
前言
昨天群友发来一个 EdgeOne 领取免费版套餐兑换码的链接,竟然是 100% 中奖,每周 2000 份,每月更新(截止到现在也就兑了 350 份,基本是随便送了),一直很想把现有的国内腾讯云 CDN、国外 Cloudflare 的方案迁移到 EdgeOne 上,但奈何一直领不到免费版兑换码,这次终于如愿,遂赶紧噼里啪啦配置一顿,工作良好。
基本功能配置完毕,看了下 EdgeOne 有哪些额外功能,注意到 EdgeOne 支持携带客户端 IP 的地理位置信息回源,这让我想起来 Cloudflare 有一个名为 IP GeoLocation 的玩意儿,做了同样的事情,会把用户的国家码注入到 CF-IPCountry
请求头上给下游用(当然 EdgeOne 这里默认名称是 EO-Client-IPCountry
)。
这让我想起来我的 WP-Statistics 插件也支持 Cloudflare IP GeoLocation 作为位置检测方法,于是探索了一下怎么让 EO 支持这套东西。本来以为简单配配规则引擎上的回源请求头就行了,结果完全没这么简单... 兜兜转转整了一天,终于探索出一套解决方案,遂分享出来(绝对不是水博客!)
解决方案
此方案也适用于其他需要在 EdgeOne 上使用 Cloudflare IP GeoLocation 同款请求头的需求。
首先,根据 WP-Statistics 源代码中的 CloudflareGeolocationProvider,可以看到 WP-Statistics 通过 isAvailable
和 isBehindCloudflare
两个函数判断是否可以使用 Cloudflare IP GeoLocation 作为位置检测方式 —— 后者是初步判断,只有满足后者才能在设置中启用 IP GeoLocation;前者是最终判断,如果这个不为 true,即使你启用了 IP GeoLocation,也会被 fallback 到 MaxMind GeoIP 数据库的查询模式。
因此,取此二者函数所需要的请求头的并集,就得到了我们需要在 EO 上配置的回源请求头列表:
- HTTP_CF_CONNECTING_IP
- HTTP_CF_IPCOUNTRY
- HTTP_CF_IPCONTINENT
- HTTP_CF_REGION
- HTTP_CF_IPCITY
- HTTP_CF_IPLATITUDE
- HTTP_CF_IPLONGITUDE
- HTTP_CF_POSTAL_CODE
他们分别反映了访问者的连接 IP、所在国家、大洲、地区/州省、城市、经纬度、邮政编码(我不知道为什么他需要这个,而且事实上 EO 也提供不了这个,所以最后 Mock 了一个默认值)
但是很遗憾的是,EdgeOne 当前的规则引擎系统不支持配置地区和经纬度,所以这部分会交由边缘函数来做(别问我为什么边缘函数支持但是规则引擎不支持,另外也别问我为什么不全放到边缘函数上,规则引擎支持的大洲信息到边缘函数这儿又不支持了)。
于是,我们首先在规则引擎上配置如下修改回源请求头规则:
CF-IPCity: ${http.request.ip.city}
CF-IPCountry: ${http.request.ip.country}
CF-IPContinent: ${http.request.ip.continent}
CF-Connecting-IP: ${http.request.ip}
然后,在 EdgeOne 边缘函数上配置如下代码:
async function handleRequest(event) {
const { request } = event;
// 修改请求头
request.headers.set('cf-iplongitude', request.eo.geo.longitude);
request.headers.set('cf-iplatitude', request.eo.geo.latitude);
request.headers.set('cf-region', request.eo.geo.regionName);
request.headers.set('cf-region-code', request.eo.geo.regionCode);
request.headers.set('cf-postal-code', '000000'); // EO 不支持邮政编码,我也不知道 WP Statistics 拿这个干什么用,所以返回一个 000000
const response = await fetch(request);
return response;
}
addEventListener('fetch', event => {
// 当函数代码抛出未处理的异常时,边缘函数会将此请求转发回源站
event.passThroughOnException();
event.respondWith(handleRequest(event));
});
配置好边缘函数的触发规则后应该就可以正常运行了。最后,注意边缘函数免费版是有用量限制的,小心被用爆。
后记:边缘函数很好玩,顺带做了个 AVIF 转换器
这功能挺好的,直接贴代码,如果用户浏览器支持 avif 就会转换成 avif,不支持就转换成 webp,否则就不转(注意配置完后记得限定触发规则为仅当文件后缀为 jpg, jpeg, png, webp
时才触发,避免浪费):
async function handleEvent(event) {
const { request } = event;
// 获取客户端支持的图片类型
const accept = request.headers.get('Accept');
const option = { eo: { image: {} } };
// 检查客户端是否支持 WebP 格式的图片,若不支持响应原图
if (accept && accept.includes('image/webp')) {
option.eo.image.format = 'webp';
}
// 支持 Avif 优先走 Avif
if (accept && accept.includes('image/avif')) {
option.eo.image.format = 'avif';
}
const response = await fetch(request, option);
return response;
}
addEventListener('fetch', event => {
// 当函数代码抛出未处理的异常时,边缘函数会将此请求转发回源站
event.passThroughOnException();
event.respondWith(handleEvent(event));
});
配置好了以后网页访问速度有肉眼可见的提升,Lighthouse 的 Performance 分数也高了些,针不戳。