fix(gateway): avoid duplicate Accept-Encoding headers

This commit is contained in:
chihao.ou
2026-09-11 19:19:40 +08:00
parent 4726bdd08b
commit 213de0797e
2 changed files with 83 additions and 1 deletions
@@ -0,0 +1,81 @@
package service
import (
"context"
"io"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
)
func TestGatewayService_AcceptEncodingOnWire(t *testing.T) {
gin.SetMode(gin.TestMode)
for _, protocol := range []struct {
name string
major int
}{
{name: "HTTP1", major: 1},
{name: "HTTP2", major: 2},
} {
t.Run(protocol.name, func(t *testing.T) {
type receivedRequest struct {
encodings []string
proto int
}
received := make(chan receivedRequest, 1)
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
received <- receivedRequest{encodings: r.Header.Values("Accept-Encoding"), proto: r.ProtoMajor}
w.WriteHeader(http.StatusNoContent)
}))
server.EnableHTTP2 = protocol.major == 2
server.StartTLS()
defer server.Close()
client := server.Client()
client.Timeout = 5 * time.Second
upstreamURL, err := url.Parse(server.URL)
require.NoError(t, err)
for _, tc := range []struct {
name string
encoding string
want string
}{
{name: "gzip", encoding: "gzip", want: "gzip"},
{name: "multiple encodings", encoding: "gzip, deflate, br", want: "gzip, deflate, br"},
{name: "identity", encoding: "identity", want: "identity"},
{name: "omitted", want: "gzip"},
} {
t.Run(tc.name, func(t *testing.T) {
c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Request = httptest.NewRequest(http.MethodPost, "/v1/messages", nil)
if tc.encoding != "" {
c.Request.Header.Set("Accept-Encoding", tc.encoding)
}
svc := &GatewayService{cfg: &config.Config{}}
account := &Account{Platform: PlatformAnthropic, Type: AccountTypeOAuth}
req, _, err := svc.buildUpstreamRequest(context.Background(), c, account,
[]byte(`{"model":"claude-sonnet-4-6","messages":[]}`), "test-token", "oauth", "claude-sonnet-4-6", false, false)
require.NoError(t, err)
req.URL.Scheme = upstreamURL.Scheme
req.URL.Host = upstreamURL.Host
req.Host = ""
resp, err := client.Do(req)
require.NoError(t, err)
_, err = io.Copy(io.Discard, resp.Body)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
got := <-received
require.Equal(t, protocol.major, got.proto)
require.Equal(t, []string{tc.want}, got.encodings)
})
}
})
}
}
+2 -1
View File
@@ -6,6 +6,7 @@ import (
)
// headerWireCasing 定义每个白名单 header 在真实 Claude CLI 抓包中的准确大小写。
// Accept-Encoding keeps canonical casing so net/http recognizes explicit compression negotiation.
// Go 的 HTTP server 解析请求时会将所有 header key 转为 Canonical 形式(如 x-app → X-App),
// 此 map 用于在转发时恢复到真实的 wire format。
//
@@ -34,7 +35,7 @@ var headerWireCasing = map[string]string{
"content-type": "content-type",
"accept-language": "accept-language",
"sec-fetch-mode": "sec-fetch-mode",
"accept-encoding": "accept-encoding",
"accept-encoding": "Accept-Encoding",
"authorization": "authorization",
// Claude Code 2.1.87+ 新增 header