- 新增 launch.queryHttpDns:类型定义 + HTTPClient.QueryHttpDns + WSSClient.resolveHost(TTL 缓存、多 host 轮询、失败回退默认 host);Host 留空或等于历史默认值即视为自动解析 - WSS 握手 baseinfo 补 tag5=exp 与 tag10 空 customHeaders;UpdateUserExpsReq(cmd23) 改为回传 getConfig 的版本 map(缓存供重连复用) - 新增 launch.pingSignal:HTTP 预热改走 pingSignal,WSS 预热在建连后探测业务通道(失败仅记日志) - UserPrizeInfo 补 tag12 orderId/tag14 图标/tag16 prizeId/tag17 shopItemId/tag19 itemId/tag20 ext/tag21 exchangeDate,前端复盘表加订单号列 - taf 补 WriteListString/ReadListString/ReadMapStringInt64/ReadMapStringStruct - 新增 internal/huya/launch_test.go:用抓包真实报文做字节级夹具(baseinfo 122B、queryHttpDns 请求 93B、记录 308B 等) - CLI -wss-host 默认改为留空;GUI 设置支持留空并给出占位提示;README/WSS_NOTES 同步协议细节
170 lines
3.8 KiB
Go
170 lines
3.8 KiB
Go
package huya
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"net/url"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"huya-go/internal/taf"
|
|
)
|
|
|
|
func TestWSRegisterGroupReqEncoding(t *testing.T) {
|
|
w := taf.NewWriter()
|
|
req := &wsRegisterGroupReq{GroupIDs: []string{WSSGroupID}}
|
|
if err := req.WriteTo(w); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := hex.EncodeToString(w.Bytes())
|
|
want := "09000106066c6976653a301600"
|
|
if got != want {
|
|
t.Fatalf("register group bytes = %s, want %s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestWSUpdateUserExpsReqEncoding(t *testing.T) {
|
|
w := taf.NewWriter()
|
|
if err := (&wsUpdateUserExpsReq{}).WriteTo(w); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := hex.EncodeToString(w.Bytes())
|
|
want := "080c"
|
|
if got != want {
|
|
t.Fatalf("update user exps bytes = %s, want %s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestGenerateWSSBaseinfoMatchesBrowserShape(t *testing.T) {
|
|
encoded, err := GenerateWSSBaseinfo(123, "guid-value", "58664.107263")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
unescaped, err := url.QueryUnescape(encoded)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
raw, err := base64.StdEncoding.DecodeString(unescaped)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
r := taf.NewReader(raw)
|
|
uid, err := r.ReadInt64(0, true, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
guid, err := r.ReadString(1, true, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ua, err := r.ReadString(2, true, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
appSrc, err := r.ReadString(3, true, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
exp, err := r.ReadString(5, true, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cookie, err := r.ReadString(8, false, "not-empty")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
traceID, err := r.ReadString(9, false, "not-empty")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if uid != 123 || guid != "guid-value" || ua != WSSCookieUA || appSrc != "HUYA&ZH&2052" {
|
|
t.Fatalf("unexpected baseinfo: uid=%d guid=%q ua=%q appSrc=%q", uid, guid, ua, appSrc)
|
|
}
|
|
if exp != "58664.107263" {
|
|
t.Fatalf("baseinfo exp=%q, want 58664.107263", exp)
|
|
}
|
|
if cookie != "" || traceID != "" {
|
|
t.Fatalf("wss baseinfo should not carry cookie/trace: cookie=%q trace=%q", cookie, traceID)
|
|
}
|
|
}
|
|
|
|
func TestNewWSSCommandTraceMatchesBrowserShape(t *testing.T) {
|
|
trace := NewWSSCommandTrace()
|
|
parts := strings.Split(trace, ":")
|
|
if len(parts) != 4 {
|
|
t.Fatalf("trace parts = %v", parts)
|
|
}
|
|
if parts[0] == "" || parts[1] == "" || parts[0] != parts[1] {
|
|
t.Fatalf("unexpected trace prefix: %q", trace)
|
|
}
|
|
if parts[2] != "0" || parts[3] != "0" {
|
|
t.Fatalf("trace suffix = %q, want :0:0", trace)
|
|
}
|
|
}
|
|
|
|
func TestSessionSharesSingleDialPerUID(t *testing.T) {
|
|
client := NewWSSClient(nil)
|
|
dialStarted := make(chan struct{}, 1)
|
|
release := make(chan struct{})
|
|
var dials atomic.Int32
|
|
client.dialFunc = func(ctx context.Context, uid int64, cookie string) (*WSSSession, error) {
|
|
dials.Add(1)
|
|
select {
|
|
case dialStarted <- struct{}{}:
|
|
default:
|
|
}
|
|
<-release
|
|
return &WSSSession{
|
|
client: client,
|
|
uid: uid,
|
|
cookie: cookie,
|
|
closed: make(chan struct{}),
|
|
pending: map[int32]chan rpcResult{},
|
|
}, nil
|
|
}
|
|
|
|
const callers = 4
|
|
sessions := make([]*WSSSession, callers)
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < callers; i++ {
|
|
wg.Add(1)
|
|
go func(i int) {
|
|
defer wg.Done()
|
|
session, err := client.session(context.Background(), 42, "yyuid=42")
|
|
if err != nil {
|
|
t.Errorf("session: %v", err)
|
|
return
|
|
}
|
|
sessions[i] = session
|
|
}(i)
|
|
}
|
|
|
|
select {
|
|
case <-dialStarted:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("dial was not started")
|
|
}
|
|
// 留出时间让其余调用进入等待分支。
|
|
time.Sleep(50 * time.Millisecond)
|
|
close(release)
|
|
wg.Wait()
|
|
|
|
if got := dials.Load(); got != 1 {
|
|
t.Fatalf("dials=%d, want 1", got)
|
|
}
|
|
for i, session := range sessions {
|
|
if session == nil {
|
|
t.Fatalf("caller %d got nil session", i)
|
|
}
|
|
if session != sessions[0] {
|
|
t.Fatalf("caller %d got a different session", i)
|
|
}
|
|
}
|
|
}
|