Files
wehub-resource-sync e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
SDK Tests / SDK CI (push) Has been cancelled
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:39:33 +08:00

230 lines
7.1 KiB
Go

// Copyright 2025 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package proxy
import (
"context"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"time"
"github.com/alibaba/opensandbox/ingress/pkg/sandbox"
slogger "github.com/alibaba/opensandbox/internal/logger"
"github.com/stretchr/testify/assert"
)
// mockProvider implements sandbox.Provider interface for testing
type mockProvider struct {
endpoints map[string]string // sandboxName -> IP
notReady map[string]bool // sandboxName -> notReady flag
accessToken map[string]string // sandboxName -> opensandbox.io/secure-access-token value (non-empty => verification required)
}
func (m *mockProvider) sandboxExists(sandboxId string) bool {
if m.notReady != nil && m.notReady[sandboxId] {
return true
}
_, ok := m.endpoints[sandboxId]
return ok
}
func (m *mockProvider) GetEndpoint(sandboxId string) (*sandbox.EndpointInfo, error) {
if m.notReady != nil && m.notReady[sandboxId] {
return nil, fmt.Errorf("%w: %s", sandbox.ErrSandboxNotReady, sandboxId)
}
if !m.sandboxExists(sandboxId) {
return nil, fmt.Errorf("%w: %s", sandbox.ErrSandboxNotFound, sandboxId)
}
ip := m.endpoints[sandboxId]
token := ""
if m.accessToken != nil {
token = strings.TrimSpace(m.accessToken[sandboxId])
}
if ip == "" {
return nil, fmt.Errorf("%w: %s", sandbox.ErrSandboxNotFound, sandboxId)
}
return &sandbox.EndpointInfo{
Endpoint: ip,
SecureAccessToken: token,
}, nil
}
func (m *mockProvider) Start(_ context.Context) error {
return nil
}
func Test_HTTPProxy(t *testing.T) {
t.Run("with header mode", func(t *testing.T) {
httpProxyWithHeaderMode(t)
})
t.Run("with uri mode", func(t *testing.T) {
httpProxyWithURIMode(t)
})
}
func httpProxyWithHeaderMode(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(realBackendHTTPHandler))
defer server.Close()
serverPort := server.URL[len("http://127.0.0.1:"):]
// Create mock provider with sandbox endpoint
provider := &mockProvider{
endpoints: map[string]string{
"test-sandbox": "127.0.0.1",
},
}
ctx := context.Background()
Logger = slogger.MustNew(slogger.Config{Level: "debug"})
proxy := NewProxy(ctx, provider, ModeHeader, nil, nil)
mux := http.NewServeMux()
mux.Handle("/", proxy)
port, err := findAvailablePort()
assert.Nil(t, err)
go func() {
assert.NoError(t, http.ListenAndServe(":"+strconv.Itoa(port), mux))
}()
time.Sleep(2 * time.Second)
// no header
request, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("http://127.0.0.1:%v/hello", port), nil)
assert.Nil(t, err)
response, err := http.DefaultClient.Do(request)
assert.Nil(t, err)
assert.Equal(t, http.StatusBadRequest, response.StatusCode)
bytes, _ := io.ReadAll(response.Body)
t.Log(string(bytes))
// no sandbox backend
request, err = http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("http://127.0.0.1:%v/hello", port), nil)
request.Header.Set(SandboxIngress, fmt.Sprintf("non-existent-%v", port))
response, err = http.DefaultClient.Do(request)
assert.Nil(t, err)
assert.Equal(t, http.StatusNotFound, response.StatusCode) // ErrSandboxNotFound -> 404
bytes, _ = io.ReadAll(response.Body)
t.Log(string(bytes))
// valid sandbox request
request, err = http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("http://127.0.0.1:%v/hello?a=1&b=2", port), nil)
assert.Nil(t, err)
request.Header.Set(SandboxIngress, fmt.Sprintf("test-sandbox-%v", serverPort))
response, err = http.DefaultClient.Do(request)
assert.Nil(t, err)
if response.StatusCode != http.StatusOK {
bytes, err := io.ReadAll(response.Body)
assert.Nil(t, err)
t.Log(string(bytes))
}
assert.Equal(t, http.StatusOK, response.StatusCode)
// Compatible Host parsing for reverse proxy mode
request, err = http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("http://127.0.0.1:%v/hello?a=1&b=2", port), nil)
assert.Nil(t, err)
request.Host = fmt.Sprintf("test-sandbox-%v.sandbox.alibaba-inc.com", serverPort)
response, err = http.DefaultClient.Do(request)
assert.Nil(t, err)
if response.StatusCode != http.StatusOK {
bytes, err := io.ReadAll(response.Body)
assert.Nil(t, err)
t.Log(string(bytes))
}
assert.Equal(t, http.StatusOK, response.StatusCode)
}
func httpProxyWithURIMode(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(realBackendHTTPHandler))
defer server.Close()
serverPort := server.URL[len("http://127.0.0.1:"):]
// Create mock provider with sandbox endpoint
provider := &mockProvider{
endpoints: map[string]string{
"test-sandbox": "127.0.0.1",
},
}
ctx := context.Background()
Logger = slogger.MustNew(slogger.Config{Level: "debug"})
proxy := NewProxy(ctx, provider, ModeURI, nil, nil)
mux := http.NewServeMux()
mux.Handle("/", proxy)
port, err := findAvailablePort()
assert.Nil(t, err)
go func() {
assert.NoError(t, http.ListenAndServe(":"+strconv.Itoa(port), mux))
}()
time.Sleep(2 * time.Second)
// uri is empty
request, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("http://127.0.0.1:%v", port), nil)
assert.Nil(t, err)
response, err := http.DefaultClient.Do(request)
assert.Nil(t, err)
assert.Equal(t, http.StatusBadRequest, response.StatusCode)
bytes, _ := io.ReadAll(response.Body)
t.Log(string(bytes))
// no sandbox backend
request, err = http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("http://127.0.0.1:%v/non-existent-xxx/80/hello", port), nil)
response, err = http.DefaultClient.Do(request)
assert.Nil(t, err)
assert.Equal(t, http.StatusNotFound, response.StatusCode) // ErrSandboxNotFound -> 404
bytes, _ = io.ReadAll(response.Body)
t.Log(string(bytes))
// valid sandbox request
request, err = http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("http://127.0.0.1:%v/test-sandbox/%v/hello?a=1&b=2", port, serverPort), nil)
assert.Nil(t, err)
response, err = http.DefaultClient.Do(request)
assert.Nil(t, err)
if response.StatusCode != http.StatusOK {
bytes, err := io.ReadAll(response.Body)
assert.Nil(t, err)
t.Log(string(bytes))
}
assert.Equal(t, http.StatusOK, response.StatusCode)
}
func realBackendHTTPHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
if r.URL.Path != "/hello" {
http.Error(w, fmt.Sprintf("path is not /hello, but %s", r.URL.Path), http.StatusBadRequest)
}
if r.URL.RawQuery != "a=1&b=2" {
http.Error(w, fmt.Sprintf("query is not a=1&b=2, but %s", r.URL.RawQuery), http.StatusBadRequest)
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("hello world"))
}