github.com/qiniu/go-sdk/v7@v7.27.0/sandbox/client.go (about) 1 package sandbox 2 3 import ( 4 "cmp" 5 "context" 6 "fmt" 7 "net/http" 8 "os" 9 "strconv" 10 "time" 11 12 "github.com/qiniu/go-sdk/v7/auth" 13 "github.com/qiniu/go-sdk/v7/sandbox/internal/apis" 14 ) 15 16 // DefaultEndpoint 是沙箱 API 的默认服务地址。 17 const DefaultEndpoint = "https://cn-yangzhou-1-sandbox.qiniuapi.com" 18 19 // Config 是沙箱客户端的配置。 20 type Config struct { 21 // APIKey 是用于身份认证的 API 密钥(必填)。 22 APIKey string 23 24 // Credentials 是用于身份认证的七牛凭证对象(可选)。 25 // InjectionRule 相关接口会使用 Credentials 进行认证。 26 Credentials *auth.Credentials 27 28 // Endpoint 是沙箱 API 服务地址(可选,默认值:DefaultEndpoint)。 29 Endpoint string 30 31 // HTTPClient 自定义 HTTP 客户端(可选,默认值:http.DefaultClient)。 32 HTTPClient *http.Client 33 34 // RetryMax 可重试 API 调用的最大重试次数(可选)。 35 // 设置为 0 禁用重试;nil 时读取 SANDBOX_RETRY_MAX,未设置时默认 5。 36 RetryMax *int 37 38 // RetryBackoff 重试退避函数,输入第几次重试(0-based),返回等待时长(可选)。 39 // 默认:指数退避 500ms → 10s + 随机抖动。 40 // 返回 0 表示不等待直接重试。 41 RetryBackoff func(attempt int) time.Duration 42 } 43 44 // Client 是沙箱客户端。 45 type Client struct { 46 config *Config 47 api apis.ClientWithResponsesInterface 48 } 49 50 // NewClient 创建一个新的沙箱客户端。 51 func NewClient(config *Config) (*Client, error) { 52 if err := normalizeRetryMax(config); err != nil { 53 return nil, err 54 } 55 endpoint := config.Endpoint 56 if endpoint == "" { 57 endpoint = DefaultEndpoint 58 } 59 if config.HTTPClient == nil { 60 config.HTTPClient = http.DefaultClient 61 } 62 63 opts := []apis.ClientOption{ 64 apis.WithHTTPClient(config.HTTPClient), 65 apis.WithRequestEditorFn(reqidEditor()), 66 } 67 if config.APIKey != "" { 68 opts = append(opts, apis.WithRequestEditorFn(apiKeyEditor(config.APIKey))) 69 } 70 71 client, err := apis.NewClientWithResponses(endpoint, opts...) 72 if err != nil { 73 return nil, err 74 } 75 76 return &Client{config: config, api: client}, nil 77 } 78 79 // normalizeRetryMax 校验重试次数,并在未显式配置时读取 SANDBOX_RETRY_MAX。 80 func normalizeRetryMax(config *Config) error { 81 if config.RetryMax != nil { 82 if *config.RetryMax < 0 { 83 return fmt.Errorf("RetryMax must be a non-negative integer") 84 } 85 return nil 86 } 87 envVal := os.Getenv("SANDBOX_RETRY_MAX") 88 if envVal == "" { 89 return nil 90 } 91 maxRetries, err := strconv.Atoi(envVal) 92 if err != nil || maxRetries < 0 { 93 return fmt.Errorf("SANDBOX_RETRY_MAX must be a non-negative integer") 94 } 95 config.RetryMax = &maxRetries 96 return nil 97 } 98 99 // reqidEditor 返回一个 RequestEditorFn,从 context 中提取 reqid 并注入 X-Reqid 请求头。 100 // 与 SDK 其他子产品(如 storage、media 等)的行为保持一致,方便统一链路追踪。 101 func reqidEditor() apis.RequestEditorFn { 102 return func(ctx context.Context, req *http.Request) error { 103 setReqidHeader(ctx, req) 104 return nil 105 } 106 } 107 108 // apiKeyEditor 返回一个 RequestEditorFn,用于注入 API Key 认证头。 109 // 同时设置 X-API-Key 和 Authorization: Bearer,兼容不同端点对认证头的要求: 110 // - 多数端点接受 X-API-Key 111 // - 部分端点(如 POST /templates/{templateID} rebuild)要求 Authorization 112 // 113 // 与 E2B js-sdk (packages/js-sdk/src/api/index.ts) 的双 header 行为保持一致。 114 // 115 // 与 Credentials 共存:apiKeyEditor 作为 client-level editor 先于 per-request 116 // editor 执行;GetCredentialsOption 返回的 editor 会 Set 覆盖 Authorization 117 // 为 "Qiniu <sig>",因此 Credentials 的优先级依旧生效,Bearer 只在未叠加 118 // Credentials editor 时才会命中。此时 X-API-Key 仍会保留在请求中,服务端 119 // 接受并忽略,与改动前 APIKey-only 路径行为一致。 120 func apiKeyEditor(apiKey string) apis.RequestEditorFn { 121 return func(ctx context.Context, req *http.Request) error { 122 if req.Header.Get("Authorization") != "" { 123 // 调用方已在进入 client-level editor 链之前手动预设 Authorization 124 // (非常规路径),尊重其选择并跳过注入,避免覆盖调用方的认证选择。 125 return nil 126 } 127 req.Header.Set("X-API-Key", apiKey) 128 req.Header.Set("Authorization", "Bearer "+apiKey) 129 return nil 130 } 131 } 132 133 func (c *Client) GetCredentialsOption() (apis.RequestEditorFn, error) { 134 cred := cmp.Or(c.config.Credentials, auth.Default()) 135 if cred == nil { 136 return nil, fmt.Errorf("credentials not provided in client config") 137 } 138 return func(ctx context.Context, req *http.Request) error { 139 token, err := cred.SignRequestV2(req) 140 if err != nil { 141 return err 142 } 143 req.Header.Set("Authorization", fmt.Sprintf("Qiniu %s", token)) 144 return nil 145 }, nil 146 }