From ea6896cf05daa74d8ba4e7411cbe30f1a1a800ff Mon Sep 17 00:00:00 2001 From: kzkzzzz Date: Sun, 14 Sep 2025 00:19:27 +0800 Subject: [PATCH] update --- go.mod | 17 +- go.sum | 30 ++- myhttp/fasthttpc/const.go | 19 ++ myhttp/fasthttpc/httpclient.go | 366 ++++++++++++++++++++++++++++ myhttp/fasthttpc/httpclient_test.go | 39 +++ myhttp/fasthttpc/option.go | 84 +++++++ 6 files changed, 539 insertions(+), 16 deletions(-) create mode 100644 myhttp/fasthttpc/const.go create mode 100644 myhttp/fasthttpc/httpclient.go create mode 100644 myhttp/fasthttpc/httpclient_test.go create mode 100644 myhttp/fasthttpc/option.go diff --git a/go.mod b/go.mod index 0f94168..3483506 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module git.makemake.in/kzkzzzz/mycommon -go 1.23.0 +go 1.24.2 require ( github.com/gin-contrib/pprof v1.5.2 @@ -32,13 +32,15 @@ require ( github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.10.0 go.uber.org/zap v1.24.0 - golang.org/x/sync v0.12.0 + golang.org/x/sync v0.17.0 + golang.org/x/time v0.5.0 google.golang.org/grpc v1.67.1 gorm.io/driver/mysql v1.5.7 gorm.io/gorm v1.25.12 ) require ( + github.com/andybalholm/brotli v1.2.0 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bytedance/sonic v1.13.2 // indirect @@ -64,6 +66,7 @@ require ( github.com/hashicorp/serf v0.10.1 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/knadh/koanf/maps v0.1.1 // indirect github.com/leodido/go-urn v1.4.0 // indirect @@ -90,14 +93,16 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.66.0 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/arch v0.15.0 // indirect - golang.org/x/crypto v0.36.0 // indirect + golang.org/x/crypto v0.42.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.37.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/net v0.44.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.29.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect diff --git a/go.sum b/go.sum index f6c2f11..dc76527 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= @@ -152,6 +154,8 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= @@ -303,6 +307,10 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.66.0 h1:M87A0Z7EayeyNaV6pfO3tUTUiYO0dZfEJnRGXTVNuyU= +github.com/valyala/fasthttp v1.66.0/go.mod h1:Y4eC+zwoocmXSVCB1JmhNbYtS7tZPRI2ztPB72EVObs= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= @@ -316,8 +324,8 @@ golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -327,15 +335,15 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -357,15 +365,17 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/myhttp/fasthttpc/const.go b/myhttp/fasthttpc/const.go new file mode 100644 index 0000000..7f0393d --- /dev/null +++ b/myhttp/fasthttpc/const.go @@ -0,0 +1,19 @@ +package fasthttpc + +const ( + HeaderUserAgent = "User-Agent" + HeaderContentType = "Content-Type" + HeaderCookie = "Cookie" + HeaderAccept = "Accept" + HeaderOrigin = "Origin" + HeaderReferer = "Referer" + HeaderAcceptLanguage = "Accept-Language" + + ContentTypeJSON = "application/json; charset=utf-8" +) +const ( + MobileUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/124.0.6367.68 MobileRequest/15E148 Safari/604.1" + PcUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36" + AcceptHtml = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" + AcceptCNLanguage = "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7" +) diff --git a/myhttp/fasthttpc/httpclient.go b/myhttp/fasthttpc/httpclient.go new file mode 100644 index 0000000..917b4c2 --- /dev/null +++ b/myhttp/fasthttpc/httpclient.go @@ -0,0 +1,366 @@ +package fasthttpc + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + jsoniter "github.com/json-iterator/go" + "github.com/valyala/fasthttp" + "golang.org/x/time/rate" + "io" + "net/http" + "net/url" + "time" +) + +var ( + defaultClient *HttpClient +) + +func init() { + defaultClient = New() +} + +func ReInitDefault(timeout time.Duration) { + defaultClient = New(WithTimout(timeout)) +} + +func ReInitDefaultOpt(opts ...ConfigOpt) { + defaultClient = New(opts...) +} + +func Client() *HttpClient { + return defaultClient +} + +type Request struct { + ctx context.Context + header http.Header + body any + mapQuery map[string]string + urlQuery url.Values + httpClient *HttpClient + contentType string + noWaitQps bool +} + +type HttpClient struct { + config *Config + client *fasthttp.Client + qpsLimiter *rate.Limiter +} + +func New(opts ...ConfigOpt) *HttpClient { + config := &Config{} + for _, opt := range opts { + opt(config) + } + + if config.timeout <= 0 { + config.timeout = time.Second * 3 + } + + if config.fasthttpTimeout <= 0 { + config.fasthttpTimeout = time.Second * 10 + } + + if config.maxConnPerHost <= 0 { + config.maxConnPerHost = 10000 + } + + if config.client == nil { + + client := &fasthttp.Client{ + TLSConfig: &tls.Config{InsecureSkipVerify: true}, + DialTimeout: (&fasthttp.TCPDialer{ + Concurrency: 0, + DNSCacheDuration: time.Second * 600, + }).DialTimeout, + MaxConnsPerHost: config.maxConnPerHost, + MaxIdleConnDuration: time.Second * 90, + MaxIdemponentCallAttempts: 5, + ReadTimeout: config.fasthttpTimeout + time.Second, + MaxConnWaitTimeout: 0, + RetryIfErr: func(req *fasthttp.Request, attempts int, err error) (bool, bool) { + if errors.Is(err, fasthttp.ErrConnectionClosed) || errors.Is(err, io.EOF) { + return false, true + } + return false, false + }, + } + + config.client = client + } + + hc := &HttpClient{ + config: config, + client: config.client, + } + + if config.qpsLimiter != nil { + hc.qpsLimiter = config.qpsLimiter + } else if config.qps > 0 { + hc.qpsLimiter = rate.NewLimiter(rate.Every(time.Second/time.Duration(config.qps)), config.qps) + } + + return hc +} + +func (h *HttpClient) RawClient() *fasthttp.Client { + return h.client +} + +func (h *HttpClient) NewRequest(ctx context.Context) *Request { + r := &Request{ + ctx: ctx, + header: nil, + mapQuery: nil, + httpClient: h, + urlQuery: url.Values{}, + } + return r +} + +func (h *HttpClient) NewPcRequest(ctx context.Context) *Request { + r := h.NewRequest(ctx) + r.SetHeaderPcAgent() + return r +} + +func (h *HttpClient) NewMobileRequest(ctx context.Context) *Request { + r := h.NewRequest(ctx) + r.SetHeaderMobileAgent() + return r +} + +func (r *Request) SetContentType(contentType string) *Request { + r.contentType = contentType + return r +} + +func (r *Request) SetBody(body any) *Request { + r.body = body + return r +} + +func (r *Request) SetQueryParam(k string, v string) *Request { + if r.mapQuery == nil { + r.mapQuery = make(map[string]string) + } + r.mapQuery[k] = v + return r +} + +func (r *Request) SetQueryParams(params map[string]string) *Request { + for k, v := range params { + r.SetQueryParam(k, v) + } + return r +} + +func (r *Request) SetUrlQueryParam(k string, v string) *Request { + r.urlQuery.Add(k, v) + return r +} + +func (r *Request) SetHeader(k, v string) *Request { + if r.header == nil { + r.header = http.Header{} + } + r.header.Set(k, v) + return r +} + +func (r *Request) SetHeaders(headers map[string]string) *Request { + for k, v := range headers { + r.SetHeader(k, v) + } + return r +} + +func (r *Request) NoWaitQps() *Request { + r.noWaitQps = true + return r +} + +func (r *Request) Get(rawUrl string) (*Response, error) { + return r.Do(http.MethodGet, rawUrl) +} + +func (r *Request) Post(rawUrl string) (*Response, error) { + return r.Do(http.MethodPost, rawUrl) +} + +var QpsLimitError = fmt.Errorf("qps limit") + +func (r *Request) DoTimeout(timeout time.Duration, method, rawUrl string) (*Response, error) { + if timeout <= time.Millisecond { + return nil, fmt.Errorf("timeout is too small <= 1 millisecond") + } + + if r.httpClient.config.useCtxTimeout == false { + return r.doRequest(timeout, method, rawUrl) + } + + ctx, cancel := context.WithTimeout(r.ctx, timeout) + defer cancel() + + type tmpRes struct { + res *Response + err error + } + + var resChan = make(chan tmpRes, 1) + + go func() { + res, err := r.doRequest(r.httpClient.config.fasthttpTimeout, method, rawUrl) + if err != nil { + resChan <- tmpRes{err: err} + return + } + resChan <- tmpRes{res: res} + }() + + select { + case <-ctx.Done(): + return nil, fmt.Errorf("request timeout: %s (%s)", timeout, ctx.Err()) + case res := <-resChan: + return res.res, res.err + } + +} + +func (r *Request) Do(method, rawUrl string) (*Response, error) { + return r.DoTimeout(r.httpClient.config.timeout, method, rawUrl) +} + +func (r *Request) doRequest(timeout time.Duration, method, rawUrl string) (*Response, error) { + if r.httpClient.qpsLimiter != nil { + if r.noWaitQps { + allow := r.httpClient.qpsLimiter.Allow() + if !allow { + return nil, QpsLimitError + } + } else { + err := r.httpClient.qpsLimiter.Wait(r.ctx) + if err != nil { + return nil, err + } + } + } + + req := fasthttp.AcquireRequest() + resp := fasthttp.AcquireResponse() + + defer func() { + fasthttp.ReleaseRequest(req) + fasthttp.ReleaseResponse(resp) + }() + + var reqBody []byte + if r.body != nil { + switch v := r.body.(type) { + case string: + reqBody = []byte(v) + case []byte: + reqBody = v + + default: + marshal, err := jsoniter.Marshal(r.body) + if err != nil { + return nil, err + } + reqBody = marshal + } + + req.SetBody(reqBody) + } + + reqUrl, err := url.Parse(rawUrl) + if err != nil { + return nil, fmt.Errorf("parse url err: %s (%s)", err.Error(), rawUrl) + } + + urlQuery := reqUrl.Query() + for k, v := range r.mapQuery { + urlQuery.Add(k, v) + } + + for k, v := range r.urlQuery { + urlQuery[k] = v + } + + if len(urlQuery) > 0 { + reqUrl.RawQuery = urlQuery.Encode() + } + + req.SetRequestURI(reqUrl.String()) + + //if len(urlQuery) > 0 { + // req.URI().SetQueryString(urlQuery.Encode()) + //} + + if r.contentType != "" { + req.Header.Set("Content-Type", r.contentType) + } + + for k := range r.header { + req.Header.Set(k, r.header.Get(k)) + } + req.Header.SetMethod(method) + + err = r.httpClient.client.DoTimeout(req, resp, timeout) + if err != nil { + if errors.Is(err, fasthttp.ErrTimeout) { + return nil, err + } + + return nil, fmt.Errorf("req err: %s (%s)", err, reqUrl.String()) + } + + tmpBody := resp.Body() + body := make([]byte, len(tmpBody)) + copy(body, tmpBody) + + if r.httpClient.config.noCheckStatus == false { + if resp.StatusCode() != http.StatusOK { + return nil, fmt.Errorf("status code err: %d (%s)", resp.StatusCode(), body) + } + } + + copyHeader := &fasthttp.ResponseHeader{} + resp.Header.CopyTo(copyHeader) + + res := &Response{ + Header: copyHeader, + body: body, + } + + return res, nil +} + +func (r *Request) SetHeaderPcAgent() *Request { + r.SetHeader(HeaderUserAgent, PcUserAgent) + return r +} + +func (r *Request) SetHeaderMobileAgent() *Request { + r.SetHeader(HeaderUserAgent, MobileUserAgent) + return r +} + +func (r *Request) SetHeaderAcceptHtml() *Request { + r.SetHeader(HeaderAccept, AcceptHtml) + return r +} + +type Response struct { + Header *fasthttp.ResponseHeader + body []byte + //Response *http.Response +} + +func (r *Response) GetBody() []byte { + return r.body +} diff --git a/myhttp/fasthttpc/httpclient_test.go b/myhttp/fasthttpc/httpclient_test.go new file mode 100644 index 0000000..5c5362b --- /dev/null +++ b/myhttp/fasthttpc/httpclient_test.go @@ -0,0 +1,39 @@ +package fasthttpc + +import ( + "context" + "fmt" + "testing" + "time" +) + +func TestClient(t *testing.T) { + client := New(WithUseCtxTimeout(true), WithTimout(time.Second*5)) + + reqUrl := "http://127.0.0.1:18001/swap-instructions" + res, err := client.NewRequest(context.Background()). + SetQueryParams(map[string]string{ + "a": "b", + "name": "qwe123", + "time": "100", + }). + SetContentType(ContentTypeJSON). + SetHeaders(map[string]string{ + "Api": "ok", + }). + SetBody(map[string]any{ + "hello": "world", + "id": 5, + }). + Post(reqUrl) + + if err != nil { + fmt.Println(err) + return + } + + fmt.Printf("%+v\n", string(res.GetBody())) + + fmt.Println(res.Header.String()) + fmt.Println(res.Header.StatusCode()) +} diff --git a/myhttp/fasthttpc/option.go b/myhttp/fasthttpc/option.go new file mode 100644 index 0000000..a046d28 --- /dev/null +++ b/myhttp/fasthttpc/option.go @@ -0,0 +1,84 @@ +package fasthttpc + +import ( + "github.com/valyala/fasthttp" + "golang.org/x/time/rate" + "time" +) + +type ( + Config struct { + timeout time.Duration + client *fasthttp.Client + noCheckStatus bool + qps int + qpsLimiter *rate.Limiter + maxConnPerHost int + fasthttpTimeout time.Duration + useCtxTimeout bool + } + + ConfigOpt func(c *Config) +) + +func WithTimout(v time.Duration) ConfigOpt { + return func(c *Config) { + c.timeout = v + } +} + +func WithUseCtxTimeout(v bool) ConfigOpt { + return func(c *Config) { + c.useCtxTimeout = v + } +} + +func WithFasthttpimout(v time.Duration) ConfigOpt { + return func(c *Config) { + c.fasthttpTimeout = v + } +} + +func WithClient(v *fasthttp.Client) ConfigOpt { + return func(c *Config) { + c.client = v + } +} + +func WithMaxConnPerHost(v int) ConfigOpt { + return func(c *Config) { + c.maxConnPerHost = v + } +} + +//func WithRedirectFn(v func(req *http.Request, via []*http.Request) error) ConfigOpt { +// return func(c *Config) { +// c.redirectFn = v +// } +//} +// +//func WithNoRedirect() ConfigOpt { +// return func(c *Config) { +// c.redirectFn = func(req *http.Request, via []*http.Request) error { +// return http.ErrUseLastResponse +// } +// } +//} + +func WithNoCheckStatus(v bool) ConfigOpt { + return func(c *Config) { + c.noCheckStatus = v + } +} + +func WithQps(v int) ConfigOpt { + return func(c *Config) { + c.qps = v + } +} + +func WithQpsLimiter(v *rate.Limiter) ConfigOpt { + return func(c *Config) { + c.qpsLimiter = v + } +}