Hello World

吞风吻雨葬落日 欺山赶海踏雪径

0%

Cloudflare Worker 代理 Bangumi API

Bangumi 的部分域名(api.bgm.tvnext.bgm.tvbangumi.tv)在当前网络环境下无法直接访问,通过 Cloudflare Worker 做反向代理来解决。

背景

Kazumi 项目依赖 Bangumi 开放 API 获取番剧元数据、评论、角色信息等。涉及三个上游域名:

域名 用途
api.bgm.tv 基础 API(搜索、条目信息、剧集、角色)
next.bgm.tv Next API(每日放送、趋势、评论、Staff)
bangumi.tv 官网资源(头像占位图、表情图片、页面链接)

三个域名在内网环境均不可达,需要统一代理。

方案设计

使用一个 Cloudflare Worker,通过路径前缀区分上游:

1
2
3
bangumi.mydomain.com/api/*  →  api.bgm.tv/*
bangumi.mydomain.com/next/* → next.bgm.tv/*
bangumi.mydomain.com/bgm/* → bangumi.tv/*

为什么用路径前缀而不是子域名

Cloudflare Worker 免费版每个 worker 绑定一个路由,用路径前缀可以在单个 worker 内处理多个上游,无需额外配置多条 DNS 记录和路由规则。

Worker 实现

核心逻辑:

  1. 路由解析 — 根据 pathname 的前缀匹配上游域名
  2. URL 重写 — 去掉路径前缀,拼接上游地址和 query string
  3. Header 过滤 — 移除 Cloudflare 注入的头(cf-connecting-ip 等),设置正确的 Host
  4. CORS 支持 — 所有响应附加跨域头,前端/移动端可直接调用
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
const UPSTREAM_MAP = {
'/api': 'https://api.bgm.tv',
'/next': 'https://next.bgm.tv',
'/bgm': 'https://bangumi.tv',
};

const DEFAULT_UPSTREAM = 'https://api.bgm.tv';

const FORBIDDEN_HEADERS = [
'host', 'cf-connecting-ip', 'cf-connecting-ipv6',
'cf-ipcountry', 'cf-ray', 'cf-visitor', 'cf-worker',
'cf-cache-status', 'x-real-ip', 'x-forwarded-for', 'x-forwarded-proto',
];

function resolveUpstream(pathname) {
for (const [prefix, origin] of Object.entries(UPSTREAM_MAP)) {
if (pathname === prefix || pathname.startsWith(prefix + '/')) {
return { origin, prefix };
}
}
return { origin: DEFAULT_UPSTREAM, prefix: '' };
}

function buildTargetURL(request) {
const url = new URL(request.url);
const { origin, prefix } = resolveUpstream(url.pathname);
const target = new URL(
url.pathname.substring(prefix.length) + url.search, origin
);
target.protocol = 'https:';
return { targetURL: target, upstreamHost: new URL(origin).host };
}

处理请求时过滤敏感头并附加 CORS:

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
async function handleRequest(event) {
const request = event.request;
const url = new URL(request.url);

if (request.method === 'OPTIONS') {
return handleOPTIONS();
}

const { targetURL, upstreamHost } = buildTargetURL(request);
const filteredHeaders = filterRequestHeaders(request.headers, upstreamHost);

const originRequest = new Request(targetURL.toString(), {
method: request.method,
headers: filteredHeaders,
body: request.method !== 'GET' && request.method !== 'HEAD'
? request.body : null,
redirect: 'follow',
});

const response = await fetch(originRequest);
const newHeaders = withCORSHeaders(response.headers);

return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHeaders,
});
}

客户端适配

Kazumi 项目中所有 Bangumi 相关的 URL 统一改为走代理:

1
2
3
4
5
6
7
8
9
// lib/request/api.dart
/// Bangumi 官网(走代理)
static const String bangumiIndex = 'https://bangumi.mydomain.com/bgm/';

/// bangumi API Domain(代理 api.bgm.tv)
static const String bangumiAPIDomain = 'https://bangumi.mydomain.com/api/';

/// Bangumi Next API Domain(代理 next.bgm.tv)
static const String bangumiAPINextDomain = 'https://bangumi.mydomain.com/next';

图片资源等硬编码 URL 也需要同步替换:

1
2
3
4
5
// 替换前
NetworkImage('https://bangumi.tv/img/info_only.png')

// 替换后
NetworkImage('https://bangumi.mydomain.com/bgm/img/info_only.png')

部署

  1. 在 Cloudflare Dashboard → Workers 中创建 Worker
  2. 粘贴脚本,保存并部署
  3. 绑定自定义域名(如 bangumi.mydomain.com

注意事项

  • bangumi.tv 的资源(图片、页面)通过 /bgm/ 前缀代理,路径映射要确保去掉前缀后与原始路径一致
  • Worker 设置 redirect: 'follow',上游如有重定向会自动跟随
  • 如果还有其他 Bangumi CDN 域名不可达(如 lain.bgm.tv),可以按相同模式加 /lain 前缀