感谢Cloudflare提供的免费服务,让本项目得以实现。

在此向Cloudflare致以最高的敬意。

前言

参考上一篇博客:使用 Cloudflare Workers 搭建随机图片 API,我搭建了随机P站图片API,结果博客上一直加载不出来。折腾了一个小时后搞定,记录一下方法。

问题

本地起Hexo服务器预览图片正常,本以为是缓存问题,强制刷新页面也没有用。

打开github.io托管版本是没有问题的,非常奇怪。

排查

请求分析

对比了一下图片的请求,发现:

  • 来自blog.esing.dev的图片请求没有附带上自己的Referer,因此被workers内的请求头检测拦下;
  • 来自zxis233.github.io的图片请求正常带上了自己的Referer,因此能正常显示。

这就很奇怪了,毕竟我访问blog-dsx.pages.dev时,图片也是能正常携带Referer的。问题可能出在HTTP的响应头上。

实际上RefererReferrer的错误拼写。由于早期HTTP规范中的拼写错误,为了保持向下兼容,只好将错就错。

HTTP头中的Referrer-Policy以及JavaScript和DOM中的referrer属性是拼写正确的。

果不其然,对比了一下,发现两个请求的“常规”选项有区别:

  • 来自blog.esing.dev的请求中,引用站点策略为same-origin
  • 来自zxis233.github.io的请求中,引用站点策略为strict-origin-when-cross-origin

这导致了两个请求非同源时,图片请求未带上自己的Referer。

何谓“同源”、何谓“同站”?

源(origin)= 协议(scheme)+ 主机名(hostname)+ 端口号(port)

站(site)= 有效顶级域名(eTLD)+1

其中,有效顶级域名即为dev/com/org这类,而 +1 表示的是和前面二级域名的组合,如baidu.com

下面举个简单的例子。以https://www.esing.dev为例,下面网站的关系是:

对比网址 是否同源 是否同站
https://www.baidu.com 否,因为 主机名 不同 否,因为 eTLD+1 不同
https://esing.dev 是,因为 子域名 不影响
https://music.esing.dev
http://www.esing.dev 否,因为 协议 不同 是,因为 协议 不影响
www.esing.dev:80 否,因为 端口 不同 是,因为 端口号不影响
www.esing.dev:443 是,完全匹配
www.esing.dev 是,完全匹配(默认为HTTPS访问) 是,因为 端口号 不影响

修改请求头

既然如此,我们修改一下主站为blog.esing.dev的请求头就行了。如何修改呢?

  1. 进入CF控制台,选择对应的域,我的是esing.dev
  2. 点击侧边栏的“规则—转换规则—修改响应头”
  3. 创建一个规则,表达式为(http.request.full_uri wildcard "https://blog.esing.dev/*"),将下列响应头设置为静态:
    • cross-origin-resource-policy: cross-origin
    • referrer-policy: strict-origin-when-cross-origin
  4. 大功告成!

或者直接点击下面链接:https://dash.cloudflare.com/redirect?zone=rules/transform-rules/modify-response-header ,会自动跳转到配置的地方。

后记

想起来之前配置live2d的API时也遇到了跨域的问题:

Access to fetch at 'https://my.live2d.api/live2d/switch/?id=1' from origin 'https://blog.esing.dev' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

因此要设置一下对应的响应头,加上这三条:

  • Access-Control-Allow-Methods: GET,HEAD,POST,OPTIONS
  • Access-Control-Allow-Origin: * (按理来说应当是注明允许的域,但是这个字段不支持多个域名,只能用*了)
  • Access-Control-Max-Age: 86400

Cloudflare Snippet

CF之前在内测Snippet,这玩意可以看做是动态的Origin Rules + Cache Rules + Configuration Rules + Page Rules + Transform Rules + Redirect Rules + …

官方的例子中就有“动态配置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
33
34
35
36
37
// Define CORS headers
const corsHeaders = {
"Access-Control-Allow-Origin": "*", // Replace * with your allowed origin(s)
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", // Adjust allowed methods as needed
"Access-Control-Allow-Headers": "Content-Type, Authorization", // Adjust allowed headers as needed
"Access-Control-Max-Age": "86400", // Adjust max age (in seconds) as needed
};

export default {
async fetch(request) {
// Make a copy of the request to modify its headers
const modifiedRequest = new Request(request);

// Handle preflight requests (OPTIONS)
if (request.method === "OPTIONS") {
return new Response(null, {
headers: {
...corsHeaders,
},
status: 200, // Respond with OK status for preflight requests
});
}

// Pass the modified request through to the origin
const response = await fetch(modifiedRequest);

// Make a copy of the response to modify its headers
const modifiedResponse = new Response(response.body, response);

// Set CORS headers on the response
Object.keys(corsHeaders).forEach((header) => {
modifiedResponse.headers.set(header, corsHeaders[header]);
});

return modifiedResponse;
},
};

只能说功能实在太强了。虽然免费账户只能享受每个域5条Snippet规则,但是也足够了,毕竟这玩意一个顶六个。啥时候能把页面规则给到5个就好了

现在还在Beta测中,期待正式上线。

后记

2024.12.16

Snippet上线了,然而仅限Pro计划使用,可惜。不知道后续会不会给免费计划名额呢?