feat: AI 服务商接口兼容 HTTP (#223)
This commit is contained in:
@@ -328,8 +328,12 @@ func ParseConfidenceThresholdBPS(raw string) (int, error) {
|
||||
|
||||
func (p AIEndpointPolicy) ValidateSyntax(raw string) (string, error) {
|
||||
parsed, err := url.Parse(strings.TrimSpace(raw))
|
||||
if err != nil || parsed.Scheme != "https" || parsed.Host == "" {
|
||||
return "", &validationError{field: "base_url", message: "Base URL 必须是完整的 HTTPS 地址"}
|
||||
if err != nil || parsed.Host == "" {
|
||||
return "", &validationError{field: "base_url", message: "Base URL 必须是完整的 HTTP 或 HTTPS 地址"}
|
||||
}
|
||||
parsed.Scheme = strings.ToLower(parsed.Scheme)
|
||||
if parsed.Scheme != "http" && parsed.Scheme != "https" {
|
||||
return "", &validationError{field: "base_url", message: "Base URL 只支持 HTTP 或 HTTPS"}
|
||||
}
|
||||
if parsed.User != nil || parsed.RawQuery != "" || parsed.Fragment != "" {
|
||||
return "", &validationError{field: "base_url", message: "Base URL 不能包含账号密码、查询参数或片段"}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -57,23 +58,56 @@ func TestSaveAIProviderConfig_新增后可查询并与审计同事务(t *testing
|
||||
}
|
||||
}
|
||||
|
||||
func TestAIEndpointPolicy_阻止凭据和内网地址(t *testing.T) {
|
||||
func TestAIEndpointPolicy_兼容HTTP并阻止凭据和内网地址(t *testing.T) {
|
||||
policy := NewAIEndpointPolicy(nil)
|
||||
for _, raw := range []string{
|
||||
"http://api.example.com/v1", "https://user:pass@api.example.com/v1",
|
||||
"https://127.0.0.1/v1", "https://169.254.169.254/latest", "https://localhost/v1",
|
||||
"ftp://api.example.com/v1", "http://user:pass@api.example.com/v1", "https://user:pass@api.example.com/v1",
|
||||
"http://api.example.com/v1?token=fake", "https://api.example.com/v1#fragment",
|
||||
"http://127.0.0.1/v1", "https://127.0.0.1/v1", "http://169.254.169.254/latest",
|
||||
"https://169.254.169.254/latest", "http://localhost/v1", "https://localhost/v1",
|
||||
} {
|
||||
if _, err := policy.ValidateSyntax(raw); err == nil {
|
||||
t.Errorf("危险地址 %q 应被拒绝", raw)
|
||||
}
|
||||
}
|
||||
got, err := policy.ValidateSyntax("https://api.example.com/v1/")
|
||||
if err != nil || got != "https://api.example.com/v1" {
|
||||
t.Fatalf("公网 HTTPS 地址应通过并去掉尾斜杠: %q %v", got, err)
|
||||
for raw, want := range map[string]string{
|
||||
"http://api.example.com/v1/": "http://api.example.com/v1",
|
||||
"HTTPS://api.example.com/v1/": "https://api.example.com/v1",
|
||||
} {
|
||||
got, err := policy.ValidateSyntax(raw)
|
||||
if err != nil || got != want {
|
||||
t.Errorf("公网 HTTP/HTTPS 地址应通过并规范化: raw=%q got=%q err=%v", raw, got, err)
|
||||
}
|
||||
}
|
||||
allowed := NewAIEndpointPolicy([]string{"10.0.0.8"})
|
||||
if _, err := allowed.ValidateSyntax("https://10.0.0.8/v1"); err != nil {
|
||||
t.Fatalf("部署允许的私有端点应通过: %v", err)
|
||||
for _, raw := range []string{"http://10.0.0.8/v1", "https://10.0.0.8/v1"} {
|
||||
if _, err := allowed.ValidateSyntax(raw); err != nil {
|
||||
t.Errorf("现有部署允许列表中的私有 HTTP/HTTPS 端点应通过: %q %v", raw, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSafeAIHTTPClient_可调用允许列表中的HTTP端点(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) {
|
||||
if request.URL.Path != "/v1/chat/completions" {
|
||||
t.Errorf("请求路径 = %q", request.URL.Path)
|
||||
}
|
||||
if request.Header.Get("Authorization") != "Bearer fake-http-key" {
|
||||
t.Errorf("Authorization 未按既有方式发送")
|
||||
}
|
||||
response.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
policy := NewAIEndpointPolicy([]string{"127.0.0.1"})
|
||||
if err := policy.ValidateResolved(context.Background(), server.URL+"/v1"); err != nil {
|
||||
t.Fatalf("测试级允许列表中的 HTTP 地址应通过完整校验: %v", err)
|
||||
}
|
||||
client := NewSafeAIHTTPClient(policy, time.Second)
|
||||
if err := callAIHealthCheck(context.Background(), client, model.AIProviderConfig{
|
||||
BaseURL: server.URL + "/v1", Model: "test-model",
|
||||
}, "fake-http-key"); err != nil {
|
||||
t.Fatalf("HTTP OpenAI 兼容端点调用失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user