mirror of
https://github.com/YFGaia/dify-plus.git
synced 2026-06-04 10:14:00 +08:00
fix(bedrock): 代理改为反向代理模式,兼容不支持 CONNECT 的代理
- 取消 http.Transport.Proxy(要求代理支持 HTTP CONNECT 隧道) - 改为直接向代理地址发 HTTP 请求(host:port),路径同 Bedrock 原生 API - httpReq.Host 设为真实 Bedrock host,SigV4 仍针对真实 host 签名后复制头部 - 支持凭证内 bedrock_proxy_url 与全局 BEDROCK_PROXY 环境变量(优先凭证) - config.Gaia 增加 BedrockProxy 字段,overrideGaiaFromEnv 从 BEDROCK_PROXY 读取
This commit is contained in:
@@ -6,5 +6,5 @@ type Gaia struct {
|
||||
SuperAdminAccountId string `mapstructure:"SUPER_ADMIN_ACCOUNT_ID" json:"SUPER_ADMIN_ACCOUNT_ID" yaml:"SUPER_ADMIN_ACCOUNT_ID"` // 超级管理员账号
|
||||
SuperAdminTenantId string `mapstructure:"SUPER_ADMIN_TENANT_ID" json:"SUPER_ADMIN_TENANT_ID" yaml:"SUPER_ADMIN_TENANT_ID"` // 系统默认工作区
|
||||
StoragePath string `mapstructure:"storage-path" json:"storage-path" yaml:"storage-path"` // Dify storage 目录路径,用于读取私钥
|
||||
BedrockProxy string `mapstructure:"bedrock_proxy" json:"bedrock_proxy" yaml:"bedrock_proxy"` // 全局 Bedrock HTTP 代理(host:port 或 http://host:port),凭证未配置 bedrock_proxy_url 时使用
|
||||
BedrockProxy string `mapstructure:"bedrock_proxy" json:"bedrock_proxy" yaml:"bedrock_proxy"` // 全局 Bedrock 代理地址(host:port 或 http://host:port),凭证未配置 bedrock_proxy_url 时使用
|
||||
}
|
||||
|
||||
@@ -121,16 +121,16 @@ func overrideRedisFromEnv() {
|
||||
|
||||
// overrideAllFromEnv 从环境变量覆盖所有配置
|
||||
func overrideAllFromEnv() {
|
||||
overrideDBFromEnv()
|
||||
overrideRedisFromEnv()
|
||||
overrideGaiaFromEnv()
|
||||
overrideDBFromEnv()
|
||||
overrideRedisFromEnv()
|
||||
overrideGaiaFromEnv()
|
||||
}
|
||||
|
||||
// overrideGaiaFromEnv 从环境变量覆盖 Gaia 配置
|
||||
func overrideGaiaFromEnv() {
|
||||
// BEDROCK_PROXY: 全局 Bedrock HTTP 代理,与 Dify Python 侧 BEDROCK_PROXY 含义一致
|
||||
if proxy := os.Getenv("BEDROCK_PROXY"); proxy != "" {
|
||||
global.GVA_CONFIG.Gaia.BedrockProxy = proxy
|
||||
fmt.Printf("Bedrock proxy overridden from BEDROCK_PROXY environment variable: %s\n", proxy)
|
||||
}
|
||||
// BEDROCK_PROXY: 全局 Bedrock 反向代理地址,与 Dify Python 侧 BEDROCK_PROXY 含义一致
|
||||
if proxy := os.Getenv("BEDROCK_PROXY"); proxy != "" {
|
||||
global.GVA_CONFIG.Gaia.BedrockProxy = proxy
|
||||
fmt.Printf("Bedrock proxy overridden from BEDROCK_PROXY environment variable: %s\n", proxy)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -74,18 +73,40 @@ func (s *ModelProviderService) proxyBedrockRequest(
|
||||
return fmt.Errorf("重写 Bedrock 请求 body 失败:%w", err)
|
||||
}
|
||||
|
||||
// 3) 构建 Bedrock URL
|
||||
host := fmt.Sprintf("bedrock-runtime.%s.amazonaws.com", region)
|
||||
// 3) 确定代理地址(优先凭证内 bedrock_proxy_url,其次全局 BEDROCK_PROXY env)
|
||||
proxyAddr := creds.BedrockProxyURL
|
||||
if proxyAddr == "" {
|
||||
proxyAddr = global.GVA_CONFIG.Gaia.BedrockProxy
|
||||
}
|
||||
// 规范化:去除 scheme,仅保留 host:port
|
||||
proxyHost := strings.TrimPrefix(strings.TrimPrefix(proxyAddr, "https://"), "http://")
|
||||
proxyHost = strings.TrimRight(proxyHost, "/")
|
||||
|
||||
// 4) 构建请求 URL
|
||||
// 真实 Bedrock host(SigV4 & Host 头使用)
|
||||
bedrockHost := fmt.Sprintf("bedrock-runtime.%s.amazonaws.com", region)
|
||||
op := "invoke"
|
||||
if streaming {
|
||||
op = "invoke-with-response-stream"
|
||||
}
|
||||
requestURL := fmt.Sprintf("https://%s/model/%s/%s", host, modelID, op)
|
||||
var requestURL string
|
||||
if proxyHost != "" {
|
||||
// 反向代理模式:发普通 HTTP 到代理地址,不需要代理支持 CONNECT 隧道
|
||||
requestURL = fmt.Sprintf("http://%s/model/%s/%s", proxyHost, modelID, op)
|
||||
} else {
|
||||
requestURL = fmt.Sprintf("https://%s/model/%s/%s", bedrockHost, modelID, op)
|
||||
}
|
||||
|
||||
httpReq, err := http.NewRequest(method, requestURL, bytes.NewReader(rewritten))
|
||||
if err != nil {
|
||||
return fmt.Errorf("构建 Bedrock 请求失败:%w", err)
|
||||
}
|
||||
// 代理模式下显式设置 Host 为真实 Bedrock 地址,使 SigV4 校验通过
|
||||
if proxyHost != "" {
|
||||
httpReq.Host = bedrockHost
|
||||
global.GVA_LOG.Info("Bedrock 经反向代理转发",
|
||||
zap.String("proxy", proxyHost), zap.String("bedrockHost", bedrockHost), zap.String("model", modelID))
|
||||
}
|
||||
httpReq.Header.Set("Content-Type", "application/json")
|
||||
if streaming {
|
||||
httpReq.Header.Set("Accept", "application/vnd.amazon.eventstream")
|
||||
@@ -94,30 +115,23 @@ func (s *ModelProviderService) proxyBedrockRequest(
|
||||
httpReq.Header.Set("Accept", "application/json")
|
||||
}
|
||||
|
||||
// 4) SigV4 签名(service=bedrock)
|
||||
// 5) SigV4 签名(始终针对真实 Bedrock HTTPS URL 签名,再把签名头复制到实际请求)
|
||||
awsCreds := credentials.NewStaticCredentials(creds.AWSAccessKeyID, creds.AWSSecretAccessKey, creds.AWSSessionToken)
|
||||
signer := v4.NewSigner(awsCreds)
|
||||
if _, err = signer.Sign(httpReq, bytes.NewReader(rewritten), "bedrock", region, time.Now()); err != nil {
|
||||
signURL := fmt.Sprintf("https://%s/model/%s/%s", bedrockHost, modelID, op)
|
||||
signReq, _ := http.NewRequest(method, signURL, bytes.NewReader(rewritten))
|
||||
signReq.Header.Set("Content-Type", "application/json")
|
||||
if _, err = signer.Sign(signReq, bytes.NewReader(rewritten), "bedrock", region, time.Now()); err != nil {
|
||||
return fmt.Errorf("Bedrock SigV4 签名失败:%w", err)
|
||||
}
|
||||
// 把签名产生的 Authorization / X-Amz-* 头复制到实际发送的请求
|
||||
for k, vals := range signReq.Header {
|
||||
httpReq.Header[k] = vals
|
||||
}
|
||||
|
||||
// 5) 发起请求(若配置了代理则经 HTTP 代理转发:优先用凭证内 bedrock_proxy_url,其次用全局 BEDROCK_PROXY)
|
||||
// 6) 发起请求
|
||||
startTime := time.Now()
|
||||
proxyAddr := creds.BedrockProxyURL
|
||||
if proxyAddr == "" {
|
||||
proxyAddr = global.GVA_CONFIG.Gaia.BedrockProxy
|
||||
}
|
||||
transport := http.DefaultTransport
|
||||
if proxyAddr != "" {
|
||||
if !strings.HasPrefix(proxyAddr, "http://") && !strings.HasPrefix(proxyAddr, "https://") {
|
||||
proxyAddr = "http://" + proxyAddr
|
||||
}
|
||||
if proxyURL, parseErr := url.Parse(proxyAddr); parseErr == nil {
|
||||
transport = &http.Transport{Proxy: http.ProxyURL(proxyURL)}
|
||||
global.GVA_LOG.Info("Bedrock 请求经代理转发", zap.String("proxy", proxyAddr), zap.String("model", modelID))
|
||||
}
|
||||
}
|
||||
client := &http.Client{Timeout: 5 * time.Minute, Transport: transport}
|
||||
client := &http.Client{Timeout: 5 * time.Minute}
|
||||
resp, err := client.Do(httpReq)
|
||||
if err != nil {
|
||||
s.logBedrock(userID, modelID, "error", err.Error(), startTime, 0, 0)
|
||||
@@ -125,7 +139,7 @@ func (s *ModelProviderService) proxyBedrockRequest(
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
// 6) 写回响应头/状态码(流式改写 Content-Type 为 SSE)
|
||||
// 7) 写回响应头/状态码(流式改写 Content-Type 为 SSE)
|
||||
if w, ok := writer.(http.ResponseWriter); ok {
|
||||
for k, v := range resp.Header {
|
||||
lower := strings.ToLower(k)
|
||||
@@ -152,7 +166,7 @@ func (s *ModelProviderService) proxyBedrockRequest(
|
||||
return nil
|
||||
}
|
||||
|
||||
// 7) 处理响应体
|
||||
// 8) 处理响应体
|
||||
var inputTokens, outputTokens int
|
||||
if streaming {
|
||||
inputTokens, outputTokens, err = s.streamBedrockEventStream(resp.Body, writer)
|
||||
@@ -170,7 +184,7 @@ func (s *ModelProviderService) proxyBedrockRequest(
|
||||
inputTokens, outputTokens = parseAnthropicUsage(buf.Bytes())
|
||||
}
|
||||
|
||||
// 8) 记录日志 + 计费扣款
|
||||
// 9) 记录日志 + 计费扣款
|
||||
s.logBedrock(userID, modelID, "success", "", startTime, inputTokens, outputTokens)
|
||||
if inputTokens > 0 || outputTokens > 0 {
|
||||
pricing, _ := s.fetchModelPricingFromDify(modelID)
|
||||
@@ -288,5 +302,3 @@ func (s *ModelProviderService) logBedrock(userID, modelID, status, errMsg string
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user