基础
首先你要有个 Cloudflare 账户,这是必须的。需要注意的是 Cloudflare 的 Workers 一天只有 10
万次免费额度,不过已经够用了。
Hello World!
登录到 Cloudflare 的面板,先点击右上角的 English(US)
并换成 简体中文
点击左上角的 菜单
-> Workers
进入到 Workers
页面。新注册的用户会提示设置一个 workers.dev
顶级域名下的二级子域名,这个子域名设置好之后是可更改的,之后你新创建的 Worker 就会使以这个域名而二级子域名开始的。
设置好二级子域名之后选择 Free
套餐计划————这样可以白嫖,然后进入到 Worker 管理界面,创建一个新的 Worker
然后在 Script
输入框里填入以下代码。worker.js
代码可以参考 xiaozhu20007/CFWorkers.
const t_url = "https://raw.githubusercontent.com"; // 目标
addEventListener("fetch", (event) => {
event.respondWith(
handleRequest(event.request).catch(
(err) => new Response(err.stack, { status: 500 })
)
);
});
async function handleRequest(request) {
const { pathname, search } = new URL(request.url);
let html = await fetch(t_url + pathname + search);
return html;
}
修改好代码之后点击左下角的 Save and Deploy
然后 Preview
看看页面是否显示正常,如果显示正常恭喜你成功啦。
进阶
优化 Google Analytics 使用体验
代码如下:
/* 转载并修改自 https://raw.githubusercontent.com/SukkaW/cloudflare-workers-async-google-analytics/master/worker.js */
//const AllowedReferrer = 'skk.moe'; // ['skk.moe', 'suka.js.org'] multiple domains is supported in array format
addEventListener("fetch", (event) => {
event.respondWith(response(event));
});
async function senData(event, url, uuid, user_agent, page_url) {
const encode = (data) => encodeURIComponent(decodeURIComponent(data));
const getReqHeader = (key) => event.request.headers.get(key);
const getQueryString = (name) => url.searchParams.get(name);
const reqParameter = {
headers: {
Host: "www.google-analytics.com",
"User-Agent": user_agent,
Accept: getReqHeader("Accept"),
"Accept-Language": getReqHeader("Accept-Language"),
"Accept-Encoding": getReqHeader("Accept-Encoding"),
"Cache-Control": "max-age=0",
},
};
const pvData = `tid=${encode(getQueryString("ga"))}&cid=${uuid}&dl=${encode(
page_url
)}&uip=${getReqHeader("CF-Connecting-IP")}&ua=${user_agent}&dt=${encode(
getQueryString("dt")
)}&de=${encode(getQueryString("de"))}&dr=${encode(
getQueryString("dr")
)}&ul=${getQueryString("ul")}&sd=${getQueryString("sd")}&sr=${getQueryString(
"sr"
)}&vp=${getQueryString("vp")}`;
const perfData = `plt=${getQueryString("plt")}&dns=${getQueryString(
"dns"
)}&pdt=${getQueryString("pdt")}&rrt=${getQueryString(
"rrt"
)}&tcp=${getQueryString("tcp")}&srt=${getQueryString(
"srt"
)}&dit=${getQueryString("dit")}&clt=${getQueryString("clt")}`;
const pvUrl = `https://www.google-analytics.com/collect?v=1&t=pageview&${pvData}&z=${getQueryString(
"z"
)}`;
const perfUrl = `https://www.google-analytics.com/collect?v=1&t=timing&${pvData}&${perfData}&z=${getQueryString(
"z"
)}`;
await fetch(pvUrl, reqParameter);
await fetch(perfUrl, reqParameter);
}
async function response(event) {
const url = new URL(event.request.url);
const getReqHeader = (key) => event.request.headers.get(key);
const Referer = getReqHeader("Referer");
const user_agent = getReqHeader("User-Agent");
const ref_host = (() => {
try {
return new URL(Referer).hostname;
} catch (e) {
return "";
}
})();
let needBlock = false;
needBlock =
!ref_host ||
ref_host === "" ||
!user_agent ||
!url.search.includes("ga=UA-")
? true
: false;
if (
typeof AllowedReferrer !== "undefined" &&
AllowedReferrer !== null &&
AllowedReferrer
) {
let _AllowedReferrer = AllowedReferrer;
if (!Array.isArray(AllowedReferrer)) _AllowedReferrer = [_AllowedReferrer];
const rAllowedReferrer = new RegExp(_AllowedReferrer.join("|"), "g");
needBlock = !rAllowedReferrer.test(ref_host) ? true : false;
console.log(_AllowedReferrer, rAllowedReferrer, ref_host);
}
if (needBlock) {
return new Response("403 Forbidden", {
headers: { "Content-Type": "text/html" },
status: 403,
statusText: "Forbidden",
});
}
const getCookie = (name) => {
const pattern = new RegExp("(^| )" + name + "=([^;]*)(;|$)");
const r = (getReqHeader("cookie") || "").match(pattern);
return r !== null ? unescape(r[2]) : null;
};
const createUuid = () => {
let s = [];
const hexDigits = "0123456789abcdef";
for (let i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
s[8] = s[13] = s[18] = s[23] = "-";
return s.join("");
};
const _uuid = getCookie("uuid");
const uuid = _uuid ? _uuid : createUuid();
// To sent data to google analytics after response id finished
event.waitUntil(senData(event, url, uuid, user_agent, Referer));
// Return an 204 to speed up: No need to download a gif
let response = new Response(null, {
status: 204,
statusText: "No Content",
});
if (!_uuid)
response.headers.set(
"Set-Cookie",
`uuid=${uuid}; Expires=${new Date(
new Date().getTime() + 365 * 86400 * 30 * 1000
).toGMTString()}; Path='/';`
);
return response;
}
如此,我们还可以用 CFW 做到镜像网站同时限定指定人访问:
// 替换成你想镜像的站点
const upstream = 'google.com'
// 如果那个站点有专门的移动适配站点,否则保持和上面一致
const upstream_mobile = 'm.google.com'
// 密码访问
const openAuth = false //开启?
const username = 'username' //用户名?
const password = 'password' //密码?
// 禁止哪些国家访问
const blocked_region = ['RU']
// 禁止 IP 访问
const blocked_ip_address = ['0.0.0.0', '127.0.0.1']
// 替换成你想镜像的站点
const replace_dict = {
'$upstream': '$custom_domain',
'//google.com': ''
}
function unauthorized() {
return new Response('Unauthorized', {
headers: {
'WWW-Authenticate': 'Basic realm="CloudFlareWorkers"',
'Access-Control-Allow-Origin': '*'
},
status: 401
});
}
function parseBasicAuth(auth) {
try {
return atob(auth.split(' ').pop()).split(':');
} catch (e) {
return [];
}
}
function doBasicAuth(request) {
const auth = request.headers.get('Authorization');
if (!auth || !/^Basic [A-Za-z0-9._~+/-]+=*$/i.test(auth)) {
return false;
}
const [user, pass] = parseBasicAuth(auth);
return user === username && pass === password;
}
async function fetchAndApply(request) {
if (request.method === 'OPTIONS') // allow preflight request
return new Response('', {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, HEAD, OPTIONS'
}
});
if (openAuth && !doBasicAuth(request)) {
return unauthorized();
}
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_host = url.host;
if (url.protocol == 'http:') {
url.protocol = 'https:'
response = Response.redirect(url.href);
return response;
}
if (await device_status(user_agent)) {
upstream_domain = upstream
} else {
upstream_domain = upstream_mobile
}
url.host = upstream_domain;
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.href);
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;
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');
const content_type = new_response_headers.get('content-type');
if (content_type.includes('text/html') && content_type.includes('UTF-8')) {
original_text = await replace_response_text(original_response_clone, upstream_domain, url_host);
} else {
original_text = original_response_clone.body
}
response = new Response(original_text, {
status,
headers: new_response_headers
})
}
return response;
}
addEventListener('fetch', event => {
event.respondWith(fetchAndApply(event.request).catch(err => {
console.error(err);
new Response(JSON.stringify(err.stack), {
status: 500,
headers: {
'Content-Type': 'application/json'
}
});
}));
})
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;
}
通过 fetch
我们还可以实现多聚合 CDN(聚合多个 S3 储存,使用 CFW 隐藏桶的公开访问地址,通过路径指向不同的 S3 服务):
const b2csEndpoint = 'https://<桶名称>.s3.<桶地域>.backblazeb2.com'
const bitfulEndpoint = 'https://<桶名称>.s3.ladydaily.com'
async function cdnHandler(pathname) {
var url_pathname = pathname
console.info(url_pathname)
if (url_pathname.startsWith('/b2cs')) {
var file = await fetch(b2csEndpoint + url_pathname.split('/b2cs')[1]).text()
// ! DEBUGGER !
console.log(file)
// ! DEBUGGER !
return new Response(file);
} else if (url_pathname.startsWith('/s4')) {
var file = await (await fetch(bitfulEndpoint + url_pathname.split('/s4')[1])).text()
return new Response(file);
} else if (url_pathname.startsWith('/npm')) {
// /npm/package@version/file
console.log('https://cdn.jsdelivr.net/npm' + url_pathname.split('/npm')[1])
var file = await fetch('https://cdn.jsdelivr.net/npm' + url_pathname.split('/npm')[1])).text()
return new Response(file);
} else {
return new Response(`<?xml version="1.0" encoding="UTF-8"?><Error><Code>AccessDenied</Code><Message>Access Denied.</Message></Error>`, { status: 404 });
}
}
export default {
/**
* @param {Request} req
*/
async fetch(req) {
try {
const { url, pathname, search } = new URL(req.url)
switch (pathname) {
case '/':
return new Response(null, { status: 204 });
default:
return await cdnHandler(pathname, search);
}
} catch (err) {
/** @type {Error} */
let e = err;
return new Response(`Error: ${e.toString()}`, { status: 500 });
}
},
};
好的,那么这篇内容就到这里啦!感谢您的阅读,有任何问题欢迎评论指出~