使用CloudFlare搭建反向代理

[2024年12月03日] CloudFlare更新 服务条款 2.2.1
禁止包括但不限于导致(直接或间接)您的 Cloudflare 代理域的流量发送到非 Cloudflare 为该域分配的 IP 地址
禁止使用服务提供虚拟专用网络或其他类似的代理服务

若任使用请知晓可能出现的风险, 包括但不限于: 暂停或终止您对cloudflare服务的使用或访问 等等。

FREE套餐CloudFlare每日提供 10w 请求的免费额度,个人用完全足够,但并不能保证很多人同时使用,强烈建议仅自用

准备

  • 一个CloudFlare账号,如果没有就去CloudFlare注册 这不废话
  • 一个可以添加到CloudFlare的域名(可选),主要是CloudFlare的一些域名被墙,导致无法直接访问

创建并配置Workers

创建 Workers

在 Cloudflare 的面板左侧点击 Workers 和 Pages 进入后点击创建按钮

点击创建Workers按钮

名字随便填,我这里就直接填proxy了,然后点击部署

点击编辑代码进入代码编辑页面

在这里你有两个选择

  • 通用反代(优点:可直接反代任意网站,缺点:容易寄,如果泄露被滥用的几率大)
  • 指定反代(优点:稳定,不容易寄,缺点:需要一条条的配置需要反代的地址)

个人推荐使用指定反代,按需选择

指定反代

将以下代码覆盖掉左侧代码框的内容

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});

// 域名映射表
const domainMappings = {
"/steam-store": "https://store.steampowered.com",
"/steam-api": "https://api.steampowered.com"
};

const specialCases = {
"*": {
"Origin": "DELETE",
"Referer": "DELETE"
}
};

function handleSpecialCases(request) {
const rules = specialCases["*"];
for (const [key, value] of Object.entries(rules)) {
switch (value) {
case "KEEP":
break;
case "DELETE":
request.headers.delete(key);
break;
default:
request.headers.set(key, value);
break;
}
}
}

async function handleRequest(request) {
const url = new URL(request.url);

// 如果是访问根目录就返回一个HTML
if (url.pathname === "/") {
return new Response(`
<html>
<head>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f9f9f9;
}
h1 {
font-family: Arial, sans-serif;
color: #333;
text-align: center;
}
</style>
</head>
<body>
<h1>Why don't you play Genshin Impact?</h1>
</body>
</html>
`, {
headers: { "Content-Type": "text/html" }
});
}

const basePath = Object.keys(domainMappings).find(path => url.pathname.startsWith(path));
if (!basePath) {
return new Response("Path not found in domain mappings", { status: 404 });
}

const targetBase = domainMappings[basePath];
const targetPath = url.pathname.slice(basePath.length) + url.search + url.hash;

const targetUrl = new URL(targetPath, targetBase);

const modifiedRequest = new Request(targetUrl, {
headers: request.headers,
method: request.method,
body: request.body,
redirect: 'follow'
});

handleSpecialCases(modifiedRequest);

const response = await fetch(modifiedRequest);
const modifiedResponse = new Response(response.body, response);
modifiedResponse.headers.set('Access-Control-Allow-Origin', '*');

return modifiedResponse;
}

可修改 域名映射表(domainMappings) 中的内容为自己想要的网站

1
2
3
4
5
// 域名映射表
const domainMappings = {
"/steam-store": "https://store.steampowered.com",
"/steam-api": "https://api.steampowered.com"
}

其中,前面的部分是路由,就是 https://你的域名/路由 也就如果

访问 https://你的域名/steam-store 反代的就是 https://store.steampowered.com
访问 https://你的域名/steam-api 反代的就是 https://api.steampowered.com

其他同理,你可以添加也可以减少

通用反代

将以下代码覆盖掉左侧代码框的内容

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});

const specialCases = {
"*": {
"Origin": "DELETE",
"Referer": "DELETE"
}
};

function handleSpecialCases(request) {
const rules = specialCases["*"];
for (const [key, value] of Object.entries(rules)) {
switch (value) {
case "KEEP":
break;
case "DELETE":
request.headers.delete(key);
break;
default:
request.headers.set(key, value);
break;
}
}
}

