Shadowfetch 是 Cloudflare Workers 内置的 fetch 的替代方案。它旨在在不泄露不必要的用户信息的情况下,最大限度地控制请求和响应处理。由于 Cloudflare 的内置 fetch 可能会披露如下细节:
用户 IP 地址
地理位置元数据(通过 CF-IPCountry 获取)
其他工人的特定头信息,Shadowfetch 通过剥离或仅转发必要的标头来充当隐私保护盾。
项目地址:https://github.com/tysak/shadowfetch
1:创建一个CF WORKERS
放入以下代码(自行修改url地址),并部署,添加个人域名即可
import { connect } from "cloudflare:sockets";
// 全局配置,包括目标 URL 和调试模式
const CONFIG = {
AUTH_TOKEN: "optional-auth-token", // 可选的认证 token,若不需要可留空或删除相关逻辑
DEFAULT_DST_URL: "https://generativelanguage.googleapis.com/", // 目标 API 地址,这里以gemini为例
DEBUG_MODE: false, // 调试模式,设为 true 可查看日志
};
// 从环境变量更新全局配置(优先使用环境变量值)
function updateConfigFromEnv(env) {
if (!env) return;
for (const key of Object.keys(CONFIG)) {
if (key in env) {
if (typeof CONFIG[key] === 'boolean') {
CONFIG[key] = env[key] === 'true';
} else {
CONFIG[key] = env[key];
}
}
}
}
// 定义文本编码器和解码器,用于字符串与字节数组的转换
const encoder = new TextEncoder();
const decoder = new TextDecoder();
// 过滤不应该转发的 HTTP 头部(如 host、accept-encoding、cf-*)
const HEADER_FILTER_RE = /^(host|accept-encoding|cf-)/i;
// 根据调试模式定义日志输出函数
const log = CONFIG.DEBUG_MODE
? (message, data = "") => console.log(`[DEBUG] ${message}`, data)
: () => {};
// 拼接多个 Uint8Array
function concatUint8Arrays(...arrays) {
const total = arrays.reduce((sum, arr) => sum + arr.length, 0);
const result = new Uint8Array(total);
let offset = 0;
for (const arr of arrays) {
result.set(arr, offset);
offset += arr.length;
}
return result;
}
// 解析 HTTP 响应头部,返回状态码、状态文本、头部和头部结束位置
function parseHttpHeaders(buff) {
const text = decoder.decode(buff);
const headerEnd = text.indexOf("\r\n\r\n");
if (headerEnd === -1) return null;
const headerSection = text.slice(0, headerEnd).split("\r\n");
const statusLine = headerSection[0];
const statusMatch = statusLine.match(/HTTP\/1\.[01] (\d+) (.*)/);
if (!statusMatch) throw new Error(`Invalid status line: ${statusLine}`);
const headers = new Headers();
for (let i = 1; i < headerSection.length; i++) {
const line = headerSection[i];
const idx = line.indexOf(": ");
if (idx !== -1) {
headers.append(line.slice(0, idx), line.slice(idx + 2));
}
}
return { status: Number(statusMatch[1]), statusText: statusMatch[2], headers, headerEnd };
}
// 读取数据直到遇到双 CRLF(HTTP 头部结束)
async function readUntilDoubleCRLF(reader) {
let respText = "";
while (true) {
const { value, done } = await reader.read();
if (value) {
respText += decoder.decode(value, { stream: true });
if (respText.includes("\r\n\r\n")) break;
}
if (done) break;
}
return respText;
}
// 异步生成器:读取 chunked 编码的 HTTP 响应数据块
async function* readChunks(reader, buff = new Uint8Array()) {
while (true) {
let pos = -1;
for (let i = 0; i < buff.length - 1; i++) {
if (buff[i] === 13 && buff[i + 1] === 10) {
pos = i;
break;
}
}
if (pos === -1) {
const { value, done } = await reader.read();
if (done) break;
buff = concatUint8Arrays(buff, value);
continue;
}
const size = parseInt(decoder.decode(buff.slice(0, pos)), 16);
log("Read chunk size", size);
if (!size) break;
buff = buff.slice(pos + 2);
while (buff.length < size + 2) {
const { value, done } = await reader.read();
if (done) throw new Error("Unexpected EOF in chunked encoding");
buff = concatUint8Arrays(buff, value);
}
yield buff.slice(0, size);
buff = buff.slice(size + 2);
}
}
// 解析完整的 HTTP 响应,处理 chunked 或固定长度的响应体
async function parseResponse(reader) {
let buff = new Uint8Array();
while (true) {
const { value, done } = await reader.read();
if (value) {
buff = concatUint8Arrays(buff, value);
const parsed = parseHttpHeaders(buff);
if (parsed) {
const { status, statusText, headers, headerEnd } = parsed;
const isChunked = headers.get("transfer-encoding")?.includes("chunked");
const contentLength = parseInt(headers.get("content-length") || "0", 10);
const data = buff.slice(headerEnd + 4);
return new Response(
new ReadableStream({
async start(ctrl) {
try {
if (isChunked) {
log("Using chunked transfer mode");
for await (const chunk of readChunks(reader, data)) {
ctrl.enqueue(chunk);
}
} else {
log("Using fixed-length transfer mode", { contentLength });
let received = data.length;
if (data.length) ctrl.enqueue(data);
while (received < contentLength) {
const { value, done } = await reader.read();
if (done) break;
received += value.length;
ctrl.enqueue(value);
}
}
ctrl.close();
} catch (err) {
log("Error parsing response", err);
ctrl.error(err);
}
},
}),
{ status, statusText, headers }
);
}
}
if (done) break;
}
throw new Error("Unable to parse response headers");
}
// 处理 HTTP 请求的转发
async function nativeFetch(req, dstUrl) {
// 清理头部,过滤不需要的头部
const cleanedHeaders = new Headers();
for (const [k, v] of req.headers) {
if (!HEADER_FILTER_RE.test(k)) {
cleanedHeaders.set(k, v);
}
}
const targetUrl = new URL(dstUrl);
cleanedHeaders.set("Host", targetUrl.hostname);
cleanedHeaders.set("accept-encoding", "identity"); // 禁用压缩
// 如果需要 API Key,可以在这里添加
// cleanedHeaders.set('Authorization', 'Bearer YOUR_API_KEY');
const port = targetUrl.protocol === "https:" ? 443 : 80;
const socket = await connect(
{ hostname: targetUrl.hostname, port },
{ secureTransport: targetUrl.protocol === "https:" ? "on" : "off" }
);
const writer = socket.writable.getWriter();
// 构造请求行和头部
const requestLine =
`${req.method} ${targetUrl.pathname}${targetUrl.search} HTTP/1.1\r\n` +
Array.from(cleanedHeaders.entries())
.map(([k, v]) => `${k}: ${v}`)
.join("\r\n") +
"\r\n\r\n";
log("Sending request", requestLine);
await writer.write(encoder.encode(requestLine));
// 转发请求体(如果有)
if (req.body) {
log("Forwarding request body");
for await (const chunk of req.body) {
await writer.write(chunk);
}
}
// 解析并返回目标服务器的响应
return await parseResponse(socket.readable.getReader());
}
// 请求处理入口
async function handleRequest(req, env) {
updateConfigFromEnv(env);
CONFIG.DEBUG_MODE = CONFIG.DEBUG_MODE;
const url = new URL(req.url);
const path = url.pathname + url.search; // 获取路径和查询参数
const targetUrl = `${CONFIG.DEFAULT_DST_URL}${path.startsWith('/') ? path.slice(1) : path}`;
log("Target URL", targetUrl);
return await nativeFetch(req, targetUrl);
}
// 导出 Cloudflare Workers 的 fetch 事件处理器
export default { fetch: handleRequest };
export const onRequest = (ctx) => handleRequest(ctx.request, ctx.env);
以上即可使用
下面是其他使用方法。
使用方法
1. HTTP 代理
你只需要把目标 URL 加到你部署的 Worker地址 的后面,用以下格式:
https://你的_Cloudflare_Worker_域名/密码/https://目标网站/其他路径
举个例子:
https://bequiet.pages.dev/fonts/https://destination.example.com/dns_query
访问该链接,就能把请求转发到 https://destination.example.com/dns_query,并且不再暴露自己的真实IP!
2. WebSocket 代理
想要代理 WebSocket?只需要把目标协议换成以 wss:// 开头即可。
格式依然是:
wss://你的_Cloudflare_Worker_域名/密码/wss://目标网站/其他路径
例如:
wss://bequiet.pages.dev/fonts/wss://destination.example.com/chatroom
一键即可中转到 wss://destination.example.com/chatroom。