使用Cloudflare Workers反代加速任意网站

Cloudflare Workers简介

Cloudflare 推出了 Workers 服务,在国内一般叫它边缘计算。之前介绍的利用又拍云边缘规则为图片增加水印其实也是这个原理。

Cloudflare Workers 的名称来自 Web Workers,更具体地说是 Service Workers,一个用于在 web 浏览器后台运行并拦截 HTTP 请求的脚本的 W3C 标准 API。Cloudflare Workers 是针对相同的标准 API 编写的,但是是在 Cloudflare 的服务器上运行,而不是在浏览器中运行。

Cloudflare Workers反代加速

Workers-Proxy 项目,可以通过 Cloudflare Workers 服务搭建一个反向代理服务器,这个反向代理服务器不需要安装在我们自己的 VPS 主机上,而是直接部署在 Cloudflare 节点上,这可以极大的降低我们自身 VPS 主机的性能损耗。

部署步骤

  1. 导航至 Cloudflare Workers ,注册并登录你的 Cloudflare 帐户,然后为 worker 设置自定义域名,然后创建一个新的 Worker 。
  2. 访问打开 “ src / index.js ” ,将代码粘贴到 Cloudflare 刚刚新创建的 Worker ,然后在编辑器中以替换默认配置。
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
// 代理网站
const upstream = 'www.google.com'

// 代理网站的目录
const upstream_path = '/'

// 手机用户代理网站
const upstream_mobile = 'www.google.com'

// 屏蔽国家和地区
const blocked_region = ['CN', 'KP', 'SY', 'PK', 'CU']

// 屏蔽 IP 地址
const blocked_ip_address = ['0.0.0.0', '127.0.0.1']

// 源站是否开启 HTTPS
const https = true

// 是否允许浏览器缓存
const disable_cache = false

// 文本替换
const replace_dict = {
'$upstream': '$custom_domain',
'//google.com': ''
}
  1. 更改 Worker 的名称,保存并部署它,最后检查其性能是否满足你的需求。

绑定自定义域名

  1. 检查域名是否接入 Cloudflare.
  2. 跳转到域名的控制面板, 选择 'Workers' 页面, 点击 'Add Route'.
  3. Route 中输入 https://<自定义域名>/* 并且选择刚创建的 Worker.
  4. 为自定义域名添加 CNAME DNS 记录. 在 DNS 页面中, 在 'Name' 区域输入自定义域名的子域名 (或者 @), 在 'Target' 区域输入 Worker 的二级域名 (例如 test.workers.dev), 将代理状态选择为 '代理'.

部署多个域名

如果被反代的网站使用其他域名的静态资源时, 可以部署多个 Workers-Proxy 并配置文本替换.

  1. www.google.com 使用位于 www.gstatic.com 的静态资源
  2. 部署 Workers-Proxy A, 用于代理 www.gstatic.com
  3. 部署 Workers-Proxy B, 用于代理 www.google.com
  4. 配置 Workers-Proxy B 的文本替换:
1
2
3
4
const replace_dict = {
'$upstream': '$custom_domain',
'www.gstatic.com': '<Workers-Proxy A 的域名>'
}

配置模板

Pornhub

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
// Website you intended to retrieve for users.
const upstream = 'www.pornhub.com'

// Custom pathname for the upstream website.
const upstream_path = '/'

// Website you intended to retrieve for users using mobile devices.
const upstream_mobile = 'www.pornhub.com'

// Countries and regions where you wish to suspend your service.
const blocked_region = ['CN', 'KP', 'SY', 'PK', 'CU']

// IP addresses which you wish to block from using your service.
const blocked_ip_address = ['0.0.0.0', '127.0.0.1']

// Whether to use HTTPS protocol for upstream address.
const https = true

// Whether to disable cache.
const disable_cache = false

// Replace texts.
const replace_dict = {
'$upstream': '$custom_domain',
'//ci.phncdn.com': '//ciphncdncom.pornproxy.workers.dev', // replace this with your other deployment.
'var scriptText': '', // Issue #30
'trafficjunky.com': '',
'trafficjunky.net': '',
'contentabc.com': '',
'Ads By Traffic Junky': '',
'cdn1d-static-shared.phncdn.com/iframe-1.1.5.html': '',
'/ads/iframe-mobile-3.0.0.html': '',
'<iframe': '<!--',
'</iframe>': '-->',
'Remove Ads': ''
}

Github

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
// Website you intended to retrieve for users.
const upstream = 'github.com'

// Custom pathname for the upstream website.
const upstream_path = '/'

// Website you intended to retrieve for users using mobile devices.
const upstream_mobile = 'github.com'

// Countries and regions where you wish to suspend your service.
const blocked_region = ['CN', 'KP', 'SY', 'PK', 'CU']

// IP addresses which you wish to block from using your service.
const blocked_ip_address = ['0.0.0.0', '127.0.0.1']

// Whether to use HTTPS protocol for upstream address.
const https = true

// Whether to disable cache.
const disable_cache = true

// Replace texts.
const replace_dict = {
'$upstream': '$custom_domain'
}

Google

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
// Website you intended to retrieve for users.
const upstream = 'www.google.com'