async function handleRequest(request) {
const url = new URL(request.url);

// 如果是访问根目录就返回一个HTML
if (url.pathname === "/") {
return new Response(`
<html>
<head>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f9f9f9;
}
h1 {
font-family: Arial, sans-serif;
color: #333;
text-align: center;
}
</style>
</head>
<body>
<h1>Why don't you play Genshin Impact?</h1>
</body>
</html>
`, {
headers: { "Content-Type": "text/html" }
});
}

if (url.pathname.startsWith("/")) {
let targetUrl = url.pathname.slice(1);

// 自动补全协议(默认HTTPS)
if (!/^https?:\/\//.test(targetUrl)) {
targetUrl = "https://" + targetUrl;
}

// 动态生成URL
const modifiedRequest = new Request(targetUrl, {
headers: request.headers,
method: request.method,
body: request.body,
redirect: 'follow'
});

handleSpecialCases(modifiedRequest);

// 发送请求
const response = await fetch(modifiedRequest);
const modifiedResponse = new Response(response.body, response);

// 允许跨域
modifiedResponse.headers.set('Access-Control-Allow-Origin', '*');

return modifiedResponse;
}

return new Response("Path not found", { status: 404 });
}

直接在 https://域名 后面加反代的地址就行,也就是 https://域名/需要反代的网站 比如

访问 https://你的域名/https://store.steampowered.com 反代的就是 https://store.steampowered.com
访问 https://你的域名/https://api.steampowered.com 反代的就是 https://api.steampowered.com

部署

点击部署

在下方出现以下提示就可以啦

访问 https://你的域名 出现下面界面就成功啦

当然,有概率你是会出现网页无法访问,这就是CloudFlare的部分域名被墙了……

这就是需要下一步的原因

绑定域名

注册域名过程略

我们首先要将自己的域名 DNS 托管给 Cloudflare,返回在控制台选择“网站”选项卡,输入你注册的域名,点击继续

个人用选择FREE套餐即可,点击继续

点击继续前往激活

点击确认

将Cloudflare 给的名称服务器填在你注册域名的地方,这里不细说

填完后点击继续等待名称服务器更新(等待时间在几分钟到几个小时之间,不要急,成功后Cloudflare会发送邮件到你的邮箱)

刷新后域名的状态变为活动就可以了

使用域名

继续前往 Workers 和 Pages ,点击进入刚才创建的 Workers 服务

进入设置,在 域和路由 点击 添加

点击自定义域

输入任意的域名前缀+你的域名.例如proxy.example.com , 将example.com替换为你的域名

点击添加域即可

然后将 Cloudflare 的域名替换为自己的即可

拓展

即使指定反代依旧有泄露被滥用的风险,那就浅浅加个用户白名单吧(doge)

加了白名单的话通用反代方便一点

IP白名单

将下面的IP白名单换成你自己的服务器IP即可,极大的保证了不被滥用以及稳定不被墙

1
2
3
4
5
6
7
//IP白名单
const allowedIPs = [
// IPv4 示例
'123.123.123.123',
// IPv6 示例
'2001:0db8:85a3:0000:0000:8a2e:0370:7334'
];

通用反代

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});

const specialCases = {
"*": {
"Origin": "DELETE",
"Referer": "DELETE"
}
};
//IP白名单
const allowedIPs = [
// IPv4 示例
'123.123.123.123',
// IPv6 示例
'2001:0db8:85a3:0000:0000:8a2e:0370:7334'
];

function handleSpecialCases(request) {
const rules = specialCases["*"];
for (const [key, value] of Object.entries(rules)) {
switch (value) {
case "KEEP":
break;
case "DELETE":
request.headers.delete(key);
break;
default:
request.headers.set(key, value);
break;
}
}
}

