引言
起因是,想反代Wikipedia和YouTube等网站给一位不能翻墙的朋友看,于是接触到了CF Workers,最初想到的方案其实是Nginx反代,但是性价比是很低的,CF Workers有免费方案并且不需要VPS,一切由边缘服务器完成.
CF Workers潜力无限,用处远不止这些,有待深入学习研究吧.
什么是反代?
用最通俗易懂的话解释:
翻墙是正代,CDN是反代.
正向代理隐藏真实客户端,反向代理隐藏真实服务端。
反向代理是位于 Web 服务器前面的服务器,其将客户端(例如 Web 浏览器)请求转发到这些 Web 服务器。反向代理通常用于帮助提高安全性、性能和可靠性。为了更好地理解反向代理的工作原理以及它可以提供的好处,我们来首先定义什么是代理服务器。
什么是边缘计算
直接在遍布全球的CDN服务器上运行程序.
边缘计算是一种致力于使计算尽可能靠近数据源、以减少延迟和带宽使用的网络理念。简而言之,边缘计算意味着在云端运行更少的进程,将这些进程移动到本地,例如用户的计算机、IoT 设备或边缘服务器。将计算放到网络边缘可以最大程度地减少客户端和服务器之间必须进行的长距离通信量。
Cloudflare: Workers Sites:直接在我们的网络上部署您的网站
Cloudflare: How Workers works
模板与可快速部署的项目
模板: https://developers.cloudflare.com/workers/examples/
在这里有官方提供的场景实现代码,可以参考和使用.
部署: https://deploy.workers.cloudflare.com/
来自GitHub等的可以直接部署的Workers项目.
创建一个Workers
在Cloudflare左侧菜单进入Workers,也可以通过具体域名配置中的Workers进入.
创建服务时服务名称
将决定域名,选择简介(HTTP处理程序)
还是HTTP处理程序
都可以,选择后者.
反向代理
首先使用CF Workers做反代的源头项目是GitHub: xiaoyang-sde / reflare
根据此项目衍生的更易部署的GitHub: fajarFWD / workersproxy
需要做的就是把index.js
里的内容改好后放进CF Workers,代码如下:
// 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': ''
}
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;
}
部署多个域名
如果被反代的网站使用其他域名的静态资源时, 可以部署多个 Workers-Proxy 并配置文本替换.
- www.google.com 使用位于 www.gstatic.com 的静态资源
- 部署 Workers-Proxy A, 用于代理 www.gstatic.com
- 部署 Workers-Proxy B, 用于代理 www.google.com
- 配置 Workers-Proxy B 的文本替换:
const replace_dict = {
'$upstream': '$custom_domain',
'www.gstatic.com': '<Workers-Proxy A 的域名>'
}
实例:反代Wikipedia
// Website you intended to retrieve for users.
const upstream = 'zh.wikipedia.org'
// Custom pathname for the upstream website.
const upstream_path = '/'
// Website you intended to retrieve for users using mobile devices.
const upstream_mobile = 'zh.m.wikipedia.org'
// Countries and regions where you wish to suspend your service.
const blocked_region = []
// 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'}
镜像网站带密码访问
// 替换成你想镜像的站点
const upstream = 'google.com'
// 如果那个站点有专门的移动适配站点,否则保持和上面一致
const upstream_mobile = 'm.google.com'
// 密码访问
const openAuth = false
const username = 'username'
const password = 'password'
// 你希望禁止哪些国家访问
const blocked_region = ['RU']
// 禁止自访问
const blocked_ip_address = ['0.0.0.0', '127.0.0.1']
// 如果被反代的网站使用其他域名的静态资源时, 可以部署多个 Workers-Proxy 并配置文本替换
const replace_dict = {
'$upstream': '$custom_domain'
}
function unauthorized() {
return new Response('Unauthorized', {
headers: {
'WWW-Authenticate': 'Basic realm="goindex"',
'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;
}
实例:反代加速GitHub
参考来源
要实现反代功能,那么就只需要将来源链接中的域名修改成 raw.githubusercontent.com,接着访问一下 GitHub 文件的内容并返回即可,直接上具体实现的代码:
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
// Cloudflare Workers 分配的域名
cf_worker_host = "xx.xxx.workers.dev";
// 自定义的域名
origin_host = "xx.xxx.com";
// GitHub 仓库文件地址
github_host = "raw.githubusercontent.com/usrname/repo/master";
// 替换 2 次以同时兼容 Worker 来源和域名来源
url = request.url.replace(cf_worker_host, github_host).replace(origin_host, github_host);
return fetch(url);
}
部署完成后,你已经可以通过 Cloudflare Workers 分配给你的域名访问你 GitHub 仓库里的图片了,而且默认就是套了 CF 的 CDN 的,在境内也能访问顶多就是速度会慢一点.
在触发器里添加路由,输入自己的子域名即可
别忘了去 DNS 处添加 CNAME 解析指向xx.xx.workers.dev(我这里用的是 DNSPod,不使用 Cloudflare 的 DNS 不开启小云朵也是可以使用 CF 的 CDN 的,因为走了一遍 Cloudflare Workers,但是无法享受到 CF 提供的免费 SSL 证书)
如果要替换网站旧连接的话
将 ](https://raw.githubusercontent.com/username/repo/master
替换为 ](https://xx.xxx.com
即可.
PicGo 中设置 GitHub 图床自定义域名https://xx.xxx.com即可
上传成功后,剪贴版里就是 xx.xxx.com/xxxxxx.png 形式的图片链接了。
301重定向
实现301的方法很多,利用CF的Rules
,Bulk Redirect
,Workers
,Pages
都可以实现301,或者在服务器的Nginx配置文件上,Apache的.htaccess
文件中,或者wordpress的主题都可以直接对url做301.
重定向可简单可复杂,涉及到SEO的问题,根据需求不同,实现方式不同,复杂的重定向需要用js实现,所以用到了Workers.
下面主要看Workers上实现的一些重定向例子.
Redirect模板
使用CF的Redirect模板.
(利用重定向,可以做订阅链接的封装.)
Redirect all requests to one URL
将所有的请求重定向到一个 URL
const destinationURL = 'https://example.com';
const statusCode = 301;
async function handleRequest(request) {
return Response.redirect(destinationURL, statusCode);
}
addEventListener('fetch', async event => {
event.respondWith(handleRequest(event.request));
});
Redirect requests from one domain to another
仅域名互换,保留后面的 path。即 foo.com/a?b=c
转 bar.com/a?b=c
const base = 'https://example.com';
const statusCode = 301;
async function handleRequest(request) {
const url = new URL(request.url);
const { pathname, search } = url;
const destinationURL = base + pathname + search;
return Response.redirect(destinationURL, statusCode);
}
addEventListener('fetch', async event => {
event.respondWith(handleRequest(event.request));
});
这两个完全没必要,因为用「页面规则」的「URL 转发」就可以完成了,除非你 3 个免费的规则都用完了。
实例:手写更通用的转换
参考来源:利用 Cloudflare Workers 进行批量 301 重定向
一个简单的示例:如果访问的地址和 old_url 撞上了,则返回 new_url。
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
let old_url = "https://dvel.xyz/posts/1234/"
let new_url = "https://dvel.me/posts/ba-la-ba-la/"
if (request.url === old_url) {
return Response.redirect(new_url, 301)
}
return fetch(request)
}
实例:同一个域名下跳转
效果:只重定向当前域名下指定的路径。以后要是修改路径了就在这更新一下。
旧地址:dvel.me/posts/1234/
新地址:dvel.me/posts/ba-la-ba-la/
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const arr = [
"https://dvel.me/posts/1234/ https://dvel.me/posts/ba-la-ba-la/",
// ...
]
let old_url, new_url
for (const u2 of arr) {
[old_url, new_url] = u2.split(' ')
if (request.url === old_url) {
return Response.redirect(new_url, 301)
}
}
return fetch(request)
}
实例:域名 A 跳转域名 B
效果:默认只转换域名,并保留后面的 path 和 search;后缀和域名都变了的,需要一个一个写进数组。
旧地址:dvel.xyz/posts/1234/
新地址:dvel.me/posts/ba-la-ba-la/
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const arr = [
"https://dvel.xyz/posts/1234/ https://dvel.me/posts/ba-la-ba-la/",
// ...
]
let old_url, new_url
for (const u2 of arr) {
[old_url, new_url] = u2.split(' ')
if (request.url === old_url) {
return Response.redirect(new_url, 301)
}
}
const base = "https://dvel.me"
const url = new URL(request.url)
const { pathname, search } = url
const destinationURL = base + pathname + search
return Response.redirect(destinationURL, 301)
}
Cloudflare Pages 的重定向
如果在用 Cloudflare Pages 的话,用 Pages 的重定向更简单。
在构件目录创建 _redirects 文件(也就是 Hugo 的 static 目录),参考 Redirects 文档创建规则,限制是 100 个。
实例:自定义场景
在这个例子中,如果传入的URL包含/en
,我们要从URL中删除它,并返回一个301重定向到删除了/en
的新URL。因此,举例来说,https://getfishtank.ca/en/sitecore-cms
将变成https://getfishtank.ca/sitecore-cms
。
const base = "https://getfishtank.ca";
const statusCode = 301;
async function handleRequest(request) {
const url = new URL(request.url);
let { pathname, search, hash } = url;
if(pathname.indexOf("/en") != 0) {
return fetch(request);
}
pathname = pathname.replace("/en/", "/");
const destinationURL = base + pathname + search + hash;
return Response.redirect(destinationURL, statusCode)
}
addEventListener("fetch", async event => {
event.respondWith(handleRequest(event.request))
})
为Workers自定义域名
原本只能通过xxx.xxx.workers.dev访问服务,自定义域名可以使用自己的域名访问服务.
前提:要使用的域名必须是在CF托管的即该域名Name Service在CF.
进行自定义域名的设置可以[通过域名指向Workers]也可以[通过Workers选择域名].
首先创建一个二级域名.
先在域名的DNS处新建一个A记录,随便写一个IP即可,代理状态
必须是启动的.
来到域名设置中的Workers
,点击添加路由
,填写刚新建的二级域名
+/*
,选择要指向的Workers即可.
我试验了加/*
和不加/*
,不加也能访问但是会导致网页大量内容缺失.
如果通过Workers选择域名,需要来到触发器
设置,点击添加路由
,并填写域名
+/*
,区域设置为根域名.
JsProxy on CF Workers
'use strict'
/**
* static files (404.html, sw.js, conf.js)
*/
const ASSET_URL = 'https://etherdream.github.io/jsproxy'
const JS_VER = 10
const MAX_RETRY = 1
/** @type {RequestInit} */
const PREFLIGHT_INIT = {
status: 204,
headers: new Headers({
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS',
'access-control-max-age': '1728000',
}),
}
/**
* @param {any} body
* @param {number} status
* @param {Object<string, string>} headers
*/
function makeRes(body, status = 200, headers = {}) {
headers['--ver'] = JS_VER
headers['access-control-allow-origin'] = '*'
return new Response(body, {status, headers})
}
/**
* @param {string} urlStr
*/
function newUrl(urlStr) {
try {
return new URL(urlStr)
} catch (err) {
return null
}
}
addEventListener('fetch', e => {
const ret = fetchHandler(e)
.catch(err => makeRes('cfworker error:\n' + err.stack, 502))
e.respondWith(ret)
})
/**
* @param {FetchEvent} e
*/
async function fetchHandler(e) {
const req = e.request
const urlStr = req.url
const urlObj = new URL(urlStr)
const path = urlObj.href.substr(urlObj.origin.length)
if (urlObj.protocol === 'http:') {
urlObj.protocol = 'https:'
return makeRes('', 301, {
'strict-transport-security': 'max-age=99999999; includeSubDomains; preload',
'location': urlObj.href,
})
}
if (path.startsWith('/http/')) {
return httpHandler(req, path.substr(6))
}
switch (path) {
case '/http':
return makeRes('请更新 cfworker 到最新版本!')
case '/ws':
return makeRes('not support', 400)
case '/works':
return makeRes('it works')
default:
// static files
return fetch(ASSET_URL + path)
}
}
/**
* @param {Request} req
* @param {string} pathname
*/
function httpHandler(req, pathname) {
const reqHdrRaw = req.headers
if (reqHdrRaw.has('x-jsproxy')) {
return Response.error()
}
// preflight
if (req.method === 'OPTIONS' &&
reqHdrRaw.has('access-control-request-headers')
) {
return new Response(null, PREFLIGHT_INIT)
}
let acehOld = false
let rawSvr = ''
let rawLen = ''
let rawEtag = ''
const reqHdrNew = new Headers(reqHdrRaw)
reqHdrNew.set('x-jsproxy', '1')
// 此处逻辑和 http-dec-req-hdr.lua 大致相同
// https://github.com/EtherDream/jsproxy/blob/master/lua/http-dec-req-hdr.lua
const refer = reqHdrNew.get('referer')
const query = refer.substr(refer.indexOf('?') + 1)
if (!query) {
return makeRes('missing params', 403)
}
const param = new URLSearchParams(query)
for (const [k, v] of Object.entries(param)) {
if (k.substr(0, 2) === '--') {
// 系统信息
switch (k.substr(2)) {
case 'aceh':
acehOld = true
break
case 'raw-info':
[rawSvr, rawLen, rawEtag] = v.split('|')
break
}
} else {
// 还原 HTTP 请求头
if (v) {
reqHdrNew.set(k, v)
} else {
reqHdrNew.delete(k)
}
}
}
if (!param.has('referer')) {
reqHdrNew.delete('referer')
}
// cfworker 会把路径中的 `//` 合并成 `/`
const urlStr = pathname.replace(/^(https?):\/+/, '$1://')
const urlObj = newUrl(urlStr)
if (!urlObj) {
return makeRes('invalid proxy url: ' + urlStr, 403)
}
/** @type {RequestInit} */
const reqInit = {
method: req.method,
headers: reqHdrNew,
redirect: 'manual',
}
if (req.method === 'POST') {
reqInit.body = req.body
}
return proxy(urlObj, reqInit, acehOld, rawLen, 0)
}
/**
*
* @param {URL} urlObj
* @param {RequestInit} reqInit
* @param {number} retryTimes
*/
async function proxy(urlObj, reqInit, acehOld, rawLen, retryTimes) {
const res = await fetch(urlObj.href, reqInit)
const resHdrOld = res.headers
const resHdrNew = new Headers(resHdrOld)
let expose = '*'
for (const [k, v] of resHdrOld.entries()) {
if (k === 'access-control-allow-origin' ||
k === 'access-control-expose-headers' ||
k === 'location' ||
k === 'set-cookie'
) {
const x = '--' + k
resHdrNew.set(x, v)
if (acehOld) {
expose = expose + ',' + x
}
resHdrNew.delete(k)
}
else if (acehOld &&
k !== 'cache-control' &&
k !== 'content-language' &&
k !== 'content-type' &&
k !== 'expires' &&
k !== 'last-modified' &&
k !== 'pragma'
) {
expose = expose + ',' + k
}
}
if (acehOld) {
expose = expose + ',--s'
resHdrNew.set('--t', '1')
}
// verify
if (rawLen) {
const newLen = resHdrOld.get('content-length') || ''
const badLen = (rawLen !== newLen)
if (badLen) {
if (retryTimes < MAX_RETRY) {
urlObj = await parseYtVideoRedir(urlObj, newLen, res)
if (urlObj) {
return proxy(urlObj, reqInit, acehOld, rawLen, retryTimes + 1)
}
}
return makeRes(res.body, 400, {
'--error': `bad len: ${newLen}, except: ${rawLen}`,
'access-control-expose-headers': '--error',
})
}
if (retryTimes > 1) {
resHdrNew.set('--retry', retryTimes)
}
}
let status = res.status
resHdrNew.set('access-control-expose-headers', expose)
resHdrNew.set('access-control-allow-origin', '*')
resHdrNew.set('--s', status)
resHdrNew.set('--ver', JS_VER)
resHdrNew.delete('content-security-policy')
resHdrNew.delete('content-security-policy-report-only')
resHdrNew.delete('clear-site-data')
if (status === 301 ||
status === 302 ||
status === 303 ||
status === 307 ||
status === 308
) {
status = status + 10
}
return new Response(res.body, {
status,
headers: resHdrNew,
})
}
/**
* @param {URL} urlObj
*/
function isYtUrl(urlObj) {
return (
urlObj.host.endsWith('.googlevideo.com') &&
urlObj.pathname.startsWith('/videoplayback')
)
}
/**
* @param {URL} urlObj
* @param {number} newLen
* @param {Response} res
*/
async function parseYtVideoRedir(urlObj, newLen, res) {
if (newLen > 2000) {
return null
}
if (!isYtUrl(urlObj)) {
return null
}
try {
const data = await res.text()
urlObj = new URL(data)
} catch (err) {
return null
}
if (!isYtUrl(urlObj)) {
return null
}
return urlObj
}
7 comments
Acidpsychedelics.com!! shop is the innovation for new generations shop that supply DMT Vape pen, DMT Carts, DMT Powder, LSD Tabs, LSD Blotter Paper, LSD Microdose, Magic mushroom Edibles, Psilocybe mushroom Gummies, LSD Candies, and other psychedelics drugs Ketamine Vials, Ayahuasca tea, Mushroom Tea and DMT Vape pens and Cartridges in all over The United States of America, Australia, the United Kingdom and Europe. We ship via FedEX,TNT,UPS
buy ecstasy online
buy golden teacher mushrooms
buy liquid lsd online
4 aco dmt buy usa
请问一下,我用这段代码确实可以反代,但是我要反代的网站是禁止大陆IP的,cf会传递客户端IP,怎样才能让CF访问代理目标的时候用CDN的IP去访问呢?
反向代理第106行那里应该修改下判断条件,允许「UTF-8」和「utf-8」,一些网站返回的content type是小写的
谢谢提醒
大佬你好,想问一下为什么我按照你的方法部署了worker,在编辑界面里预览是正常的,但是在浏览器里打开显示Error 1101 Worker threw exception
抱歉我没经历过这个报错,没法帮助到你。