使用Cloudflare Workers配置反向代理


前段时间无意之间在网上看到文章说可以用CloudFlare的Workers做反向代理,因为比赛和考试之类的事情太多就把这事忙忘了。这几天实训周稍微闲下来一点,想起来这事,就查了下相关资料和文章,顺便做个记录。

CloudFlare Workers简介

CloudFlare WorkerCloudFlare 的边缘计算服务。开发者可通过 JavaScriptCDN 进行编程,从而能灵活处理 HTTP 请求。这使得很多任务可在 CDN 上完成,无需自己的服务器参与。

实现原理

通过在 Workers 的 边缘计算节点直接无服务器运行 JavaScript 代码以实现反向代理。

配置方法

  1. 登录 Cloudflare ,进入 Workers 选项卡。第一次使用会提示自定义 Workers 域名

  2. 新建一个 Workers,并将以下 JavaScript 粘贴进文本框并保存。

注意:将upstreamupstream_mobile改成你需要进行反代的域名

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

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

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

// Countries and regions where you wish to suspend your service.
const blocked_region = ['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'
}

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
})

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;
}
  1. 直接访问你所设置的链接即可(例如你的 Workers 名叫 test,则链接应为 test.yourname.workers.dev)

与Nginx反向代理的比较

几年前我曾经在维基百科好友User:Argon Pub的帮助下通过 Nginx 进行了对维基百科的反向代理。
个人对两种方式的反向代理理解都不够深,在此记录一下个人对两种方式对比的拙见。
Workers 方式进行反向代理配置优点在于配置方便、简单、操作易上手,缺点在无法登陆且只能反向代理一些内容比较简单的网站;
Nginx 方式进行反向代理则可以解决 Workers 方式的问题,但同时也引入了新的问题,例如域名需要自己准备,服务器也同样需要自行购买,虽然可以对JS进行高度自由配置解决类似登陆等一系列问题但对新手来说过于复杂,难以上手。
只能说见仁见智了。

参考资料

Workers Docs - CloudFlare
使用Cloudflare Workers反代国外网站 - 煎饼果子 (CC BY-4.0方式共享许可)
Cloudflare Workers简单设置轻松访问外面世界 - 苏耀峰 (CC BY-4.0方式共享许可)