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 全量
|
→ quota.fetchQuote() // HTTP 全量
|
||||||
→ BaiduQuoteWs.connect() // wss://finance-ws.pae.baidu.com/
|
→ BaiduQuoteWs.connect() // ws(s)://{host}/finance-ws/ → Vite/Nginx 代理到百度
|
||||||
→ subscribe tick + snapshot
|
→ subscribe tick + snapshot
|
||||||
→ 每 6s ping
|
→ 每 6s ping
|
||||||
→ 每 60s patch snapshot
|
→ 每 60s patch snapshot
|
||||||
@ -58,7 +58,7 @@
|
|||||||
|
|
||||||
## 4. 协议细节
|
## 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 后立即发送两条):**
|
**订阅(open 后立即发送两条):**
|
||||||
|
|
||||||
@ -143,4 +143,4 @@
|
|||||||
|
|
||||||
- WS 与 HTTP 的 snapshot 字段结构不完全一致(HTTP 用 `origin_pankou`,WS 用 `pankouinfos` 数组);mapper 需独立,勿强行复用 HTTP mapper 全文
|
- WS 与 HTTP 的 snapshot 字段结构不完全一致(HTTP 用 `origin_pankou`,WS 用 `pankouinfos` 数组);mapper 需独立,勿强行复用 HTTP mapper 全文
|
||||||
- 去重键不含服务端唯一 id(协议未提供);极端情况下同秒同价同量同向两笔可能被误去重,可接受
|
- 去重键不含服务端唯一 id(协议未提供);极端情况下同秒同价同量同向两笔可能被误去重,可接受
|
||||||
- 跨域 / 鉴权:浏览器直连百度 WS;若环境拦截再评估代理,本期先直连
|
- 跨域 / 鉴权:浏览器不能直连(Origin=localhost → 403)。开发经 Vite 代理 `/finance-ws`,并把 Origin/Referer 改写为 `https://gushitong.baidu.com`;生产需同等反向代理。
|
||||||
|
|||||||
@ -1,12 +1,21 @@
|
|||||||
import { contractConfig } from '../../config/contract'
|
import { contractConfig } from '../../config/contract'
|
||||||
import type { BaiduWsItem, BaiduWsMessage, BaiduWsOutbound } from './wsTypes'
|
import type { BaiduWsItem, BaiduWsMessage, BaiduWsOutbound } from './wsTypes'
|
||||||
|
|
||||||
const WS_URL = 'wss://finance-ws.pae.baidu.com/'
|
|
||||||
const PING_MS = 6_000
|
const PING_MS = 6_000
|
||||||
const PATCH_MS = 60_000
|
const PATCH_MS = 60_000
|
||||||
const RECONNECT_BASE_MS = 1_000
|
const RECONNECT_BASE_MS = 1_000
|
||||||
const RECONNECT_MAX_MS = 30_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 = {
|
export type BaiduQuoteWsHandlers = {
|
||||||
onMessage: (msg: BaiduWsMessage) => void
|
onMessage: (msg: BaiduWsMessage) => void
|
||||||
onError?: (err: unknown) => void
|
onError?: (err: unknown) => void
|
||||||
@ -71,7 +80,7 @@ export class BaiduQuoteWs {
|
|||||||
private openSocket(): void {
|
private openSocket(): void {
|
||||||
if (this.intentionalClose) return
|
if (this.intentionalClose) return
|
||||||
|
|
||||||
const ws = new WebSocket(WS_URL)
|
const ws = new WebSocket(resolveWsUrl())
|
||||||
this.ws = ws
|
this.ws = ws
|
||||||
|
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import { defineConfig } from 'vite'
|
|||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
import { fileURLToPath, URL } from 'node:url'
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
const BAIDU_ORIGIN = 'https://gushitong.baidu.com'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
resolve: {
|
resolve: {
|
||||||
@ -11,12 +13,30 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
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': {
|
'/baidu': {
|
||||||
target: 'https://finance.pae.baidu.com',
|
target: 'https://finance.pae.baidu.com',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/baidu/, ''),
|
rewrite: (path) => path.replace(/^\/baidu/, ''),
|
||||||
headers: {
|
headers: {
|
||||||
Referer: 'https://gushitong.baidu.com/',
|
Referer: `${BAIDU_ORIGIN}/`,
|
||||||
'User-Agent':
|
'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',
|
'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