98 lines
3.5 KiB
Go
98 lines
3.5 KiB
Go
package upstreamimpl
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"time"
|
|
|
|
"fmt"
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
"strings"
|
|
"git.5iot.com/iotlcm-public/iotlcm-upstream-proxy/internal/model"
|
|
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
"github.com/gogf/gf/v2/net/gclient"
|
|
)
|
|
|
|
const (
|
|
// gatewayBatchTimeoutConfigKey 限制单次批量转发的最长等待,防止上游异常时无限占用连接。
|
|
gatewayBatchTimeoutConfigKey = "gateway.batchTimeout"
|
|
// defaultGatewayBatchTimeout 覆盖 100 张卡按单卡 10 秒预算的最坏耗时,并留有余量。
|
|
defaultGatewayBatchTimeout = 20 * time.Minute
|
|
// defaultDataServiceTimeout 是数据开停转发的独立预算。
|
|
defaultDataServiceTimeout = 210 * time.Second
|
|
)
|
|
|
|
// configureGatewayTimeout 让响应头等待与整次请求使用同一预算,避免默认30秒提前中断。
|
|
// upstream-chan 对批量操作按卡串行调用运营商接口,耗时随卡数线性增长;
|
|
// 因此批量请求不能沿用 gclient 默认的 30 秒响应头超时,否则请求仍在办理就会被判为失败。
|
|
func configureGatewayTimeout(ctx context.Context, client *gclient.Client, path string, count int, perCard time.Duration) {
|
|
if path == "/sim/change/sim-apn" || path == "/sim/query/sim-apn-capability" {
|
|
client.SetContentType("application/json")
|
|
}
|
|
applyGatewayTimeout(client, path, count, perCard,
|
|
configDuration(ctx, "gateway.dataServiceTimeout", defaultDataServiceTimeout),
|
|
configDuration(ctx, gatewayBatchTimeoutConfigKey, defaultGatewayBatchTimeout))
|
|
}
|
|
|
|
// applyGatewayTimeout 计算并应用单次转发预算:整次请求与响应头等待必须一致,
|
|
// 否则响应头超时会在业务仍在办理时提前中断请求。
|
|
func applyGatewayTimeout(client *gclient.Client, path string, count int, perCard, dataServiceTimeout, batchTimeout time.Duration) {
|
|
if count < 1 {
|
|
count = 1
|
|
}
|
|
if perCard <= 0 {
|
|
perCard = 10 * time.Second
|
|
}
|
|
timeout := perCard * time.Duration(count)
|
|
if path == "/sim/change/sim-apn" {
|
|
timeout = dataServiceTimeout
|
|
if timeout <= 0 {
|
|
timeout = defaultDataServiceTimeout
|
|
}
|
|
} else if batchTimeout > 0 && timeout > batchTimeout {
|
|
timeout = batchTimeout
|
|
}
|
|
client.SetTimeout(timeout)
|
|
if transport, ok := client.Transport.(*http.Transport); ok {
|
|
transport.ResponseHeaderTimeout = timeout
|
|
}
|
|
}
|
|
|
|
// configDuration 读取时长配置,兼容写成 "20m" 字符串或秒数整数两种形式。
|
|
func configDuration(ctx context.Context, key string, fallback time.Duration) time.Duration {
|
|
value := g.Cfg().MustGet(ctx, key)
|
|
if value == nil || value.IsNil() {
|
|
return fallback
|
|
}
|
|
if duration := value.Duration(); duration >= time.Second {
|
|
return duration
|
|
}
|
|
if seconds := value.Int(); seconds > 0 {
|
|
return time.Duration(seconds) * time.Second
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func readGatewayResponse(response *gclient.Response) (model.SimBaseResJson, error) {
|
|
defer response.Close()
|
|
body := response.ReadAll()
|
|
var output struct {
|
|
Code *int `json:"code"`
|
|
Message string `json:"message"`
|
|
Data model.SimBaseResJson `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(body, &output); err != nil {
|
|
return nil, gerror.Wrap(err, "解析网关响应失败")
|
|
}
|
|
if response.StatusCode < 200 || response.StatusCode >= 300 || output.Code == nil || *output.Code != 0 {
|
|
message := strings.TrimSpace(output.Message)
|
|
if message == "" {
|
|
message = fmt.Sprintf("网关响应无效,HTTP状态码:%d", response.StatusCode)
|
|
}
|
|
return nil, gerror.New(message)
|
|
}
|
|
return output.Data, nil
|
|
}
|