我正在用vuejs + vite开发SPA
我的后端有一个 websocket 端点。
我可以通过 cli 工具连接到该 websocket
websocat
。
但是当尝试在 javascript 中连接时,firefox 和 chrome 都显示连接错误。
vite 开发服务器使用以下配置代理我的后端:
server: {
// 允许IP访问
host: "portal-local.itps.xxx.com",
// 应用端口 (默认:3000)
port: Number(env.VITE_APP_PORT),
// 运行是否自动打开浏览器
open: true,
https: {
key: fs.readFileSync(
"/home/ggfan/2-work/git/itps/1A-IaaS-Manifests/kubernetes/1.27/ssl/certs/itps.xxx.com/privkey.pem"
),
cert: fs.readFileSync(
"/home/ggfan/2-work/git/itps/1A-IaaS-Manifests/kubernetes/1.27/ssl/certs/itps.xxx.com/fullchain.pem"
),
},
proxy: {
/**
* 反向代理解决跨域配置
* http://localhost:3000/dev-api/users (F12可见请求路径) => http://localhost:8989/users (实际请求后端 API 路径)
*
* env.VITE_APP_BASE_API: /dev-api
* env.VITE_APP_API_URL: http://localhost:8989
*/
/*
[env.VITE_APP_BASE_API]: {
changeOrigin: true,
target: "http://localhost:8080",
rewrite: (path) =>
path.replace(new RegExp("^" + env.VITE_APP_BASE_API), ""),
},
*/
["/oauth2"]: {
changeOrigin: true,
target: "https://api.itps.xxx.com",
// secure: false,
rewrite: (path) => path.replace(new RegExp("^" + env.VITE_APP_BASE_API), ""),
/*
cookieDomainRewrite: {
"itps.xxx.com": "localhost",
},
*/
},
["/pus"]: {
changeOrigin: true,
target: "http://localhost:8080",
// secure: false,
rewrite: (path) => path.replace(/^\/pus/, ""),
/*
cookieDomainRewrite: {
"itps.xxx.com": "localhost",
},
*/
configure: proxyConfig,
},
["/ws"]: {
changeOrigin: true,
target: "http://localhost:8080",
ws: true,
secure: false,
rewrite: (path) => path.replace(/^\/ws/, ""),
configure: proxyConfig,
},
},
}
我的打字稿代码:
const ws = new WebSocket("wss://portal-local.itps.xxx.com:3000/ws/export/sea/users")
ws.onopen = () => {
ws.send("{}")
}
ws.onmessage = async (event) => {
console.log(event.data)
await writable.write(event.data)
}
ws.onclose = async (event) => {
await writable.close()
}
Chrome 错误:
SEAExportDialog.vue:104 WebSocket connection to 'wss://portal-local.itps.xxx.com:3000/ws/export/sea/users' failed:
如果重要的话,证书是从 Let's Encrypt 获得的。
使用wireshark,我将问题范围缩小到vite代理服务器响应403:
Frame 22: 844 bytes on wire (6752 bits), 844 bytes captured (6752 bits) on interface lo, id 0
Ethernet II, Src: 00:00:00_00:00:00 (00:00:00:00:00:00), Dst: 00:00:00_00:00:00 (00:00:00:00:00:00)
Internet Protocol Version 4, Src: 127.0.0.1, Dst: 127.0.0.1
Transmission Control Protocol, Src Port: 38478, Dst Port: 3000, Seq: 683, Ack: 4150, Len: 778
Transport Layer Security
Hypertext Transfer Protocol
GET /ws/export/sea/users HTTP/1.1\r\n
[Expert Info (Chat/Sequence): GET /ws/export/sea/users HTTP/1.1\r\n]
[GET /ws/export/sea/users HTTP/1.1\r\n]
[Severity level: Chat]
[Group: Sequence]
Request Method: GET
Request URI: /ws/export/sea/users
Request Version: HTTP/1.1
Host: portal-local.itps.xxx.com:3000\r\n
Connection: Upgrade\r\n
Pragma: no-cache\r\n
Cache-Control: no-cache\r\n
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36\r\n
Upgrade: websocket\r\n
Origin: https://portal-local.itps.xxx.com:3000\r\n
Sec-WebSocket-Version: 13\r\n
Accept-Encoding: gzip, deflate, br, zstd\r\n
Accept-Language: zh-CN,zh;q=0.9\r\n
Cookie: _oauth2_proxy=X29hdXRoMl9wcm94eS00ZWU3OTM2NTIwYWYxNjBjNjZlNGQ4ODJkNTA0OGE4Yy5NM0FOUzVJRlBTV2JkN1g2Zlh6RWtB|1713431461|XkFGp00nkvsob926le3vxJ61wpGV44gtE1GeZcl4eEg=\r\n
Cookie pair: _oauth2_proxy=X29hdXRoMl9wcm94eS00ZWU3OTM2NTIwYWYxNjBjNjZlNGQ4ODJkNTA0OGE4Yy5NM0FOUzVJRlBTV2JkN1g2Zlh6RWtB|1713431461|XkFGp00nkvsob926le3vxJ61wpGV44gtE1GeZcl4eEg=
Sec-WebSocket-Key: FLMsmXSIxrT0bV1TA8yd/Q==\r\n
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n
Sec-WebSocket-Protocol: WebSocket\r\n
\r\n
[Full request URI: https://portal-local.itps.xxx.com:3000/ws/export/sea/users]
[HTTP request 1/1]
[Response in frame: 24]
Frame 24: 161 bytes on wire (1288 bits), 161 bytes captured (1288 bits) on interface lo, id 0
Ethernet II, Src: 00:00:00_00:00:00 (00:00:00:00:00:00), Dst: 00:00:00_00:00:00 (00:00:00:00:00:00)
Internet Protocol Version 4, Src: 127.0.0.1, Dst: 127.0.0.1
Transmission Control Protocol, Src Port: 3000, Dst Port: 38478, Seq: 4724, Ack: 1461, Len: 95
Transport Layer Security
Hypertext Transfer Protocol
HTTP/1.1 403 \r\n
[Expert Info (Chat/Sequence): HTTP/1.1 403 \r\n]
[HTTP/1.1 403 \r\n]
[Severity level: Chat]
[Group: Sequence]
Response Version: HTTP/1.1
Status Code: 403
[Status Code Description: Forbidden]
content-length: 0\r\n
[Content length: 0]
date: Thu, 18 Apr 2024 09:24:37 GMT\r\n
\r\n
[HTTP response 1/1]
[Time since request: 0.011595626 seconds]
[Request in frame: 22]
[Request URI: https://portal-local.itps.xxx.com:3000/ws/export/sea/users]
事实证明:
后端服务器返回403响应。
对我来说是 spring boot + tomcat,我需要设置允许的来源:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
private SEAExportWSHandler seaExportWSHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(seaExportWSHandler, "/export/**").setAllowedOriginPatterns("*");
}
}
但奇怪的是,chrome 浏览器吞下了 403 响应并给出空的错误消息。