// Custom pathname for the upstream website.
const upstream_path = '/'

// Website you intended to retrieve for users using mobile devices.
const upstream_mobile = 'www.google.com'

// Countries and regions where you wish to suspend your service.
const blocked_region = ['CN', 'KP', 'SY', 'PK', 'CU']

// IP addresses which you wish to block from using your service.
const blocked_ip_address = ['0.0.0.0', '127.0.0.1']

// Whether to use HTTPS protocol for upstream address.
const https = true

// Whether to disable cache.
const disable_cache = false

// Replace texts.
const replace_dict = {
'$upstream': '$custom_domain',
'//google.com': ''
}

总结

优点:

  • 用 Cloudflare Workers加速任意网站, 无需购买或配置服务器;
  • 可以用来做静态资源 CDN..不用把域名接入 Cloudflare ;
  • 可以根据 user-agent 屏蔽部分地区或 IP;
  • 可以绑定自定义域名;

缺点:

  • workers 一天免费 10 万次请求,注意在workers中可能会提示:“Cannot read property ‘toUpperCase’ of null”的错误,在githun的issue中有人提示说是正常现象。

完整代码

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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// 代理网站
const upstream = 'www.google.com'

// 代理网站的目录
const upstream_path = '/'

// 手机用户代理网站
const upstream_mobile = 'www.google.com'

// 屏蔽国家和地区
const blocked_region = ['CN', 'KP', 'SY', 'PK', 'CU']

// 屏蔽 IP 地址
const blocked_ip_address = ['0.0.0.0', '127.0.0.1']

// 源站是否开启 HTTPS
const https = true

// 是否允许浏览器缓存
const disable_cache = false

// 文本替换
const replace_dict = {
'$upstream': '$custom_domain',
'//google.com': ''
}

// 以下无需修改
addEventListener('fetch', event => {
event.respondWith(fetchAndApply(event.request));
})

async function fetchAndApply(request) {
const region = request.headers.get('cf-ipcountry').toUpperCase();
const ip_address = request.headers.get('cf-connecting-ip');
const user_agent = request.headers.get('user-agent');

let response = null;
let url = new URL(request.url);
let url_hostname = url.hostname;

if (https == true) {
url.protocol = 'https:';
} else {
url.protocol = 'http:';
}

if (await device_status(user_agent)) {
var upstream_domain = upstream;
} else {
var upstream_domain = upstream_mobile;
}

url.host = upstream_domain;
if (url.pathname == '/') {
url.pathname = upstream_path;
} else {
url.pathname = upstream_path + url.pathname;
}

if (blocked_region.includes(region)) {
response = new Response('Access denied: WorkersProxy is not available in your region yet.', {
status: 403
});
} else if (blocked_ip_address.includes(ip_address)) {
response = new Response('Access denied: Your IP address is blocked by WorkersProxy.', {
status: 403
});
} else {
let method = request.method;
let request_headers = request.headers;
let new_request_headers = new Headers(request_headers);

new_request_headers.set('Host', upstream_domain);
new_request_headers.set('Referer', url.protocol + '//' + url_hostname);

let original_response = await fetch(url.href, {
method: method,
headers: new_request_headers
})

connection_upgrade = new_request_headers.get("Upgrade");
if (connection_upgrade && connection_upgrade.toLowerCase() == "websocket") {
return original_response;
}

let original_response_clone = original_response.clone();
let original_text = null;
let response_headers = original_response.headers;
let new_response_headers = new Headers(response_headers);
let status = original_response.status;

if (disable_cache) {
new_response_headers.set('Cache-Control', 'no-store');
}

new_response_headers.set('access-control-allow-origin', '*');
new_response_headers.set('access-control-allow-credentials', true);
new_response_headers.delete('content-security-policy');
new_response_headers.delete('content-security-policy-report-only');
new_response_headers.delete('clear-site-data');

if (new_response_headers.get("x-pjax-url")) {
new_response_headers.set("x-pjax-url", response_headers.get("x-pjax-url").replace("//" + upstream_domain, "//" + url_hostname));
}

const content_type = new_response_headers.get('content-type');
if (content_type != null && content_type.includes('text/html') && content_type.includes('UTF-8')) {
original_text = await replace_response_text(original_response_clone, upstream_domain, url_hostname);
} else {
original_text = original_response_clone.body
}

response = new Response(original_text, {
status,
headers: new_response_headers
})
}
return response;
}

async function replace_response_text(response, upstream_domain, host_name) {
let text = await response.text()

var i, j;
for (i in replace_dict) {
j = replace_dict[i]
if (i == '$upstream') {
i = upstream_domain
} else if (i == '$custom_domain') {
i = host_name
}

if (j == '$upstream') {
j = upstream_domain
} else if (j == '$custom_domain') {
j = host_name
}

let re = new RegExp(i, 'g')
text = text.replace(re, j);
}
return text;
}


async function device_status(user_agent_info) {
var agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
var flag = true;
for (var v = 0; v < agents.length; v++) {
if (user_agent_info.indexOf(agents[v]) > 0) {
flag = false;
break;
}
}
return flag;
}