Skip to content
Open
2 changes: 1 addition & 1 deletion components/ingress/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/alibaba/OpenSandbox/sandbox-k8s v0.0.0
github.com/alibaba/opensandbox/internal v0.0.0
github.com/alicebob/miniredis/v2 v2.37.0
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
github.com/coder/websocket v1.8.15
github.com/redis/go-redis/v9 v9.18.0
github.com/stretchr/testify v1.11.1
k8s.io/apimachinery v0.34.3
Expand Down
4 changes: 2 additions & 2 deletions components/ingress/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA=
github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
Expand Down Expand Up @@ -46,8 +48,6 @@ github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgY
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
Expand Down
10 changes: 9 additions & 1 deletion components/ingress/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,15 @@ func main() {
http.Handle("/", reverseProxy)
http.HandleFunc("/status.ok", proxy.Healthz)

if err := http.ListenAndServe(fmt.Sprintf(":%v", flag.Port), nil); err != nil {
protos := new(http.Protocols)
protos.SetHTTP1(true)
protos.SetUnencryptedHTTP2(true)
Comment thread
Pangjiping marked this conversation as resolved.
srv := &http.Server{
Addr: fmt.Sprintf(":%v", flag.Port),
Protocols: protos,
}

if err := srv.ListenAndServe(); err != nil {
log.Panicf("Error starting http server: %v", err)
}

Expand Down
27 changes: 21 additions & 6 deletions components/ingress/pkg/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,31 @@ func (p *Proxy) serve(w http.ResponseWriter, r *http.Request) {
}

func (p *Proxy) isWebSocketRequest(r *http.Request) bool {
if r.Method != http.MethodGet {
return false
// HTTP/2 Extended CONNECT (RFC 8441)
if r.Method == http.MethodConnect && r.ProtoMajor >= 2 &&
strings.EqualFold(r.Header.Get(":protocol"), "websocket") {
return true
}
if r.Header.Get("Upgrade") != "websocket" {
// HTTP/1.1 Upgrade
if r.Method != http.MethodGet {
return false
}
if r.Header.Get("Connection") != "Upgrade" {
return false
return headerContainsToken(r.Header, "Connection", "Upgrade") &&
headerContainsToken(r.Header, "Upgrade", "websocket")
}

// headerContainsToken checks whether a comma-separated HTTP header contains
// the given token (case-insensitive). This handles L7 proxies that send
// multi-value headers like "Connection: keep-alive, Upgrade".
func headerContainsToken(h http.Header, key, token string) bool {
for _, v := range h[http.CanonicalHeaderKey(key)] {
for _, s := range strings.Split(v, ",") {
if strings.EqualFold(strings.TrimSpace(s), token) {
return true
}
}
}
return true
return false
}

func (p *Proxy) resolveRealHost(host *sandboxHost) (string, error, int) {
Expand Down
25 changes: 25 additions & 0 deletions components/ingress/pkg/proxy/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,31 @@ func TestIsWebSocketRequest(t *testing.T) {
req.Header.Set("Upgrade", "websocket")
req.Header.Set("Connection", "Upgrade")
assert.False(t, proxy.isWebSocketRequest(req))

// Connection header with multiple tokens (L7 proxy scenario)
req = httptest.NewRequest(http.MethodGet, "/ws", nil)
req.Header.Set("Upgrade", "websocket")
req.Header.Set("Connection", "keep-alive, Upgrade")
assert.True(t, proxy.isWebSocketRequest(req))

// Case-insensitive headers
req = httptest.NewRequest(http.MethodGet, "/ws", nil)
req.Header.Set("Upgrade", "WebSocket")
req.Header.Set("Connection", "upgrade")
assert.True(t, proxy.isWebSocketRequest(req))

// HTTP/2 Extended CONNECT (RFC 8441)
req = httptest.NewRequest(http.MethodConnect, "/ws", nil)
req.ProtoMajor = 2
req.ProtoMinor = 0
req.Header.Set(":protocol", "websocket")
assert.True(t, proxy.isWebSocketRequest(req))

// HTTP/2 CONNECT without :protocol is not WebSocket
req = httptest.NewRequest(http.MethodConnect, "/ws", nil)
req.ProtoMajor = 2
req.ProtoMinor = 0
assert.False(t, proxy.isWebSocketRequest(req))
}

func TestParseHostRoute(t *testing.T) {
Expand Down
Loading
Loading