之前的的帖子好像不能修改了, 开一个新的帖子
把 cf反代的worker.js 更新了一下,加上了缓存
现在感觉对话加载过一次之后, 再切换会确实快很多, 刷新也快了
worker.js
// 常量定义
const MAX_RETRIES = 3;
const RETRY_DELAY_MS = 500;
const EXPIRATION_BUFFER_SECONDS = 60; // JWT过期前的缓冲时间
// 缓存策略定义
const CACHE_STRATEGIES = {
IMMUTABLE: 'public, max-age=31536000, immutable',
LONG: 'public, max-age=604800',
MEDIUM: 'public, max-age=86400',
SHORT: 'public, max-age=3600',
NONE: 'no-store, max-age=0'
};
// 内容替换规则
const CONTENT_REPLACEMENTS = [
{ pattern: '<title>bocchi Chat</title>', replacement: '<title>OWU</title>' },
{ pattern: '"name":"Open WebUI"', replacement: '"name":"OWU"' },
{ pattern: '"short_name":"Open WebUI"', replacement: '"short_name":"OWU"' },
{ pattern: '<meta name="apple-mobile-web-app-title" content="Open WebUI">', replacement: '<meta name="apple-mobile-web-app-title" content="OWU">'}
];
// 静态资源映射
const STATIC_RESOURCES = {
'/static/logo.png': 'https://registry.npmmirror.com/@lobehub/icons-static-svg/1.45.0/files/icons/aionlabs-color.svg',
'/static/splash.png': 'https://registry.npmmirror.com/@lobehub/icons-static-svg/1.45.0/files/icons/aionlabs-color.svg',
'/static/splash-dark.png': 'https://registry.npmmirror.com/@lobehub/icons-static-svg/1.45.0/files/icons/aionlabs-color.svg',
'/static/favicon-dark.png': 'https://registry.npmmirror.com/@lobehub/icons-static-svg/1.45.0/files/icons/aionlabs-color.svg',
'/static/favicon.png': 'https://registry.npmmirror.com/@lobehub/icons-static-svg/1.45.0/files/icons/aionlabs-color.svg',
'/static/favicon-96x96.png': 'https://registry.npmmirror.com/@lobehub/icons-static-svg/1.45.0/files/icons/aionlabs-color.svg',
'/static/favicon.svg': 'https://registry.npmmirror.com/@lobehub/icons-static-svg/1.45.0/files/icons/aionlabs-color.svg',
'/static/favicon.ico': 'https://registry.npmmirror.com/@lobehub/icons-static-png/latest/files/dark/aionlabs-color.png', // 注意:/favicon.ico 通常由 handleStaticResource 中的重定向处理
'/static/apple-touch-icon.png': 'https://registry.npmmirror.com/@lobehub/icons-static-png/latest/files/dark/aionlabs-color.png',
'/favicon.ico': 'https://registry.npmmirror.com/@lobehub/icons-static-svg/1.45.0/files/icons/aionlabs-color.svg' // 主 /favicon.ico 由 handleStaticResource 重定向处理
};
// 根据路径确定缓存策略
function getCacheStrategy(path) {
if (path.includes('/_app/immutable/') || path.match(/\.[a-f0-9]{8}\.(js|css)$/)) {
return CACHE_STRATEGIES.IMMUTABLE;
}
if (path.startsWith('/static/') || path.startsWith('/assets/')) {
return CACHE_STRATEGIES.MEDIUM;
}
if (path.includes('favicon') || path.endsWith('manifest.json')) {
return CACHE_STRATEGIES.MEDIUM;
}
if (path.endsWith('.html') || path === '/' || !path.includes('.')) {
return CACHE_STRATEGIES.SHORT;
}
return CACHE_STRATEGIES.MEDIUM;
}
// 使用缓存处理请求
async function handleWithCache(request, handler) {
const url = new URL(request.url);
const path = url.pathname;
const cacheStrategy = getCacheStrategy(path);
if (cacheStrategy === CACHE_STRATEGIES.NONE || request.method !== 'GET') {
return handler();
}
const cache = caches.default;
let response = await cache.match(request);
if (!response || request.headers.get('cache-control') === 'no-cache') {
response = await handler();
if (response.status === 200) {
const clonedResponse = response.clone();
const headers = new Headers(clonedResponse.headers);
headers.set('Cache-Control', cacheStrategy);
const cachedResponse = new Response(clonedResponse.body, {
status: clonedResponse.status,
statusText: clonedResponse.statusText,
headers: headers
});
await cache.put(request, cachedResponse.clone());
return cachedResponse;
}
}
return response;
}
async function getJwtToken(HF_SPACE_NAME, HF_TOKEN, HF_SPACE_USER, D1, forceRefresh = false) {
const now = Date.now() / 1000;
let isRefreshing;
let retries = 0;
let jwtToken = null;
let jwtExpiration = 0;
while (true) {
let result;
try {
result = await D1.prepare("SELECT token, expiration, is_refreshing FROM tokens WHERE id = 1").first();
} catch (e) {
throw new Error("Database access error");
}
if (result) {
jwtToken = result.token;
jwtExpiration = result.expiration;
isRefreshing = result.is_refreshing;
} else {
jwtToken = null;
jwtExpiration = 0;
isRefreshing = 0;
}
if (jwtToken && jwtExpiration > now + EXPIRATION_BUFFER_SECONDS && !forceRefresh) {
return jwtToken;
}
if (isRefreshing) {
await new Promise((resolve) => setTimeout(resolve, 100));
continue;
}
try {
await D1.prepare("INSERT OR REPLACE INTO tokens (id, is_refreshing) VALUES (1, 1)").run();
} catch (e) {
throw new Error("Database update error");
}
try {
if (!HF_TOKEN || !HF_SPACE_NAME || !HF_SPACE_USER) {
throw new Error('One or more required environment variables are missing.');
}
const HF_API_URL = `https://huggingface.co/api/spaces/${HF_SPACE_USER}/${HF_SPACE_NAME}/jwt`;
let response;
while (retries < MAX_RETRIES) {
try {
response = await fetch(HF_API_URL, {
headers: { "Authorization": `Bearer ${HF_TOKEN}` },
});
if (!response.ok) {
if (response.status >= 500 && response.status < 600) {
retries++;
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
continue;
} else {
const errorText = await response.text();
throw new Error(`Failed to fetch JWT token: ${response.status} ${response.statusText} - ${errorText}`);
}
}
break;
} catch (networkError) {
retries++;
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
}
}
if (retries === MAX_RETRIES) {
throw new Error("Max retries reached while fetching JWT token");
}
const apiResult = await response.json();
jwtToken = apiResult.token;
try {
const jwtPayload = JSON.parse(atob(jwtToken.split('.')[1]));
jwtExpiration = jwtPayload.exp;
} catch (_) {
jwtExpiration = now + 3600;
}
try {
await D1.prepare("INSERT OR REPLACE INTO tokens (id, token, expiration, is_refreshing) VALUES (1, ?, ?, 0)")
.bind(jwtToken, jwtExpiration)
.run();
} catch (e) {
throw new Error("Database update error");
}
return jwtToken;
} catch (e) {
try {
await D1.prepare("UPDATE tokens SET is_refreshing = 0 WHERE id = 1").run();
} catch (dbError) {}
throw e;
}
}
}
async function initDatabase(D1) {
try {
await D1.batch([
D1.prepare(`CREATE TABLE IF NOT EXISTS tokens (
id INTEGER PRIMARY KEY,
token TEXT,
expiration REAL,
is_refreshing INTEGER DEFAULT 0
);`).bind()
]);
} catch (e) {
throw new Error("Failed to initialize database");
}
}
// Function to handle static resources
async function handleStaticResource(request) {
const path = new URL(request.url).pathname;
let resourceUrl = STATIC_RESOURCES[path];
// Handle special case for /favicon.ico to redirect to /static/favicon.png
if (path === '/favicon.ico') {
return new Response(null, {
status: 301,
headers: {
'Location': '/static/favicon.png',
'Cache-Control': 'public, max-age=2592000'
}
});
}
if (resourceUrl) {
return handleWithCache(request, async () => {
// Fetch the static resource
const response = await fetch(resourceUrl, {
headers: {
'Host': '',
'Referer': ''
}
});
// Create a new response with caching headers
const headers = new Headers(response.headers);
headers.set('Cache-Control', getCacheStrategy(path));
// Set appropriate content type for favicon.ico
if (path.endsWith('.ico')) {
headers.set('Content-Type', 'image/x-icon');
}
return new Response(response.body, {
status: response.status,
headers: headers
});
});
}
return null; // Not a static resource
}
// Function to replace content in responses
async function replaceContent(response, contentType) {
if (!contentType || !(contentType.includes('text/html') || contentType.includes('application/json'))) {
return response;
}
let text = await response.text();
for (const replacement of CONTENT_REPLACEMENTS) {
text = text.replace(new RegExp(replacement.pattern, 'g'), replacement.replacement);
}
const headers = new Headers(response.headers);
headers.set('Content-Type', contentType);
return new Response(text, {
status: response.status,
statusText: response.statusText,
headers: headers
});
}
// 处理WebSocket请求的函数
async function handleWebSocketRequest(request, env) {
const HF_TOKEN = env.HF_TOKEN;
const HF_SPACE_NAME = env.HF_SPACE_NAME;
const HF_SPACE_USER = env.HF_SPACE_USER;
const D1 = env.D1;
try {
// 获取JWT令牌
await initDatabase(D1);
let token = await getJwtToken(HF_SPACE_NAME, HF_TOKEN, HF_SPACE_USER, D1);
// 准备转发到HF空间的WebSocket连接
const url = new URL(request.url);
url.host = `${HF_SPACE_USER}-${HF_SPACE_NAME}.hf.space`;
// 构建新的头部,包含令牌
const headers = new Headers();
for (const [key, value] of request.headers.entries()) {
headers.set(key, value);
}
// 添加JWT令牌到Cookie
const originalCookies = headers.get('Cookie') || headers.get('cookie') || '';
const cookieString = originalCookies ? `${originalCookies}; spaces-jwt=${token}` : `spaces-jwt=${token}`;
headers.set('Cookie', cookieString);
// 使用Cloudflare的WebSocketPair API
const webSocketPair = new WebSocketPair();
const [client, server] = Object.values(webSocketPair);
// 处理WebSocket连接
await handleWebSocketSession(server, url.toString(), headers, token, HF_SPACE_NAME, HF_TOKEN, HF_SPACE_USER, D1);
return new Response(null, {
status: 101,
webSocket: client,
});
} catch (error) {
return new Response(`WebSocket错误: ${error.message}`, { status: 500 });
}
}
// 处理WebSocket会话的函数
async function handleWebSocketSession(server, targetUrl, headers, token, HF_SPACE_NAME, HF_TOKEN, HF_SPACE_USER, D1) {
// 接受WebSocket连接
server.accept();
let backendWs = null;
try {
// 连接到后端WebSocket
const resp = await fetch(targetUrl, {
headers: headers,
method: 'GET',
cf: {
websocket: true,
}
});
// 检查连接是否成功
if (!resp.webSocket) {
server.send(JSON.stringify({ error: "无法连接到后端WebSocket" }));
server.close(1011, "无法连接到后端WebSocket");
return;
}
backendWs = resp.webSocket;
backendWs.accept();
// 处理令牌刷新
let tokenRefreshInterval = setInterval(async () => {
try {
// 获取新令牌
const newToken = await getJwtToken(HF_SPACE_NAME, HF_TOKEN, HF_SPACE_USER, D1);
// 发送令牌刷新消息到后端
backendWs.send(JSON.stringify({ type: "token_refresh", token: newToken }));
} catch (error) {
console.error("令牌刷新失败:", error);
}
}, 30 * 60 * 1000); // 30分钟刷新一次
// 从客户端转发消息到后端
server.addEventListener("message", (event) => {
if (backendWs && backendWs.readyState === 1) {
backendWs.send(event.data);
}
});
// 从后端转发消息到客户端
backendWs.addEventListener("message", (event) => {
if (server.readyState === 1) {
server.send(event.data);
}
});
// 处理客户端关闭
server.addEventListener("close", (event) => {
clearInterval(tokenRefreshInterval);
if (backendWs) {
backendWs.close(event.code, event.reason);
}
});
// 处理后端关闭
backendWs.addEventListener("close", (event) => {
clearInterval(tokenRefreshInterval);
if (server.readyState === 1) {
server.close(event.code, event.reason);
}
});
// 处理错误
server.addEventListener("error", (event) => {
console.error("客户端WebSocket错误:", event);
clearInterval(tokenRefreshInterval);
});
backendWs.addEventListener("error", (event) => {
console.error("后端WebSocket错误:", event);
clearInterval(tokenRefreshInterval);
if (server.readyState === 1) {
server.send(JSON.stringify({ error: "后端连接错误" }));
}
});
} catch (error) {
console.error("WebSocket会话处理错误:", error);
if (server.readyState === 1) {
server.send(JSON.stringify({ error: error.message }));
server.close(1011, "内部错误");
}
}
}
// 处理HTTP请求的函数
async function handleHttpRequest(request, env) {
return handleWithCache(request, async () => {
const HF_TOKEN = env.HF_TOKEN;
const HF_SPACE_NAME = env.HF_SPACE_NAME;
const HF_SPACE_USER = env.HF_SPACE_USER;
const D1 = env.D1;
try {
// Check if this is a request for a static resource
const staticResponse = await handleStaticResource(request);
if (staticResponse) {
return staticResponse;
}
await initDatabase(D1);
let token = await getJwtToken(HF_SPACE_NAME, HF_TOKEN, HF_SPACE_USER, D1);
const url = new URL(request.url);
url.host = `${HF_SPACE_USER}-${HF_SPACE_NAME}.hf.space`;
const headers = new Headers();
for (const [key, value] of request.headers.entries()) {
headers.set(key, value);
}
// Remove Accept-Encoding to ensure content is not compressed
headers.delete('Accept-Encoding');
const originalCookies = headers.get('Cookie') || headers.get('cookie') || '';
const cookieString = originalCookies ? `${originalCookies}; spaces-jwt=${token}` : `spaces-jwt=${token}`;
headers.set('Cookie', cookieString);
let newRequest = new Request(url.toString(), {
method: request.method,
headers: headers,
body: request.body,
redirect: request.redirect,
});
let response = await fetch(newRequest);
if (response.status === 401 || response.status === 403) {
token = await getJwtToken(HF_SPACE_NAME, HF_TOKEN, HF_SPACE_USER, D1, true);
const updatedCookieString = originalCookies ? `${originalCookies}; spaces-jwt=${token}` : `spaces-jwt=${token}`;
headers.set('Cookie', updatedCookieString);
newRequest = new Request(url.toString(), {
method: request.method,
headers: headers,
body: request.body,
redirect: request.redirect,
});
response = await fetch(newRequest);
}
const modifiedHeaders = new Headers(response.headers);
modifiedHeaders.delete('Link');
modifiedHeaders.set('charset', 'utf-8');
// Create a new response with the modified headers
let modifiedResponse = new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: modifiedHeaders,
});
// Apply content replacement
const contentType = response.headers.get('content-type');
return await replaceContent(modifiedResponse, contentType);
} catch (error) {
return new Response(`Error: ${error.message}`, { status: 500 });
}
});
}
export default {
async fetch(request, env) {
// Check if this is a request for a static resource
const path = new URL(request.url).pathname;
if (path in STATIC_RESOURCES || path === '/favicon.ico') {
return handleStaticResource(request);
}
// Check if it's a WebSocket request
const upgradeHeader = request.headers.get('Upgrade');
if (upgradeHeader && upgradeHeader.toLowerCase() === 'websocket') {
return handleWebSocketRequest(request, env);
} else {
return handleHttpRequest(request, env);
}
},
};

