- Published on
AI应用WebSocket连接中断问题解决记录
- Authors

- Name
- Tails Azimuth
🔍 背景介绍
我们正在开发一个医学文章生成的AI应用,使用了以下技术栈:
- 前端:Next.js + CopilotKit + TypeScript
- 后端:Python + LangGraph
- 部署:Docker + Caddy反向代理
- 域名架构:
- 主站:
www.example.com(端口3000) - AI服务:
ai.example.com(端口3333)
- 主站:
🚨 问题描述
在部署到生产环境后,出现了一个奇特的跨域跳转报错问题:
现象表现
- 跳转报错:从AI页面通过导航栏跳转到主站时,浏览器显示"无法打开此网页"错误
- 时机相关:只有在AI预测输入进行中时跳转才会报错,预测完成后跳转正常
- 域名相关:使用域名访问会报错,直接用IP+端口访问则正常
错误信息
无法打开此网页
请检查您的网络连接:
• 关闭其他标签页或应用
• 在新的无痕式窗口中打开网页 (⇧⌘N)
• 重新启动 Chrome
• 重新启动计算机
错误代码:11
🔬 问题分析
根本原因
经过深入分析,发现这是一个典型的WebSocket连接中断问题:
- CopilotKit建立WebSocket连接:用于实时AI对话和预测输入功能
- 强制页面跳转:使用普通
<a href>导致页面完全刷新 - 连接突然中断:WebSocket连接在预测输入进行中被强制断开
- 代理配置问题:Caddy没有正确处理WebSocket连接的清理
技术细节
// 问题代码:直接跳转,无连接清理
<a href="https://www.example.com/home" target="_self">
网站首页
</a>
✅ 解决方案
1. 优化导航组件
为导航链接添加智能处理逻辑:
// ui/src/components/MainHeader.tsx
const handleExternalLinkClick = async (e: React.MouseEvent<HTMLAnchorElement>, href: string) => {
// 如果当前在AI页面,给一个小的延迟让正在进行的请求完成
if (pathname?.includes('/medical-science')) {
e.preventDefault();
try {
// 给一个小的延迟让正在进行的请求完成
await new Promise(resolve => setTimeout(resolve, 100));
// 触发页面跳转
window.location.href = href;
} catch (error) {
console.warn('清理连接时出错:', error);
// 即使清理失败也要跳转
window.location.href = href;
}
}
};
// 应用到导航链接
<a
href={item.href}
onClick={(e) => handleExternalLinkClick(e, item.href)}
target="_self"
>
{item.label}
</a>
2. 添加页面离开监听
在主组件中添加优雅的连接清理:
// ui/src/app/Main.tsx
useEffect(() => {
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
// 如果有正在进行的AI对话或预测输入,给用户提示
if (state.logs && state.logs.some(log => !log.done)) {
const message = "AI正在处理您的请求,确定要离开吗?";
event.preventDefault();
event.returnValue = message;
return message;
}
};
const handleUnload = () => {
// 页面卸载时执行清理
try {
if ('sendBeacon' in navigator) {
navigator.sendBeacon('/api/cleanup', JSON.stringify({
agent: agent,
timestamp: Date.now()
}));
}
} catch (error) {
// 忽略错误,因为页面即将关闭
}
};
window.addEventListener('beforeunload', handleBeforeUnload);
window.addEventListener('unload', handleUnload);
return () => {
window.removeEventListener('beforeunload', handleBeforeUnload);
window.removeEventListener('unload', handleUnload);
};
}, [state.logs, agent]);
3. 创建清理API端点
// ui/src/app/api/cleanup/route.ts
export async function POST(req: NextRequest) {
try {
const body = await req.json();
const { agent, timestamp } = body;
console.log(`客户端请求清理连接:`, {
agent, timestamp,
userAgent: req.headers.get('user-agent'),
ip: req.headers.get('x-forwarded-for')
});
return NextResponse.json({
success: true,
message: '清理请求已处理'
});
} catch (error) {
console.error('处理清理请求时出错:', error);
return NextResponse.json({
success: true,
message: '清理请求已接收'
});
}
}
4. 优化Caddy配置
# AI站点配置
ai.medstarai.com {
# WebSocket连接特殊处理
@websocket {
header Connection *Upgrade*
header Upgrade websocket
}
reverse_proxy @websocket http://ip:3333 {
header_up Host {host}
header_up X-Real-IP {remote}
header_up X-Forwarded-For {remote}
header_up X-Forwarded-Proto {scheme}
header_up Connection {>Connection}
header_up Upgrade {>Upgrade}
transport http {
read_timeout 300s
write_timeout 300s
dial_timeout 5s
}
}
# 普通HTTP请求
reverse_proxy http://ip:3333 {
header_up Host {host}
header_up X-Real-IP {remote}
header_up X-Forwarded-For {remote}
header_up X-Forwarded-Proto {scheme}
}
}
⚠️ 遇到的额外问题
React Hooks规则错误
在实现过程中遇到构建错误:
Error: React Hook "useCopilotContext" is called conditionally.
React Hooks must be called in the exact same order in every component render.
解决方案:移除条件性Hook调用,改为基于pathname判断:
// ❌ 错误做法
try {
const copilotContext = useCopilotContext();
} catch (error) {
// 处理错误
}
// ✅ 正确做法
if (pathname?.includes('/medical-science')) {
// 处理AI页面逻辑
}
Next.js图片优化502错误
使用<Image>组件后出现502错误:
GET /_next/image?url=https%3A%2F%2Fwww.google.com%2Fs2%2Ffavicons...
502 Bad Gateway
解决方案:禁用图片优化避免代理问题:
// next.config.mjs
const nextConfig = {
output: "standalone",
images: {
// 禁用图片优化,避免代理问题
unoptimized: true,
remotePatterns: [
{
protocol: 'https',
hostname: 'www.google.com',
pathname: '/s2/favicons/**',
},
],
},
};
📚 经验总结
技术要点
- WebSocket连接管理:在SPA应用中,需要特别注意WebSocket连接的生命周期管理
- 优雅降级:即使连接清理失败,也要保证用户能正常跳转
- 事件监听最佳实践:合理使用
beforeunload和unload事件 - React Hooks规则:避免条件性调用Hooks
架构建议
- 连接状态管理:建议使用状态管理工具追踪WebSocket连接状态
- 错误边界:为WebSocket相关组件添加错误边界
- 监控告警:添加连接异常的监控和告警机制
部署注意事项
- 代理配置:确保反向代理正确处理WebSocket升级
- 超时设置:为长连接设置合理的超时时间
- 健康检查:添加WebSocket连接的健康检查机制
🎯 结论
通过系统性的问题分析和解决方案实施,我们成功解决了医学AI应用中的WebSocket连接中断问题。关键在于:
- 准确诊断:识别出问题的根本原因是连接管理不当
- 优雅处理:实现了平滑的连接清理机制
- 防御编程:添加了多层错误处理和降级方案
这次问题解决过程让我们深刻理解了现代Web应用中实时连接管理的复杂性,以及在生产环境中进行充分测试的重要性。