代码拉取完成,页面将自动刷新
package cHTTPClient
import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"strings"
"time"
"github.com/gin-gonic/gin"
"gitee.com/csingo/cHelper"
"gitee.com/csingo/cCommon"
"gitee.com/csingo/cContext"
"gitee.com/csingo/cLog"
)
type client struct {
ctx *gin.Context
option *Option
interval int
}
func (c *client) url() string {
var host, uri, query string
var builder strings.Builder
var fields = make([]string, 0)
host = strings.TrimSuffix(c.option.Host, "/")
uri = strings.TrimPrefix(c.option.Uri, "/")
for k, v := range c.option.Query {
fields = append(fields, fmt.Sprintf("%s=%s", k, v))
}
query = strings.Join(fields, "&")
builder.WriteString(host)
if uri != "" {
builder.WriteString("/")
builder.WriteString(uri)
}
if strings.Contains(host, "?") || strings.Contains(uri, "?") {
builder.WriteString("&")
} else {
builder.WriteString("?")
}
builder.WriteString(query)
return builder.String()
}
func (c *client) send() (rsp *Response, err error) {
var request *http.Request
var response *http.Response
var current int
for current < c.option.Try || c.option.Try < 0 {
if current > 0 {
var msg string
if err != nil {
msg = err.Error()
}
cLog.WithContext(c.ctx, map[string]any{
"source": "cHTTPClient.client.send",
"retry": current,
"err": msg,
}).Error("外部请求重试")
}
c.sleep(current)
// 创建客户端
cli := &http.Client{
CheckRedirect: c.option.RedirectHandler,
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: c.option.ConnectTimeout * time.Second, // 设置连接超时
}).DialContext,
},
Timeout: c.option.Timeout, // 设置请求超时,包括连接建立、重定向及读取响应主体的时间
}
// 创建请求
var body io.Reader
body, err = formatRequestBody(c.option.Data, c.option.Headers[HeaderContentType])
if err != nil {
return nil, err
}
request, err = http.NewRequest(string(c.option.Method), c.url(), body)
if err != nil {
return nil, err
}
// 设置请求头
for k, v := range c.option.Headers {
request.Header.Set(k, v)
}
// 设置cookies
if len(c.option.Cookies) > 0 {
for _, cookie := range c.option.Cookies {
request.AddCookie(cookie)
}
}
// 发起请求
var retry bool
response, err = cli.Do(request)
if c.option.ResponseHandler != nil {
retry, err = c.option.ResponseHandler(c.ctx, response, err)
if !retry {
break
}
}
// if err == nil {
// var isRetry bool
// if c.option.ResponseHandler != nil {
// isRetry, err = c.option.ResponseHandler(c.ctx, response)
// }
//
// if err == nil {
// break
// }
//
// if !isRetry {
// break
// }
// }
current++
}
rsp, err = c.response(response)
return
}
func (c *client) response(r *http.Response) (rsp *Response, err error) {
if r == nil {
rsp = &Response{
Header: make(http.Header),
Status: http.StatusText(http.StatusNoContent),
StatusCode: http.StatusNoContent,
Body: make([]byte, 0),
}
return
}
defer r.Body.Close()
if r.Header == nil {
r.Header = make(http.Header)
}
rsp = &Response{
Header: r.Header,
Cookies: r.Cookies(),
Status: r.Status,
StatusCode: r.StatusCode,
Body: nil,
}
if r.StatusCode != http.StatusOK {
err = errors.New(r.Status)
return
}
switch r.Header.Get("Content-Type") {
case "text/event-stream":
if c.option.Stream == nil {
rsp.Body, err = io.ReadAll(r.Body)
if err != nil {
return
}
break
}
reader := bufio.NewReader(r.Body)
for {
var line []byte
var content string
line, err = reader.ReadBytes('\n')
if err != nil {
c.option.Stream <- err.Error()
break
}
content = string(line)
content = strings.TrimPrefix(content, "data:")
content = strings.Trim(content, "")
content = strings.Trim(content, "\n")
if strings.TrimSpace(content) == cCommon.HTTPStreamEnd {
break
}
c.option.Stream <- content
}
c.option.Stream <- cCommon.HTTPStreamEnd
default:
rsp.Body, err = io.ReadAll(r.Body)
if err != nil {
return
}
if c.option.Stream != nil {
c.option.Stream <- string(rsp.Body)
}
}
return
}
func (c *client) sleep(count int) {
try := count - 1
if try < 0 {
try = 0
}
incr := try / c.option.IntervalTry * c.option.IntervalIncr
if count > 0 {
c.interval = incr + c.option.Interval
}
if c.interval > c.option.IntervalMax {
c.interval = c.option.IntervalMax
}
if c.interval > 0 {
time.Sleep(time.Duration(c.interval) * time.Second)
}
}
func newClient(ctx *gin.Context, option *Option) (cli *client) {
if option.Interval < 0 {
option.Interval = 0
}
if option.IntervalIncr < 0 {
option.IntervalIncr = 0
}
if option.IntervalMax < 0 {
option.IntervalMax = 0
}
if option.IntervalTry < 1 {
option.IntervalTry = 1
}
if option.Try == 0 {
option.Try = 3
}
if option.Headers == nil {
option.Headers = make(map[string]string)
}
if option.Method == "" {
option.Method = MethodGET
}
option.Headers[cCommon.XIndex_TraceId] = cContext.GetTraceId(ctx)
if _, ok := option.Headers[HeaderContentType]; !ok && option.Method != MethodGET {
option.Headers[HeaderContentType] = HeaderContentTypeJson
}
if option.ResponseHandler == nil {
option.ResponseHandler = defaultResponseHandler
}
cli = &client{
ctx: ctx,
option: option,
interval: 0,
}
return
}
func defaultResponseHandler(ctx *gin.Context, response *http.Response, e error) (retry bool, err error) {
if e != nil {
return true, e
}
if response.StatusCode != http.StatusOK {
return true, errors.New(response.Status)
}
return
}
func formatRequestBody(data any, contentType string) (body io.Reader, err error) {
if cHelper.IsString(data) {
body = strings.NewReader(data.(string))
return
}
switch contentType {
default:
return
case HeaderContentTypeJson:
var buf bytes.Buffer
encoder := json.NewEncoder(&buf)
encoder.SetEscapeHTML(false)
if err = encoder.Encode(data); err != nil {
return
}
body = &buf
return
case HeaderContentTypeMultipart:
body = data.(io.Reader)
return
}
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。