2 Star 0 Fork 0

TeamsHub/backend-gopkg

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
client.go 15.81 KB
一键复制 编辑 原始数据 按行查看 历史
HCY 提交于 2024-07-24 10:12 . 反向代理日志类型
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
package httpclient
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"gitee.com/wuzheng0709/backend-gopkg/infrastructure/pkg/gin/log"
"io"
"net"
"net/http"
httpURL "net/url"
"strings"
"time"
"github.com/gin-gonic/gin"
"gitee.com/wuzheng0709/backend-gopkg/infrastructure/pkg/errors"
"gitee.com/wuzheng0709/backend-gopkg/infrastructure/pkg/trace"
"net/url"
)
const (
// DefaultTTL 一次http请求最长执行1分钟
DefaultTTL = time.Minute
)
// Get get 请求
func Get(url string, form httpURL.Values, options ...Option) (body []byte, err error) {
return withoutBody(http.MethodGet, url, form, options...)
}
// Delete delete 请求
func Delete(url string, form httpURL.Values, options ...Option) (body []byte, err error) {
return withoutBody(http.MethodDelete, url, form, options...)
}
func withoutBody(method, url string, form httpURL.Values, options ...Option) (body []byte, err error) {
if url == "" {
return nil, errors.New("url required")
}
if len(form) > 0 {
if url, err = addFormValuesIntoURL(url, form); err != nil {
return
}
}
ts := time.Now()
opt := getOption()
defer func() {
if opt.trace != nil {
opt.dialog.Success = err == nil
opt.dialog.CostSeconds = time.Since(ts).Seconds()
opt.trace.AppendDialog(opt.dialog)
}
releaseOption(opt)
}()
for _, f := range options {
f(opt)
}
opt.header["Content-Type"] = []string{"application/x-www-form-urlencoded; charset=utf-8"}
if opt.trace != nil {
opt.header[trace.Header] = []string{opt.trace.ID()}
}
ttl := opt.ttl
if ttl <= 0 {
ttl = DefaultTTL
}
ctx, cancel := context.WithTimeout(context.Background(), ttl)
defer cancel()
if opt.dialog != nil {
decodedURL, _ := httpURL.QueryUnescape(url)
opt.dialog.Request = &trace.Request{
TTL: ttl.String(),
Method: method,
DecodedURL: decodedURL,
Header: opt.header,
}
}
retryTimes := opt.retryTimes
if retryTimes <= 0 {
retryTimes = DefaultRetryTimes
}
retryDelay := opt.retryDelay
if retryDelay <= 0 {
retryDelay = DefaultRetryDelay
}
var httpCode int
defer func() {
if opt.alarmObject == nil {
return
}
if opt.alarmVerify != nil && !opt.alarmVerify(body) && err == nil {
return
}
info := &struct {
TraceID string `json:"trace_id"`
Request struct {
Method string `json:"method"`
URL string `json:"url"`
} `json:"request"`
Response struct {
HTTPCode int `json:"http_code"`
Body string `json:"body"`
} `json:"response"`
Error string `json:"error"`
}{}
if opt.trace != nil {
info.TraceID = opt.trace.ID()
}
info.Request.Method = method
info.Request.URL = url
info.Response.HTTPCode = httpCode
info.Response.Body = string(body)
info.Error = ""
if err != nil {
info.Error = fmt.Sprintf("%+v", err)
}
raw, _ := json.MarshalIndent(info, "", " ")
onFailedAlarm(opt.alarmTitle, raw, opt.logger, opt.alarmObject)
}()
for k := 0; k < retryTimes; k++ {
body, httpCode, err = doHTTP(ctx, method, url, nil, opt)
if shouldRetry(ctx, httpCode) || (opt.retryVerify != nil && opt.retryVerify(body)) {
time.Sleep(retryDelay)
continue
}
return
}
return
}
// PostForm post form 请求
func PostForm(url string, form httpURL.Values, options ...Option) (body []byte, err error) {
return withFormBody(http.MethodPost, url, form, options...)
}
// PostJSON post json 请求
func PostJSON(url string, raw json.RawMessage, options ...Option) (body []byte, err error) {
return withJSONBody(http.MethodPost, url, raw, options...)
}
// PutForm put form 请求
func PutForm(url string, form httpURL.Values, options ...Option) (body []byte, err error) {
return withFormBody(http.MethodPut, url, form, options...)
}
// PutJSON put json 请求
func PutJSON(url string, raw json.RawMessage, options ...Option) (body []byte, err error) {
return withJSONBody(http.MethodPut, url, raw, options...)
}
// PatchFrom patch form 请求
func PatchFrom(url string, form httpURL.Values, options ...Option) (body []byte, err error) {
return withFormBody(http.MethodPatch, url, form, options...)
}
// PatchJSON patch json 请求
func PatchJSON(url string, raw json.RawMessage, options ...Option) (body []byte, err error) {
return withJSONBody(http.MethodPatch, url, raw, options...)
}
func withFormBody(method, url string, form httpURL.Values, options ...Option) (body []byte, err error) {
if url == "" {
return nil, errors.New("url required")
}
if len(form) == 0 {
return nil, errors.New("form required")
}
ts := time.Now()
opt := getOption()
defer func() {
if opt.trace != nil {
opt.dialog.Success = err == nil
opt.dialog.CostSeconds = time.Since(ts).Seconds()
opt.trace.AppendDialog(opt.dialog)
}
releaseOption(opt)
}()
for _, f := range options {
f(opt)
}
opt.header["Content-Type"] = []string{"application/x-www-form-urlencoded; charset=utf-8"}
if opt.trace != nil {
opt.header[trace.Header] = []string{opt.trace.ID()}
}
ttl := opt.ttl
if ttl <= 0 {
ttl = DefaultTTL
}
ctx, cancel := context.WithTimeout(context.Background(), ttl)
defer cancel()
formValue := form.Encode()
if opt.dialog != nil {
decodedURL, _ := httpURL.QueryUnescape(url)
opt.dialog.Request = &trace.Request{
TTL: ttl.String(),
Method: method,
DecodedURL: decodedURL,
Header: opt.header,
Body: formValue,
}
}
retryTimes := opt.retryTimes
if retryTimes <= 0 {
retryTimes = DefaultRetryTimes
}
retryDelay := opt.retryDelay
if retryDelay <= 0 {
retryDelay = DefaultRetryDelay
}
var httpCode int
defer func() {
if opt.alarmObject == nil {
return
}
if opt.alarmVerify != nil && !opt.alarmVerify(body) && err == nil {
return
}
info := &struct {
TraceID string `json:"trace_id"`
Request struct {
Method string `json:"method"`
URL string `json:"url"`
} `json:"request"`
Response struct {
HTTPCode int `json:"http_code"`
Body string `json:"body"`
} `json:"response"`
Error string `json:"error"`
}{}
if opt.trace != nil {
info.TraceID = opt.trace.ID()
}
info.Request.Method = method
info.Request.URL = url
info.Response.HTTPCode = httpCode
info.Response.Body = string(body)
info.Error = ""
if err != nil {
info.Error = fmt.Sprintf("%+v", err)
}
raw, _ := json.MarshalIndent(info, "", " ")
onFailedAlarm(opt.alarmTitle, raw, opt.logger, opt.alarmObject)
}()
for k := 0; k < retryTimes; k++ {
body, httpCode, err = doHTTP(ctx, method, url, []byte(formValue), opt)
if shouldRetry(ctx, httpCode) || (opt.retryVerify != nil && opt.retryVerify(body)) {
time.Sleep(retryDelay)
continue
}
return
}
return
}
func withJSONBody(method, url string, raw json.RawMessage, options ...Option) (body []byte, err error) {
if url == "" {
return nil, errors.New("url required")
}
if len(raw) == 0 {
return nil, errors.New("raw required")
}
ts := time.Now()
opt := getOption()
defer func() {
if opt.trace != nil {
opt.dialog.Success = err == nil
opt.dialog.CostSeconds = time.Since(ts).Seconds()
opt.trace.AppendDialog(opt.dialog)
}
releaseOption(opt)
}()
for _, f := range options {
f(opt)
}
opt.header["Content-Type"] = []string{"application/json; charset=utf-8"}
if opt.trace != nil {
opt.header[trace.Header] = []string{opt.trace.ID()}
}
ttl := opt.ttl
if ttl <= 0 {
ttl = DefaultTTL
}
ctx, cancel := context.WithTimeout(context.Background(), ttl)
defer cancel()
if opt.dialog != nil {
decodedURL, _ := httpURL.QueryUnescape(url)
opt.dialog.Request = &trace.Request{
TTL: ttl.String(),
Method: method,
DecodedURL: decodedURL,
Header: opt.header,
Body: string(raw), // TODO unsafe
}
}
retryTimes := opt.retryTimes
if retryTimes <= 0 {
retryTimes = DefaultRetryTimes
}
retryDelay := opt.retryDelay
if retryDelay <= 0 {
retryDelay = DefaultRetryDelay
}
var httpCode int
defer func() {
if opt.alarmObject == nil {
return
}
if opt.alarmVerify != nil && !opt.alarmVerify(body) && err == nil {
return
}
info := &struct {
TraceID string `json:"trace_id"`
Request struct {
Method string `json:"method"`
URL string `json:"url"`
} `json:"request"`
Response struct {
HTTPCode int `json:"http_code"`
Body string `json:"body"`
} `json:"response"`
Error string `json:"error"`
}{}
if opt.trace != nil {
info.TraceID = opt.trace.ID()
}
info.Request.Method = method
info.Request.URL = url
info.Response.HTTPCode = httpCode
info.Response.Body = string(body)
info.Error = ""
if err != nil {
info.Error = fmt.Sprintf("%+v", err)
}
raw, _ := json.MarshalIndent(info, "", " ")
onFailedAlarm(opt.alarmTitle, raw, opt.logger, opt.alarmObject)
}()
for k := 0; k < retryTimes; k++ {
body, httpCode, err = doHTTP(ctx, method, url, raw, opt)
if shouldRetry(ctx, httpCode) || (opt.retryVerify != nil && opt.retryVerify(body)) {
time.Sleep(retryDelay)
continue
}
return
}
return
}
// PublicProxyLog [...]
type PublicProxyLog struct {
Status int `gorm:"column:status;type:int(3);not null;comment:'http状态码'" json:"status"` // http状态码
Method string `gorm:"column:method;type:varchar(32);not null;comment:'http方法'" json:"method"` // http方法
URL string `gorm:"column:url;type:varchar(255);not null;comment:'路径'" json:"url"` // 路径
Header http.Header `gorm:"column:header;type:varchar(255);not null;comment:'请求头'" json:"header"` // 请求头
Body string `gorm:"column:body;type:text;default:null;comment:'请求body信息'" json:"body"` // 请求body信息
ResInfo string `gorm:"column:res_info;type:text;not null;comment:'返回信息'" json:"res_info"` // 返回信息
ResErr string `gorm:"column:res_err;type:text;default:null" json:"res_err"`
Latency int `gorm:"column:latency;type:int(32);not null;comment:'延迟(单位:毫秒)'" json:"latency"` // 延迟(单位:毫秒)
LogType string `json:"log_type"` // 日志类型
}
func NewProxyLog(status, latency int, method, url string, header http.Header, resInfo, body, resErr string) PublicProxyLog {
return PublicProxyLog{
Status: status, // http状态码
Method: method, // http方法
URL: url, // 路径
Header: header, // 请求头
ResInfo: resInfo, // 返回信息
Latency: latency, // 延迟(单位:毫秒)
Body: body, // 请求body信息
ResErr: resErr,
LogType: "internal_proxy", // 日志类型
}
}
// 反向代理
func PublicRequire(url, method string, ReqByte []byte, header *http.Header, hc gin.HandlersChain, c *gin.Context) (resBody []byte, resCode int, err error) {
// 使用现有中间件
if len(hc) != 0 {
for _, f := range hc {
f(c)
}
}
client, err := GetHttpClient(url)
if err != nil {
return resBody, http.StatusInternalServerError, err
}
req, err := http.NewRequest(method, url, strings.NewReader(string(ReqByte)))
if err != nil {
resCode = http.StatusInternalServerError
log.Error("包装请求错误:", err)
return
}
req.Header = *header
res, err := client.Do(req)
if err != nil {
if res != nil {
resCode = res.StatusCode
log.Error("反向代理请求失败,err:", err)
return
}
return
}
// 将调用结果写入到数据库中
defer func(res *http.Response) {
// TODO: 开启线程池后启用
res.Body.Close()
}(res)
resBody, err = io.ReadAll(res.Body)
if err != nil {
resCode = http.StatusInternalServerError
log.Error("读取 反向代理的 body 失败,err:", err.Error())
return
}
resCode = res.StatusCode
return
}
// 生成代理日志
func RetProxyLog(header http.Header, resCode int, timeNow time.Time, method string, url string, resBody, ReqByte []byte, err error) {
var errLog string
if err != nil {
errLog = err.Error()
}
// 反向代理执行前后的结果信息
npl := NewProxyLog(
resCode,
int(time.Since(timeNow).Milliseconds()),
method,
url,
header,
string(resBody),
string(ReqByte),
errLog,
)
// 数据写入 数据库 中
nplByte, _ := json.Marshal(npl)
log.Info(string(nplByte))
}
func GetHttpClient(url string) (*http.Client, error) {
client := &http.Client{}
u, err := httpURL.Parse(url)
if err != nil {
return nil, err
}
if u.Scheme == "https" && net.ParseIP(strings.Split(u.Host, ":")[0]) != nil {
client.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
}
return client, nil
}
// 反向代理 GET
func PublicRequireByGet(url string, header http.Header) (resBody []byte, resCode int, err error) {
ft := time.Now()
resBody, resCode, err = PublicRequireByGetWithoutLog(url, header, nil)
RetProxyLog(header, resCode, ft, "GET", url, resBody, []byte{}, err)
return resBody, resCode, err
}
// 反向代理 GET, 不产生反向代理日志,url为网关api中登记的接口才可调用
func PublicRequireByGetWithoutLog(url string, header http.Header, reqByte []byte) (resBody []byte, resCode int, err error) {
hc := make(gin.HandlersChain, 0)
resBody, resCode, err = PublicRequire(url, "GET", nil, &header, hc, nil)
return resBody, resCode, err
}
// 反向代理 POST
func PublicRequireByPost(url string, header http.Header, reqByte []byte) (resBody []byte, resCode int, err error) {
ft := time.Now()
resBody, resCode, err = PublicRequireByPostWithoutLog(url, header, reqByte)
RetProxyLog(header, resCode, ft, "POST", url, resBody, reqByte, err)
return resBody, resCode, err
}
// 反向代理 POST, 不产生反向代理日志,url为网关api中登记的接口才可调用
func PublicRequireByPostWithoutLog(url string, header http.Header, reqByte []byte) (resBody []byte, resCode int, err error) {
hc := make(gin.HandlersChain, 0)
method := "POST"
resBody, resCode, err = PublicRequire(url, method, reqByte, &header, hc, nil)
return resBody, resCode, err
}
// 反向代理 PUT
func PublicRequireByPUT(url string, header http.Header, reqByte []byte) (resBody []byte, resCode int, err error) {
hc := make(gin.HandlersChain, 0)
ft := time.Now()
method := "PUT"
resBody, resCode, err = PublicRequire(url, method, reqByte, &header, hc, nil)
RetProxyLog(header, resCode, ft, method, url, resBody, reqByte, err)
return resBody, resCode, err
}
// 反向代理 PUT,不产生反向代理日志,url为网关api中登记的接口才可调用
func PublicRequireByPUTWithoutLog(url string, header http.Header, reqByte []byte) (resBody []byte, resCode int, err error) {
hc := make(gin.HandlersChain, 0)
method := "PUT"
resBody, resCode, err = PublicRequire(url, method, reqByte, &header, hc, nil)
return resBody, resCode, err
}
// 反向代理 POST 传入内容为表单
func PublicRequireByPostForm(url string, header http.Header, urlV url.Values) (resBody []byte, resCode int, err error) {
ft := time.Now()
resBody, resCode, err = PublicRequireByPostFormWithoutLog(url, header, urlV)
RetProxyLog(header, resCode, ft, "POST", url, resBody, []byte(urlV.Encode()), err)
return resBody, resCode, err
}
// 反向代理 POST 传入内容为表单,不产生反向代理日志,url为网关api中登记的接口才可调用
func PublicRequireByPostFormWithoutLog(url string, header http.Header, urlV url.Values) (resBody []byte, resCode int, err error) {
hc := make(gin.HandlersChain, 0)
reqByte := []byte(urlV.Encode())
header.Set("Content-Type", "application/x-www-form-urlencoded")
resBody, resCode, err = PublicRequire(url, "POST", reqByte, &header, hc, nil)
return resBody, resCode, err
}
func BehaviorIsAccess(koalaUrl string) (isAccess bool, err error) {
resBody, resCode, resErr := PublicRequireByGet(koalaUrl,
http.Header{})
if resCode != 200 {
return false, resErr
}
var resMap map[string]interface{}
if err := json.Unmarshal(resBody, &resMap); err != nil {
log.Error("反序列化失败,err:", err.Error())
return false, err
}
if resMap["Str_reason"].(string) == "Allow" {
return true, nil
}
return false, errors.New("禁止触发当前行为")
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/wuzheng0709/backend-gopkg.git
git@gitee.com:wuzheng0709/backend-gopkg.git
wuzheng0709
backend-gopkg
backend-gopkg
v1.5.27

搜索帮助

0d507c66 1850385 C8b1a773 1850385