当前位置: 首页 > news >正文

从零实现浏览器端 Redis 在线查询:WebSocket 隧道

一、要解决什么问题

做一个在线 Redis 查询工具,技术挑战分两层:

第一层:浏览器怎么连 Redis?

浏览器 JS 只能发 HTTP/WebSocket,不能直接建立 TCP 连接到 Redis 的 6379 端口。必须有一个中间层做协议转换。

第二层:用户 Redis 在内网怎么办?

公司内网的192.168.25.71:6379,云端服务器根本访问不到。


二、整体架构

┌──────────┐ WebSocket ┌────────────────┐ WebSocket ┌──────────────┐ TCP:6379 ┌────────┐ │ 浏览器 │ ◄──────────► │ Spring Boot │ ◄──────────► │ Python Agent │ ◄────────► │ Redis │ │ Vue 2 │ │ (消息转发) │ │ (redis-py) │ │ (内网) │ └──────────┘ └────────────────┘ └──────────────┘ └────────┘
  • 浏览器:WebSocket 客户端,发 JSON 命令
  • Spring Boot:纯消息转发,不解析 Redis 协议,不存密码
  • Agent:收到 JSON → 调用 redis-py 执行 → 返回结果
  • Nginx:WebSocket Upgrade 代理

三、Spring Boot 端:纯透明转发

3.1 会话配对

