fix(ws): proxy Baidu WebSocket via Vite to bypass Origin 403
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
497236b8b4
commit
1c1f370d59
@ -31,7 +31,7 @@
|
||||
```
|
||||
页面挂载
|
||||
→ quota.fetchQuote() // HTTP 全量
|
||||
→ BaiduQuoteWs.connect() // wss://finance-ws.pae.baidu.com/
|
||||
→ BaiduQuoteWs.connect() // ws(s)://{host}/finance-ws/ → Vite/Nginx 代理到百度
|
||||
→ subscribe tick + snapshot
|
||||
→ 每 6s ping
|
||||
→ 每 60s patch snapshot
|
||||
@ -58,7 +58,7 @@
|
||||
|
||||
## 4. 协议细节
|
||||
|
||||
**端点:** `wss://finance-ws.pae.baidu.com/`
|
||||
**端点:** 浏览器连同域 `/finance-ws/`;由 Vite(开发)或 Nginx(生产)代理到 `wss://finance-ws.pae.baidu.com/`,并改写 `Origin`/`Referer` 为 `https://gushitong.baidu.com`(否则 localhost Origin 会 403)。
|
||||
|
||||
**订阅(open 后立即发送两条):**
|
||||
|
||||
@ -143,4 +143,4 @@
|
||||
|
||||
- WS 与 HTTP 的 snapshot 字段结构不完全一致(HTTP 用 `origin_pankou`,WS 用 `pankouinfos` 数组);mapper 需独立,勿强行复用 HTTP mapper 全文
|
||||
- 去重键不含服务端唯一 id(协议未提供);极端情况下同秒同价同量同向两笔可能被误去重,可接受
|
||||
- 跨域 / 鉴权:浏览器直连百度 WS;若环境拦截再评估代理,本期先直连
|
||||
- 跨域 / 鉴权:浏览器不能直连(Origin=localhost → 403)。开发经 Vite 代理 `/finance-ws`,并把 Origin/Referer 改写为 `https://gushitong.baidu.com`;生产需同等反向代理。
|
||||
|
||||
@ -1,12 +1,21 @@
|
||||
import { contractConfig } from '../../config/contract'
|
||||
import type { BaiduWsItem, BaiduWsMessage, BaiduWsOutbound } from './wsTypes'
|
||||
|
||||
const WS_URL = 'wss://finance-ws.pae.baidu.com/'
|
||||
const PING_MS = 6_000
|
||||
const PATCH_MS = 60_000
|
||||
const RECONNECT_BASE_MS = 1_000
|
||||
const RECONNECT_MAX_MS = 30_000
|
||||
|
||||
/**
|
||||
* Same-origin path proxied by Vite/Nginx to wss://finance-ws.pae.baidu.com.
|
||||
* Direct browser connect sends Origin=localhost and gets 403.
|
||||
*/
|
||||
function resolveWsUrl(): string {
|
||||
const proto = typeof location !== 'undefined' && location.protocol === 'https:' ? 'wss:' : 'ws:'
|
||||
const host = typeof location !== 'undefined' ? location.host : 'localhost:5173'
|
||||
return `${proto}//${host}/finance-ws/`
|
||||
}
|
||||
|
||||
export type BaiduQuoteWsHandlers = {
|
||||
onMessage: (msg: BaiduWsMessage) => void
|
||||
onError?: (err: unknown) => void
|
||||
@ -71,7 +80,7 @@ export class BaiduQuoteWs {
|
||||
private openSocket(): void {
|
||||
if (this.intentionalClose) return
|
||||
|
||||
const ws = new WebSocket(WS_URL)
|
||||
const ws = new WebSocket(resolveWsUrl())
|
||||
this.ws = ws
|
||||
|
||||
ws.onopen = () => {
|
||||
|
||||
@ -2,6 +2,8 @@ import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
const BAIDU_ORIGIN = 'https://gushitong.baidu.com'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
resolve: {
|
||||
@ -11,12 +13,30 @@ export default defineConfig({
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
/**
|
||||
* WebSocket must be registered before `/baidu` so it is not swallowed.
|
||||
* Baidu rejects non-baidu Origins (localhost → 403); rewrite Origin here.
|
||||
*/
|
||||
'/finance-ws': {
|
||||
target: 'wss://finance-ws.pae.baidu.com',
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
rewrite: (path) => path.replace(/^\/finance-ws/, '') || '/',
|
||||
configure: (proxy) => {
|
||||
const setBaiduHeaders = (proxyReq: { setHeader: (k: string, v: string) => void }) => {
|
||||
proxyReq.setHeader('Origin', BAIDU_ORIGIN)
|
||||
proxyReq.setHeader('Referer', `${BAIDU_ORIGIN}/`)
|
||||
}
|
||||
proxy.on('proxyReq', setBaiduHeaders)
|
||||
proxy.on('proxyReqWs', setBaiduHeaders)
|
||||
},
|
||||
},
|
||||
'/baidu': {
|
||||
target: 'https://finance.pae.baidu.com',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/baidu/, ''),
|
||||
headers: {
|
||||
Referer: 'https://gushitong.baidu.com/',
|
||||
Referer: `${BAIDU_ORIGIN}/`,
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user