代码拉取完成,页面将自动刷新
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
}
type SkipReqBody func(url string) bool
// 生成代理日志
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()
}
// TODO: 通过 http.Header 过滤 body
// 反向代理执行前后的结果信息
if header.Get(SKIPREQBODY) == "true" {
ReqByte = []byte("跳过读取(body)")
}
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
}
const SKIPREQBODY = "SkipReqBody"
func SetSkipReqBody(header http.Header) http.Header {
header.Set(SKIPREQBODY, "true")
return header
}
// 反向代理 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("禁止触发当前行为")
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。