update
This commit is contained in:
19
myhttp/fasthttpc/const.go
Normal file
19
myhttp/fasthttpc/const.go
Normal file
@@ -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"
|
||||
)
|
||||
366
myhttp/fasthttpc/httpclient.go
Normal file
366
myhttp/fasthttpc/httpclient.go
Normal file
@@ -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
|
||||
}
|
||||
39
myhttp/fasthttpc/httpclient_test.go
Normal file
39
myhttp/fasthttpc/httpclient_test.go
Normal file
@@ -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())
|
||||
}
|
||||
84
myhttp/fasthttpc/option.go
Normal file
84
myhttp/fasthttpc/option.go
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user