剪藏:用 Deno 搭建 Koofr WebDAV 中继加速服务

背景
Koofr 自带 WebDAV 能力,适合用来挂载网盘、同步文件或接入支持 WebDAV 的客户端。不过在部分网络环境下,客户端直连 https://app.koofr.net/dav/Koofr 时可能会遇到连接慢、目录加载卡顿、文件同步不稳定等问题。
一种简单的解决思路是:在客户端与 Koofr 之间增加一个轻量级中继层。客户端先访问中继服务,再由中继服务转发请求到 Koofr,从而利用中继节点的网络线路改善访问体验。
本文介绍如何使用 Deno Deploy / Deno Playground 搭建一个 Koofr WebDAV 中继服务,也会补充在 VPS 上自建部署的方式。
方案原理
整体链路如下:
WebDAV 客户端
↓
Deno 中继服务
↓
Koofr WebDAV 服务
客户端不再直接访问:
https://app.koofr.net/dav/Koofr
而是访问:
https://your-proxy.deno.dev/koofr/dav/Koofr
中继服务会把 /koofr 前缀后的请求转发给 https://app.koofr.net。
为什么需要特殊处理 PROPFIND
WebDAV 客户端在列出目录时,通常会发送 PROPFIND 请求。Koofr 返回的 XML 响应中会包含文件或目录的 href 路径,例如:
<d:href>/dav/Koofr/example.txt</d:href>
但客户端现在访问的是代理地址:
/koofr/dav/Koofr/example.txt
如果不重写这些路径,客户端后续访问文件时仍可能请求原始路径,导致路径错误。因此,中继服务需要在 PROPFIND 响应中把 Koofr 返回的路径改写为带 /koofr 前缀的代理路径。
完整代码
下面代码可以直接部署到 Deno Deploy,也可以在本地或 VPS 上运行。
import { serve } from "https://deno.land/std@0.224.0/http/server.ts";
const pathMappings: Record<string, string> = {
"/koofr": "https://app.koofr.net",
// 可选:如果你还想复用这个中继代理其他 API,可以继续保留这些映射。
"/anthropic": "https://api.anthropic.com",
"/gemini": "https://generativelanguage.googleapis.com",
"/openai": "https://api.openai.com",
"/openrouter": "https://openrouter.ai/api",
"/xai": "https://api.x.ai",
"/telegram": "https://api.telegram.org",
"/discord": "https://discord.com/api",
"/groq": "https://api.groq.com/openai",
"/cohere": "https://api.cohere.ai",
"/huggingface": "https://api-inference.huggingface.co",
"/together": "https://api.together.xyz",
"/novita": "https://api.novita.ai",
"/portkey": "https://api.portkey.ai",
"/fireworks": "https://api.fireworks.ai/inference",
};
const port = Number(Deno.env.get("PORT") || "8000");
function secureHeaders(contentType?: string): Headers {
const headers = new Headers();
if (contentType) {
headers.set("Content-Type", contentType);
}
headers.set("X-Content-Type-Options", "nosniff");
headers.set("X-Frame-Options", "DENY");
headers.set("Referrer-Policy", "no-referrer");
return headers;
}
function removeHopByHopHeaders(headers: Headers): void {
[
"Connection",
"Keep-Alive",
"Proxy-Authenticate",
"Proxy-Authorization",
"TE",
"Trailer",
"Transfer-Encoding",
"Upgrade",
"Forwarded",
"Via",
"X-Forwarded-For",
"X-Real-IP",
].forEach((name) => headers.delete(name));
}
function findMapping(pathname: string): {
prefix: string;
targetBaseUrl: string;
} | null {
for (const [prefix, targetBaseUrl] of Object.entries(pathMappings)) {
if (pathname === prefix || pathname.startsWith(`${prefix}/`)) {
return { prefix, targetBaseUrl };
}
}
return null;
}
function buildTargetUrl(
incomingUrl: URL,
matchedPrefix: string,
targetBaseUrlString: string,
): {
targetUrl: URL;
suffixPath: string;
} {
const targetBaseUrl = new URL(targetBaseUrlString);
const suffixPath = incomingUrl.pathname.slice(matchedPrefix.length) || "/";
const basePath = targetBaseUrl.pathname.replace(/\/$/, "");
const targetUrl = new URL(targetBaseUrl.origin);
targetUrl.pathname = `${basePath}${suffixPath}`;
targetUrl.search = incomingUrl.search;
return { targetUrl, suffixPath };
}
function addSecurityHeaders(headers: Headers): Headers {
headers.set("X-Content-Type-Options", "nosniff");
headers.set("X-Frame-Options", "DENY");
headers.set("Referrer-Policy", "no-referrer");
return headers;
}
serve(async (req: Request) => {
const incomingUrl = new URL(req.url);
const incomingPathname = incomingUrl.pathname;
if (incomingPathname === "/" || incomingPathname === "/index.html") {
return new Response("Not Found", {
status: 404,
headers: secureHeaders("text/plain; charset=utf-8"),
});
}
if (incomingPathname === "/robots.txt") {
return new Response("User-agent: *
Disallow: /", {
status: 200,
headers: secureHeaders("text/plain; charset=utf-8"),
});
}
const mapping = findMapping(incomingPathname);
if (!mapping) {
console.warn(`[${new Date().toISOString()}] 未找到路径映射: ${incomingPathname}`);
return new Response("未找到:此路径没有代理映射。", {
status: 404,
headers: secureHeaders("text/plain; charset=utf-8"),
});
}
const { prefix: matchedPrefix, targetBaseUrl } = mapping;
const { targetUrl, suffixPath } = buildTargetUrl(
incomingUrl,
matchedPrefix,
targetBaseUrl,
);
const requestHeaders = new Headers(req.headers);
removeHopByHopHeaders(requestHeaders);
const proxyReq = new Request(targetUrl.toString(), {
method: req.method,
headers: requestHeaders,
body: ["GET", "HEAD"].includes(req.method) ? undefined : req.body,
redirect: "manual",
});
try {
const proxyRes = await fetch(proxyReq);
const responseHeaders = new Headers(proxyRes.headers);
removeHopByHopHeaders(responseHeaders);
addSecurityHeaders(responseHeaders);
// WebDAV 目录列表需要改写 XML 响应中的 href 路径。
if (matchedPrefix === "/koofr" && req.method === "PROPFIND") {
const contentType = responseHeaders.get("Content-Type") || "";
if (
proxyRes.ok &&
(contentType.includes("application/xml") || contentType.includes("text/xml"))
) {
const originalBody = await proxyRes.text();
let rewrittenBody = originalBody;
if (suffixPath && suffixPath !== "/") {
rewrittenBody = rewrittenBody.replaceAll(
suffixPath,
incomingPathname,
);
}
responseHeaders.delete("Content-Length");
return new Response(rewrittenBody, {
status: proxyRes.status,
statusText: proxyRes.statusText,
headers: responseHeaders,
});
}
}
return new Response(proxyRes.body, {
status: proxyRes.status,
statusText: proxyRes.statusText,
headers: responseHeaders,
});
} catch (error) {
console.error(
`[${new Date().toISOString()}] 请求上游失败:${targetUrl.toString()}`,
error,
);
return new Response("网关错误:连接上游服务器失败。", {
status: 502,
headers: secureHeaders("text/plain; charset=utf-8"),
});
}
}, { port });
通过 Deno Deploy 部署
1. 创建 Playground
访问 Deno Deploy 控制台:
https://dash.deno.com
使用 GitHub 账号登录后,点击 New Playground。
2. 粘贴代码并部署
将上面的完整代码粘贴到 Playground 中,然后点击 Save & Deploy。
部署完成后,Deno 会分配一个公开访问地址,格式类似:
https://your-project-name.deno.dev
3. 配置 WebDAV 客户端
假设你的 Deno Deploy 地址是:
https://my-proxy.deno.dev
原始 Koofr WebDAV 地址为:
https://app.koofr.net/dav/Koofr
代理后的 WebDAV 地址就是:
https://my-proxy.deno.dev/koofr/dav/Koofr
在 WebDAV 客户端中,将服务器地址替换为代理后的地址即可。用户名和密码仍然使用 Koofr 的 WebDAV 凭据。
在 VPS 上部署
如果你希望完全控制运行环境,也可以把同一份代码部署到自己的 VPS 上。相比 Deno Deploy,VPS 方式维护成本更高,但可控性更强,也更适合长期固定使用。
1. 安装 Deno
使用官方安装脚本:
curl -fsSL https://deno.land/install.sh | sh
将 Deno 添加到环境变量:
echo 'export DENO_INSTALL="$HOME/.deno"' >> ~/.bashrc
echo 'export PATH="$DENO_INSTALL/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
验证安装:
deno --version
2. 创建代理脚本
创建 proxy.ts:
nano ~/proxy.ts
将完整代码粘贴进去并保存。
3. 测试运行
deno run --allow-net --allow-env ~/proxy.ts
默认监听端口是 8000。如果要指定端口,可以这样运行:
PORT=8000 deno run --allow-net --allow-env ~/proxy.ts
4. 配置 systemd 服务
创建服务文件:
sudo nano /etc/systemd/system/deno-koofr-proxy.service
写入以下内容,并将 your-username 替换为你的实际用户名:
[Unit]
Description=Deno Koofr WebDAV Proxy
After=network.target
[Service]
Type=simple
User=your-username
WorkingDirectory=/home/your-username
Environment="PORT=8000"
ExecStart=/home/your-username/.deno/bin/deno run --allow-net --allow-env /home/your-username/proxy.ts
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
启动服务:
sudo systemctl daemon-reload
sudo systemctl enable deno-koofr-proxy
sudo systemctl start deno-koofr-proxy
sudo systemctl status deno-koofr-proxy
使用 Nginx 反向代理
建议使用 Nginx 对外提供 HTTPS,并将请求反向代理到本地的 Deno 服务。
1. 安装 Nginx
sudo apt update
sudo apt install nginx
2. 创建站点配置
sudo nano /etc/nginx/sites-available/deno-koofr-proxy
写入以下配置,并将 your-domain.com 替换为你的域名:
server {
listen 80;
server_name your-domain.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebDAV 大文件上传和目录操作建议关闭缓冲。
proxy_buffering off;
request_buffering off;
client_max_body_size 0;
}
}
启用配置:
sudo ln -s /etc/nginx/sites-available/deno-koofr-proxy /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
3. 配置 HTTPS
使用 Let's Encrypt 申请免费证书:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com
完成后,你的 WebDAV 地址就是:
https://your-domain.com/koofr/dav/Koofr
注意事项
-
凭据安全
WebDAV 用户名和密码会经过你的中继服务转发。请只部署在自己信任的平台或自己的服务器上,不要使用不可信的第三方中继。
-
不要公开成通用代理
如果只用于 Koofr,建议删除其他 API 映射,只保留
/koofr,避免服务被滥用。 -
流量与限制
中继服务会消耗 Deno Deploy 或 VPS 的出入站流量。同步大文件前,建议确认平台或服务器的流量限制。
-
网络效果取决于中继节点线路
中继并不一定在所有地区都能加速。实际效果取决于客户端到中继、中继到 Koofr 两段链路的质量。
-
客户端兼容性
不同 WebDAV 客户端对重定向、路径、
PROPFIND响应的处理方式可能不同。如果某个客户端无法正常列目录,可以优先检查 XML 中的href是否已经被正确改写。
总结
通过 Deno 搭建 Koofr WebDAV 中继,可以用较低成本改善 Koofr 在部分网络环境下的访问体验。Deno Deploy 的优点是部署简单、维护成本低;VPS 的优点是可控性更强,适合对稳定性和隐私边界要求更高的场景。
如果只是个人使用,建议优先尝试 Deno Deploy;如果需要长期稳定运行,或者希望完全掌控代理环境,可以选择 VPS 部署。