feat: add http
parent
7840b82d75
commit
f54969697b
|
@ -1,234 +0,0 @@
|
||||||
package components
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"google.golang.org/grpc/metadata"
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ctxKey int
|
|
||||||
|
|
||||||
const (
|
|
||||||
timeout = time.Second * 30
|
|
||||||
// BKAPIRequestIDHeader 蓝鲸网关的请求ID
|
|
||||||
BKAPIRequestIDHeader = "X-Bkapi-Request-Id"
|
|
||||||
userAgent = "prime/v1.0"
|
|
||||||
requestIDCtxKey = ctxKey(1)
|
|
||||||
// RequestIDHeaderKey
|
|
||||||
RequestIDHeaderKey = "X-Request-Id"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
maskKeys = map[string]struct{}{
|
|
||||||
"bk_app_secret": {},
|
|
||||||
}
|
|
||||||
clientOnce sync.Once
|
|
||||||
globalClient *resty.Client
|
|
||||||
)
|
|
||||||
|
|
||||||
// WithLabelMatchValue 设置 RequestId 值
|
|
||||||
func WithRequestIDValue(ctx context.Context, id string) context.Context {
|
|
||||||
newCtx := context.WithValue(ctx, requestIDCtxKey, id)
|
|
||||||
return metadata.AppendToOutgoingContext(newCtx, RequestIDHeaderKey, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestIDValue 获取 RequestId 值
|
|
||||||
func RequestIDValue(ctx context.Context) string {
|
|
||||||
v, ok := ctx.Value(requestIDCtxKey).(string)
|
|
||||||
if !ok || v == "" {
|
|
||||||
return grpcRequestIDValue(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// grpcRequestIDValue grpc 需要单独处理
|
|
||||||
func grpcRequestIDValue(ctx context.Context) string {
|
|
||||||
md, ok := metadata.FromIncomingContext(ctx)
|
|
||||||
if !ok {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
values := md.Get(RequestIDHeaderKey)
|
|
||||||
if len(values) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return values[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRequestIDHeaderValue 设置 RequestId 值到头部
|
|
||||||
func SetRequestIDHeaderValue(req *http.Request, id string) {
|
|
||||||
req.Header.Set(RequestIDHeaderKey, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// restyReqToCurl curl 格式的请求日志
|
|
||||||
func restyReqToCurl(r *resty.Request) string {
|
|
||||||
headers := ""
|
|
||||||
for key, values := range r.Header {
|
|
||||||
for _, value := range values {
|
|
||||||
headers += fmt.Sprintf(" -H %q", fmt.Sprintf("%s: %s", key, value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 过滤掉敏感信息
|
|
||||||
rawURL := *r.RawRequest.URL
|
|
||||||
queryValue := rawURL.Query()
|
|
||||||
for key := range queryValue {
|
|
||||||
if _, ok := maskKeys[key]; ok {
|
|
||||||
queryValue.Set(key, "<masked>")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rawURL.RawQuery = queryValue.Encode()
|
|
||||||
|
|
||||||
reqMsg := fmt.Sprintf("curl -X %s '%s'%s", r.Method, rawURL.String(), headers)
|
|
||||||
if r.Body != nil {
|
|
||||||
switch body := r.Body.(type) {
|
|
||||||
case []byte:
|
|
||||||
reqMsg += fmt.Sprintf(" -d %q", body)
|
|
||||||
case string:
|
|
||||||
reqMsg += fmt.Sprintf(" -d %q", body)
|
|
||||||
case io.Reader:
|
|
||||||
reqMsg += " -d (io.Reader)"
|
|
||||||
default:
|
|
||||||
prtBodyBytes, err := json.Marshal(body)
|
|
||||||
if err != nil {
|
|
||||||
reqMsg += fmt.Sprintf(" -d %q (MarshalErr %s)", body, err)
|
|
||||||
} else {
|
|
||||||
reqMsg += fmt.Sprintf(" -d '%s'", prtBodyBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r.FormData.Encode() != "" {
|
|
||||||
encodeStr := r.FormData.Encode()
|
|
||||||
reqMsg += fmt.Sprintf(" -d %q", encodeStr)
|
|
||||||
rawStr, _ := url.QueryUnescape(encodeStr)
|
|
||||||
reqMsg += fmt.Sprintf(" -raw `%s`", rawStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return reqMsg
|
|
||||||
}
|
|
||||||
|
|
||||||
// restyResponseToCurl 返回日志
|
|
||||||
func restyResponseToCurl(resp *resty.Response) string {
|
|
||||||
// 最大打印 1024 个字符
|
|
||||||
body := string(resp.Body())
|
|
||||||
if len(body) > 1024 {
|
|
||||||
body = fmt.Sprintf("%s...(Total %s)", body[:1024], humanize.Bytes(uint64(len(body))))
|
|
||||||
}
|
|
||||||
|
|
||||||
respMsg := fmt.Sprintf("[%s] %s %s", resp.Status(), resp.Time(), body)
|
|
||||||
|
|
||||||
// 请求蓝鲸网关记录RequestID
|
|
||||||
bkAPIRequestID := resp.RawResponse.Header.Get(BKAPIRequestIDHeader)
|
|
||||||
if bkAPIRequestID != "" {
|
|
||||||
respMsg = fmt.Sprintf("[%s] %s bkapi_request_id=%s %s", resp.Status(), resp.Time(), bkAPIRequestID, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
return respMsg
|
|
||||||
}
|
|
||||||
|
|
||||||
func restyErrHook(r *resty.Request, err error) {
|
|
||||||
klog.Infof("[%s] REQ: %s", RequestIDValue(r.RawRequest.Context()), restyReqToCurl(r))
|
|
||||||
klog.Infof("[%s] RESP: [err] %s", RequestIDValue(r.RawRequest.Context()), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func restyAfterResponseHook(c *resty.Client, r *resty.Response) error {
|
|
||||||
klog.Infof("[%s] REQ: %s", RequestIDValue(r.Request.Context()), restyReqToCurl(r.Request))
|
|
||||||
klog.Infof("[%s] RESP: %s", RequestIDValue(r.Request.Context()), restyResponseToCurl(r))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func restyBeforeRequestHook(c *resty.Client, r *http.Request) error {
|
|
||||||
SetRequestIDHeaderValue(r, RequestIDValue(r.Context()))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetClient : 新建Client, 设置公共参数,每次新建,cookies不复用
|
|
||||||
func GetClient() *resty.Client {
|
|
||||||
if globalClient == nil {
|
|
||||||
clientOnce.Do(func() {
|
|
||||||
globalClient = resty.New().
|
|
||||||
SetTimeout(timeout).
|
|
||||||
SetDebug(false). // 更多详情, 可以开启为 true
|
|
||||||
SetCookieJar(nil). // 后台API去掉 cookie 记录
|
|
||||||
SetDebugBodyLimit(1024).
|
|
||||||
OnAfterResponse(restyAfterResponseHook).
|
|
||||||
SetPreRequestHook(restyBeforeRequestHook).
|
|
||||||
OnError(restyErrHook).
|
|
||||||
SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
|
|
||||||
SetHeader("User-Agent", userAgent)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return globalClient
|
|
||||||
}
|
|
||||||
|
|
||||||
// BKResult 蓝鲸返回规范的结构体
|
|
||||||
type BKResult struct {
|
|
||||||
Code interface{} `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Data interface{} `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalBKResult 反序列化为蓝鲸返回规范
|
|
||||||
func UnmarshalBKResult(resp *resty.Response, data interface{}) error {
|
|
||||||
if resp.StatusCode() != http.StatusOK {
|
|
||||||
return errors.Errorf("http code %d != 200", resp.StatusCode())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 部分接口,如 usermanager 返回的content-type不是json, 需要手动Unmarshal
|
|
||||||
bkResult := &BKResult{Data: data}
|
|
||||||
if err := json.Unmarshal(resp.Body(), bkResult); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := bkResult.ValidateCode(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateCode 返回结果是否OK
|
|
||||||
func (r *BKResult) ValidateCode() error {
|
|
||||||
code, err := refineCode(r.Code)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if code != 0 {
|
|
||||||
return errors.Errorf("resp code %d != 0, %s", code, r.Message)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// refineCode 多种返回Code统一处理
|
|
||||||
// 支持 "00", 0, "0"
|
|
||||||
func refineCode(code interface{}) (int, error) {
|
|
||||||
var resultCode int
|
|
||||||
switch code := code.(type) {
|
|
||||||
case int:
|
|
||||||
resultCode = code
|
|
||||||
case float64:
|
|
||||||
resultCode = int(code)
|
|
||||||
case string:
|
|
||||||
c, err := strconv.Atoi(code)
|
|
||||||
if err != nil {
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
resultCode = c
|
|
||||||
default:
|
|
||||||
return -1, errors.Errorf("conversion to int from %T not supported", code)
|
|
||||||
}
|
|
||||||
return resultCode, nil
|
|
||||||
}
|
|
4
go.mod
4
go.mod
|
@ -14,7 +14,6 @@ require (
|
||||||
github.com/go-resty/resty/v2 v2.7.0
|
github.com/go-resty/resty/v2 v2.7.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/pkg/errors v0.9.1
|
|
||||||
github.com/prometheus/client_golang v1.14.0
|
github.com/prometheus/client_golang v1.14.0
|
||||||
github.com/redis/go-redis/v9 v9.0.3
|
github.com/redis/go-redis/v9 v9.0.3
|
||||||
github.com/samber/lo v1.47.0
|
github.com/samber/lo v1.47.0
|
||||||
|
@ -24,8 +23,6 @@ require (
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0
|
||||||
go.opentelemetry.io/otel/sdk v1.27.0
|
go.opentelemetry.io/otel/sdk v1.27.0
|
||||||
google.golang.org/grpc v1.64.0
|
|
||||||
k8s.io/klog/v2 v2.90.1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
@ -63,6 +60,7 @@ require (
|
||||||
golang.org/x/sys v0.20.0 // indirect
|
golang.org/x/sys v0.20.0 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/text v0.16.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd // indirect
|
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd // indirect
|
||||||
|
google.golang.org/grpc v1.64.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.1 // indirect
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
5
go.sum
5
go.sum
|
@ -34,7 +34,6 @@ github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
|
||||||
github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
|
github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
|
||||||
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
@ -86,8 +85,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zk
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
|
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
|
||||||
|
@ -162,5 +159,3 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw=
|
|
||||||
k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package rest
|
package rest
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -14,12 +14,12 @@ var (
|
||||||
|
|
||||||
// APIResponse 返回的标准结构
|
// APIResponse 返回的标准结构
|
||||||
type APIResponse struct {
|
type APIResponse struct {
|
||||||
Err error `json:"-"` // low-level runtime error
|
Err error `json:"-"` // low-level runtime error
|
||||||
HTTPStatusCode int `json:"-"` // http response status code
|
HTTPStatusCode int `json:"-"` // http response status code
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
RequestId string `json:"request_id"`
|
RequestId string `json:"request_id"`
|
||||||
Data interface{} `json:"data"`
|
Data any `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render chi Render 实现
|
// Render chi Render 实现
|
||||||
|
|
Loading…
Reference in New Issue