async function handleRequest(request) {
const url = new URL(request.url);

// 获取用户 IP 地址(支持 IPv4 和 IPv6)
const userIP = request.headers.get('CF-Connecting-IP') || request.headers.get('X-Forwarded-For') || request.connection.remoteAddress;

// 检查 IP 是否在白名单内
if (!allowedIPs.includes(userIP)) {
return new Response(`
<html>
<head>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f9f9f9;
}
h1 {
font-family: Arial, sans-serif;
color: #333;
text-align: center;
}
</style>
</head>
<body>
<h1>Caused by playing Genshin Impact</h1>
</body>
</html>
`, {
headers: { "Content-Type": "text/html" }
});
}

// 如果是访问根目录就返回一个HTML
if (url.pathname === "/") {
return new Response(`
<html>
<head>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f9f9f9;
}
h1 {
font-family: Arial, sans-serif;
color: #333;
text-align: center;
}
</style>
</head>
<body>
<h1>Why don't you play Genshin Impact?</h1>
</body>
</html>
`, {
headers: { "Content-Type": "text/html" }
});
}

if (url.pathname.startsWith("/")) {
let targetUrl = url.pathname.slice(1);

// 自动补全协议
if (!/^https?:\/\//.test(targetUrl)) {
targetUrl = "https://" + targetUrl;
}

// 动态生成URL
const modifiedRequest = new Request(targetUrl, {
headers: request.headers,
method: request.method,
body: request.body,
redirect: 'follow'
});

handleSpecialCases(modifiedRequest);

// 发送请求
const response = await fetch(modifiedRequest);
const modifiedResponse = new Response(response.body, response);

// 允许跨域
modifiedResponse.headers.set('Access-Control-Allow-Origin', '*');

return modifiedResponse;
}

return new Response("Path not found", { status: 404 });
}

指定反代

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});

// 域名映射表
const domainMappings = {
"/steam-store": "https://store.steampowered.com",
"/steam-api": "https://api.steampowered.com"
};

const specialCases = {
"*": {
"Origin": "DELETE",
"Referer": "DELETE"
}
};
//IP白名单
const allowedIPs = [
// IPv4 示例
'123.123.123.123',
// IPv6 示例
'2001:0db8:85a3:0000:0000:8a2e:0370:7334'
];

function handleSpecialCases(request) {
const rules = specialCases["*"];
for (const [key, value] of Object.entries(rules)) {
switch (value) {
case "KEEP":
break;
case "DELETE":
request.headers.delete(key);
break;
default:
request.headers.set(key, value);
break;
}
}
}

// 获取用户 IP 地址
function getUserIP(request) {
return request.headers.get('CF-Connecting-IP') || request.headers.get('X-Forwarded-For') || request.connection.remoteAddress;
}

async function handleRequest(request) {
const url = new URL(request.url);

const userIP = getUserIP(request);

// 检查 IP 是否在白名单内
if (!allowedIPs.includes(userIP)) {
return new Response(`
<html>
<head>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f9f9f9;
}
h1 {
font-family: Arial, sans-serif;
color: #333;
text-align: center;
}
</style>
</head>
<body>
<h1>Caused by playing Genshin Impact</h1>
</body>
</html>
`, {
headers: { "Content-Type": "text/html" }
});
}

// 如果是访问根目录就返回一个HTML
if (url.pathname === "/") {
return new Response(`
<html>
<head>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f9f9f9;
}
h1 {
font-family: Arial, sans-serif;
color: #333;
text-align: center;
}
</style>
</head>
<body>
<h1>Why don't you play Genshin Impact?</h1>
</body>
</html>
`, {
headers: { "Content-Type": "text/html" }
});
}

const basePath = Object.keys(domainMappings).find(path => url.pathname.startsWith(path));
if (!basePath) {
return new Response("Path not found in domain mappings", { status: 404 });
}

const targetBase = domainMappings[basePath];
const targetPath = url.pathname.slice(basePath.length) + url.search + url.hash;

const targetUrl = new URL(targetPath, targetBase);

const modifiedRequest = new Request(targetUrl, {
headers: request.headers,
method: request.method,
body: request.body,
redirect: 'follow'
});

handleSpecialCases(modifiedRequest);

// 发送请求
const response = await fetch(modifiedRequest);
const modifiedResponse = new Response(response.body, response);

modifiedResponse.headers.set('Access-Control-Allow-Origin', '*');

return modifiedResponse;
}