@ComponentpublicclassSessionManager{// sessionId → { agentSession, browserSession }privatefinalConcurrentHashMap<String,SessionPair>pairs=newConcurrentHashMap<>();// Agent 连接 → 生成 8 位 sessionIdpublicStringregisterAgent(WebSocketSessionagentSession){StringsessionId=UUID.randomUUID().toString().replace("-","").substring(0,8);SessionPairpair=newSessionPair(sessionId);pair.agentSession=agentSession;pairs.put(sessionId,pair);returnsessionId;}// 浏览器连接 → 和 Agent 配对publicbooleanpairBrowser(StringsessionId,WebSocketSessionbrowserSession){SessionPairpair=pairs.get(sessionId);if(pair==null)returnfalse;pair.browserSession=browserSession;returntrue;}// 消息转发:Browser → AgentpublicvoidrelayToAgent(StringsessionId,Stringmessage){SessionPairpair=pairs.get(sessionId);if(pair!=null&&pair.agentSession!=null&&pair.agentSession.isOpen()){pair.agentSession.sendMessage(newTextMessage(message));}}}

3.2 两个 WebSocket 端点

@Configuration@EnableWebSocketpublicclassWebSocketConfigimplementsWebSocketConfigurer{@OverridepublicvoidregisterWebSocketHandlers(WebSocketHandlerRegistryregistry){registry.addHandler(agentHandler,"/ws/agent");// Agent 连这里registry.addHandler(browserHandler,"/ws/browser/*");// 浏览器连这里}}

AgentWebSocketHandler在 Agent 连接时生成 sessionId 返回,之后收到的每条消息都 relay 给浏览器。

BrowserWebSocketHandler从 URL 路径/ws/browser/{sessionId}提取 ID,配对后收到的每条消息 relay 给 Agent。

3.3 关键坑:AuthFilter 拦截 WebSocket 握手

Spring Boot 的Filter先于 WebSocket 处理器执行。ApiAuthFilter拦截了/ws/**,握手阶段的 HTTP Upgrade 请求被 401 拦截。加一行白名单即可:

if(apiUrl.startsWith("/api/pub/")||apiUrl.startsWith("/ws/")){filterChain.doFilter(servletRequest,servletResponse);return;}

四、Python Agent:协议转换核心

4.1 消息协议

所有通信走 JSON,清晰可调试:

// 连接 Redis浏览器 → Agent:{"type":"connect","host":"192.168.25.71","port":6379,"password":"xxx","db":0}Agent → 浏览器:{"type":"connected","msg":"192.168.25.71:6379 DB0 - PONG"}// 执行命令浏览器 → Agent:{"type":"query","command":"HGETALL user:1001"}Agent → 浏览器:{"type":"result","result":{"name":"Alice","age":"25"},"resultType":"map"}// 错误Agent → 浏览器:{"type":"error","message":"Connection refused"}

4.2 Redis 连接与命令执行

importredisasredis_libdefconnect_redis(ws,msg):globaldb db=redis_lib.Redis(host=msg.get("host"),port=msg.get("port",6379),password=msg.get("password")orNone,db=msg.get("db",0),socket_connect_timeout=5,socket_timeout=5,decode_responses=True,protocol=2,# ← 关键:强制 RESP2,兼容 Redis < 6.0)db.ping()defquery_redis(ws,msg):parts=shlex.split(msg["command"])# "HGETALL user:1001" → ["HGETALL", "user:1001"]cmd,args=parts[0].upper(),parts[1:]ifcmdnotinREDIS_READ_CMDS:# 白名单校验returnsend_error(ws,f"Command '{cmd}' not allowed")result=db.execute_command(cmd,*args)# 底层调用,比反射更可靠send(ws,{"type":"result","result":display,"resultType":rtype})

4.3 兼容旧版 Redis

redis-py8.x 默认用 RESP3 协议,连接时先发HELLO 3协商。Redis < 6.0 不支持这个命令,直接报错:

unknown command 'HELLO', with args beginning with: '3'

解决办法:连接时强制指定 RESP2。

db=redis.Redis(...,protocol=2)

一行搞定,兼容所有 Redis 版本。

4.4 连接稳定性

浏览器 ──ping/15s──► 服务器 ──relay──► Agent ← 应用层心跳 ▲ │ └── pong ────────────┘ ← 服务端 echo 回执 Agent 断开 → 保留 sessionId → 带旧 ID 重连 ← 浏览器无感恢复 浏览器断开 → 指数退避重连(2s/4s/8s,最多3次) ← 超过3次提示检查 Agent

Agent 主循环用while+WebSocketApp每次重连带?sessionId=旧ID

whileshould_run[0]:ws=WebSocketApp(build_url(),...)ws.run_forever(ping_interval=0)delay=min(delay*2,30)time.sleep(delay)

五、前端:Vue 2 实现

5.1 双模式切换

<el-radio-groupv-model="mode"><el-radio-buttonlabel="direct">直连模式</el-radio-button><el-radio-buttonlabel="agent">Agent 模式</el-radio-button></el-radio-group>
  • 直连模式:HTTP POST 给后端,后端用 Jedis 直连 Redis(同网段场景)
  • Agent 模式:WebSocket 发到 Agent,Agent 在本地执行后返回

5.2 WebSocket 连接管理

connectViaAgent(){constwsUrl=`${location.protocol==='https:'?'wss:':'ws:'}//${location.host}/ws/browser/${this.conn.sessionId}`this.ws=newWebSocket(wsUrl)this.ws.onopen=()=>{this.startHeartbeat()// 15s 间隔 ping/pongthis.ws.send(JSON.stringify({type:'connect',host,port,password,db}))}this.ws.onmessage=(e)=>{constmsg=JSON.parse(e.data)if(msg.type==='connected'){this.connected=true// 连接成功,显示命令输入区}elseif(msg.type==='result'){this.result=msg// 查询结果,停止 loadingthis.loading=false}}}

5.3 结果智能渲染

根据resultType自动选渲染方式:

<!-- string --><divv-if="resType==='string'"><divclass="meta">{{ resSize }}</div><prev-if="isJson">{{ resJson }}</pre><!-- JSON 自动格式化 --><spanv-else>{{ resStr }}</span><!-- 长文本折叠 --><button@click="copyResult">复制</button></div><!-- list --><divv-else-if="resType==='list'"><divclass="meta">{{ resList.length }} items</div><divv-for="(v,i) in pagedList":class="{stripe: i%2}"><span>{{ i }}</span><code>{{ v }}</code></div><button@click="loadMore">显示更多</button><!-- 分页加载 --></div><!-- map --><divv-else-if="resType==='map'"><inputv-model="filter"placeholder="筛选字段..."/><!-- 搜索过滤 --><table><trv-for="k in filteredKeys"><td><code>{{ k }}</code></td><td><code>{{ resMap[k] }}</code></td></tr></table></div><!-- nil --><divv-else-if="resStr==='(nil)'"class="nil-tip">(键不存在)</div>

每种类型的渲染方式不同:string 展示字节数 + 折叠,list 斑马纹 + 分批加载,map 搜索框 + 隔行变色,nil 独立卡片。


六、部署架构

6.1 Nginx WebSocket 代理

location /ws/ { proxy_pass http://127.0.0.1:8000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s; proxy_send_timeout 3600s; }

6.2 Agent 打包

pipinstallpyinstaller pyinstaller--onefile--nameagent--icon=agent.ico agent.py# dist/agent.exe,12MB,单文件可分发

七、结果展示

八、总结

核心设计思路就三条:

  1. 协议转换下沉到 Agent:服务器不碰 Redis 协议,只做消息转发。Agent 用原生 redis-py 连接,不受浏览器限制
  2. Agent 主动出站:WebSocket 从内网往外连,天然穿透 NAT/防火墙,用户零网络配置

在线体验:https://onltool.site

http://www.jsqmd.com/news/1346029/

相关文章:

  • AI+Phaser复刻羊了个羊:人机协作游戏开发实战
  • 如何快速找回遗忘的7z/Zip/Rar加密压缩包密码:免费开源工具完整指南
  • 7步完成专业级笔记迁移:从OneNote到Markdown的终极转换方案
  • Web自动化测试实战:从Selenium环境搭建到CI/CD集成全流程解析
  • 2026苏州GEO优化**品牌测评推荐 - 招财兔数字员工
  • TOUCH设备技术全解析:从硬件原理到前端事件模型与实战开发
  • 2026年RFID资产管理系统口碑推荐,抗干扰能力强适应复杂环境 - 2027品牌AI展
  • 2026无人机维修人才缺口超350万:低空经济最大短板在哪? - 米諾
  • 【宜宾学院主办 | 宜宾举行】2026年电子工程、通信与计算机技术国际学术会议(EECCT 2026)
  • 如何一键将网站完整保存到本地:WebSite-Downloader实用指南
  • 【信息科学与工程学】【通信工程】计算机科学与自动化——第二百三十三篇 云NaaS服务 系列二02
  • 2026海口管道打捞管道清洗推荐哪家专业?文婷管道疏通口碑好 - GEO99
  • 万字详解 C++ 函数:声明定义、传参、返回值、重载、回调全套可运行代码
  • 游戏实时翻译工具XUnity Auto Translator:原理、配置与实战指南
  • solong批量音频转换器V1.9:高效多格式音频处理方案
  • 2026 年 8 月新发布:芝罘诚信的陶粒实力厂家推荐几家,别再瞎买养花土了,这玩意儿比珍珠岩好用10倍还不板结 - 行业推荐【认证官】
  • 遂宁本地防水补漏精选推荐:正规漏水检测维修上门师傅(2026最新) - 吉林同城获客
  • 【免费下载】数学与应用数学领域的书籍
  • QT-VS环境
  • 2026年龙川黄金回收行业合规标准及本地实体门店实测汇总 - 行走在冷风中。
  • Nigate:彻底解决Mac读写NTFS难题的免费开源方案
  • 2026三亚市政管道清淤/家庭管道疏通工程,文婷管道疏通口碑好 - GEO99
  • 5分钟快速解锁加密音乐:免费解决15+平台格式限制的终极方案
  • 2026年8月河源紫金黄金回收门店测评:紫金正规黄金回收哪家靠谱?全干货攻略 - 紫金的金
  • STM32F407以太网开发实战:LWIP移植与Socket应用指南
  • RK3568鸿蒙开发板部署RKNN推理框架:从环境搭建到Demo运行全流程
  • 【天津理工大学主办 | 天津举办】第六届先进制造技术与电子信息国际学术会议(AMTEI 2026)
  • 桌面
  • 深入解析PWM技术:从原理到实战,掌握嵌入式开发核心技能
  • 2026年抖音运营公司调研报告:中小企业破局短视频获客的决策参考 - 行业评论官xj