如何用 curl模拟 websocket 协议请求?
要使用 curl
模拟 WebSocket 协议请求,需要注意以下事项:
问题说明
-
WebSocket 协议是基于 HTTP 升级机制的双向通信协议,而
curl
本身是面向 HTTP/HTTPS 的单次请求-响应工具,默认不支持 WebSocket 的长连接和消息交互。 -
替代方案:
- 若需完整的 WebSocket 交互(握手 + 消息收发),推荐使用专门工具(如
wscat
)。 - 若仅需模拟 WebSocket 握手阶段(验证连接是否建立),可通过
curl
手动构造 HTTP 升级请求。
- 若需完整的 WebSocket 交互(握手 + 消息收发),推荐使用专门工具(如
方案 1:使用 curl
模拟 WebSocket 握手
以下命令可模拟 WebSocket 的 HTTP 升级请求(仅握手阶段):
curl -i -N \
-H "Host: your-websocket-server.com" \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
https://your-websocket-server.com/ws-endpoint
参数解释:
-
-i
:显示响应头信息。 -
-N
:禁用缓冲,避免curl
等待响应体(因 WebSocket 是长连接)。 -
Host
:目标服务器域名(某些服务器会校验此字段)。 -
Connection: Upgrade
:请求协议升级。 -
Upgrade: websocket
:升级到 WebSocket 协议。 -
Sec-WebSocket-Version: 13
:指定 WebSocket 协议版本(必须为 13)。 -
Sec-WebSocket-Key
:随机 Base64 字符串(客户端生成,服务器需返回Sec-WebSocket-Accept
校验)。
结果验证:
若服务器返回 HTTP/1.1 101 Switching Protocols
,表示握手成功(但 curl
无法继续维持连接)。
方案 2:使用专用工具 wscat
wscat
是一个基于 Node.js 的 WebSocket 命令行工具,支持完整的消息交互:
-
安装:
npm install -g wscat
-
发起 WebSocket 请求:
# 明文 WebSocket(ws://) wscat -c ws://your-websocket-server.com/ws-endpoint # 加密 WebSocket(wss://) wscat -c wss://your-websocket-server.com/ws-endpoint
-
交互示例:
Connected to wss://your-websocket-server.com/ws-endpoint > Hello, Server! # 发送消息 < Hello, Client! # 接收响应
方案 3:使用 Python 脚本
若需编程式测试,可用 Python 的 websockets
库:
-
安装库:
pip install websockets
-
示例代码:
import asyncio import websockets async def test_websocket(): async with websockets.connect("wss://your-websocket-server.com/ws-endpoint") as ws: await ws.send("Hello, Server!") response = await ws.recv() print(f"Server response: {response}") asyncio.get_event_loop().run_until_complete(test_websocket())
总结
-
仅需测试握手:使用
curl
手动构造 HTTP 升级请求。 -
完整交互测试:使用
wscat
或 Python 的websockets
库。 -
生产环境:推荐使用客户端 SDK(如 JavaScript 的
WebSocket
API)。
用 curl模拟 websocket 协议请求
$ curl -v http://172.16.0.5:11241/maco-platform/websocket/client \
--no-buffer \
-H 'Connection: keep-alive, Upgrade' \
-H 'Upgrade: websocket' \
-H 'Sec-WebSocket-Version: 13' \
-H 'Sec-WebSocket-Key: TDyiIDMBXScLr5NGpsKHaA=='
* About to connect() to 172.16.0.5 port 11241 (#0)
* Trying 172.16.0.5...
* Connected to 172.16.0.5 (172.16.0.5) port 11241 (#0)
> GET /maco-platform/websocket/client HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 172.16.0.5:11241
> Accept: */*
> Connection: keep-alive, Upgrade
> Upgrade: websocket
> Sec-WebSocket-Version: 13
> Sec-WebSocket-Key: TDyiIDMBXScLr5NGpsKHaA==
>
< HTTP/1.1 101 Switching Protocols
< Upgrade: WebSocket
< Connection: Upgrade
< Sec-WebSocket-Accept: PiTH8qQbYEZAX+2Sg0nRWU8hvEc=
* Empty reply from server
* Connection #0 to host 172.16.0.5 left intact
curl: (52) Empty reply from server
$ curl -v http://10.86.9.20:12613/maco-platform/websocket/client \
--no-buffer \
-H 'Connection: keep-alive, Upgrade' \
-H 'Upgrade: websocket' \
-H 'Sec-WebSocket-Version: 13' \
-H 'Sec-WebSocket-Key: TDyiIDMBXScLr5NGpsKHaA=='
* Trying 10.86.9.20:12613...
* Connected to 10.86.9.20 (10.86.9.20) port 12613 (#0)
> GET /maco-platform/websocket/client HTTP/1.1
> Host: 10.86.9.20:12613
> User-Agent: curl/7.71.1
> Accept: */*
> Connection: keep-alive, Upgrade
> Upgrade: websocket
> Sec-WebSocket-Version: 13
> Sec-WebSocket-Key: TDyiIDMBXScLr5NGpsKHaA==
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 500
< vary: accept-encoding
< Content-Type: text/html;charset=utf-8
< Content-Language: zh-CN
< Content-Length: 5166
< Date: Sat, 24 May 2025 05:14:55 GMT
< Connection: close
<
<!doctype html><html lang="zh"><head><title>HTTP状态 500 - 内部服务器错误</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP状态 500 - 内部服务器错误</h1><hr class="line" /><p><b>类型</b> 异常报告</p><p><b>消息</b> Servlet执行抛出一个异常</p><p><b>描述</b> 服务器遇到一个意外的情况,阻止它完成请求。</p><p><b>例外情况</b></p><pre>javax.servlet.ServletException: Servlet执行抛出一个异常
com.apusic.aas.websocket.server.WsFilter.doFilter(WsFilter.java:53)
com.weibaobiao.support.spring.AppLogServiceFilter.doFilter(AppLogServiceFilter.java:54)
org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:61)
org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:154)
org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:154)
org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:458)
org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:373)
org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:387)
org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:370)
org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:154)
org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:347)
org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:263)
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
</pre><p><b>根本原因。</b></p><pre>java.lang.NoClassDefFoundError: org/eclipse/jetty/server/AbstractHttpConnection
org.eclipse.jetty.websocket.WebSocketFactory.upgrade(WebSocketFactory.java:233)
org.eclipse.jetty.websocket.WebSocketFactory.acceptWebSocket(WebSocketFactory.java:382)
org.eclipse.jetty.websocket.WebSocketServlet.service(WebSocketServlet.java:104)
javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
com.apusic.aas.websocket.server.WsFilter.doFilter(WsFilter.java:53)
com.weibaobiao.support.spring.AppLogServiceFilter.doFilter(AppLogServiceFilter.java:54)
org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:61)
org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:154)
org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
org.apache.shiro.web.servlet.AdviceFilter.executeChain(AdviceFilter.java:108)
org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:137)
org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:154)
org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:458)
org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:373)
org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:387)
org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:370)
org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:154)
org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:347)
org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:263)
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
</pre><p><b>):注意</b> 主要问题的全部 stack 信息可以在 server logs 里查看</p><hr class="line" /></body></html>* Closing connection 0
从你的 curl
请求和服务器返回的 500
错误日志分析,问题出在服务器端依赖缺失(java.lang.NoClassDefFoundError: org/eclipse/jetty/server/AbstractHttpConnection
),而非客户端请求配置。以下是详细分析和解决方案:
问题原因
-
服务器依赖缺失:
- 错误日志显示
AbstractHttpConnection
类未找到,这是 Jetty 服务器旧版本(Jetty 8 或更早)中的类。 - 服务器使用的 WebSocket 实现(可能是旧版 Jetty 或相关库)依赖
AbstractHttpConnection
,但实际部署环境中缺少对应的 Jetty 库。
- 错误日志显示
-
版本不兼容:
- 如果服务器升级了 Jetty 到较新版本(如 Jetty 9+),
AbstractHttpConnection
已被移除,导致旧代码无法运行。
- 如果服务器升级了 Jetty 到较新版本(如 Jetty 9+),
解决方案
1. 检查服务器依赖
- 确认服务器使用的 Jetty 版本:
- 若使用 Jetty 8 或更早版本,确保
jetty-server
库已正确部署。 - 若使用 Jetty 9+,需修改 WebSocket 实现代码,替换对
AbstractHttpConnection
的依赖(参考 Jetty 9 的 WebSocket API)。
- 若使用 Jetty 8 或更早版本,确保
2. 修正 curl
命令
你的 curl
请求头部存在两个问题:
-
Connection
头格式错误:应仅包含Upgrade
,而非keep-alive, Upgrade
。 -
未使用
wss://
协议:如果服务器启用 TLS,需使用https://
或wss://
。
修正后的命令:
curl -v -N \
http://10.86.9.20:12613/maco-platform/websocket/client \
--no-buffer \
-H 'Connection: Upgrade' \
-H 'Upgrade: websocket' \
-H 'Sec-WebSocket-Version: 13' \
-H 'Sec-WebSocket-Key: TDyiIDMBXScLr5NGpsKHaA=='
3. 使用专用工具验证
即使 curl
握手成功,也无法进行后续消息交互。建议使用 wscat
测试完整流程:
wscat -c ws://10.86.9.20:12613/maco-platform/websocket/client
如果服务器依赖问题已修复,wscat
会显示连接成功。
4. 服务器端修复步骤
-
检查依赖树:
# Maven 项目 mvn dependency:tree | grep jetty # Gradle 项目 gradle dependencies | grep jetty
确保
jetty-server
和jetty-websocket
版本一致且兼容。 -
更新或降级 Jetty:
-
降级 Jetty(临时方案):
<!-- Maven 示例 --> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId> <version>8.2.0.v20160908</version> </dependency>
-
升级代码(推荐):
迁移到 Jetty 9+ 的 WebSocket API,删除对AbstractHttpConnection
的依赖。
-
降级 Jetty(临时方案):
验证流程
-
本地模拟服务器(快速验证):
// Jetty 8 示例 Server server = new Server(8080); WebSocketHandler wsHandler = new WebSocketHandler() { @Override public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { return new MyWebSocket(); } }; server.setHandler(wsHandler); server.start();
检查本地服务是否抛出相同错误。
-
对比测试:
- 使用
wscat
或浏览器 WebSocket 客户端连接,观察是否返回相同错误。
- 使用
总结
- 直接原因:服务器缺少 Jetty 旧版本依赖或代码版本不兼容。
- 修复方向:修正依赖版本或升级服务器 WebSocket 实现。
-
临时验证:通过修正后的
curl
或wscat
确认服务可用性。