package httpc import ( "bytes" "context" "crypto/tls" "errors" "fmt" "git.makemake.in/kzkzzzz/mycommon/mylog" jsoniter "github.com/json-iterator/go" "io" "net/http" "net/url" "strings" "time" ) var ( defaultClient *HttpClient noRedirectClient *HttpClient ) func init() { defaultClient = NewWithRedirect(false) noRedirectClient = NewWithRedirect(true) } func NewTransport() *http.Transport { tr := http.DefaultTransport.(*http.Transport).Clone() tr.MaxIdleConns = 0 tr.MaxConnsPerHost = 0 tr.MaxIdleConnsPerHost = 2048 tr.IdleConnTimeout = time.Second * 90 tr.DisableKeepAlives = false tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: false} return tr } type Request struct { ctx context.Context header http.Header body any query map[string]string httpClient *HttpClient contentType string } type HttpClient struct { client *http.Client } func NewWithRedirect(noRedirect bool) *HttpClient { tr := NewTransport() client := &http.Client{ Transport: tr, Timeout: time.Second * 6, } if noRedirect { client.CheckRedirect = func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse } } h := &HttpClient{client: client} return h } func NewWithClient(httpClient *http.Client) *HttpClient { h := &HttpClient{client: httpClient} return h } func (h *HttpClient) SetProxy(pr string) *HttpClient { u, err := url.Parse(pr) if err != nil { panic(err) } h.client.Transport.(*http.Transport).Proxy = http.ProxyURL(u) return h } func (h *HttpClient) Request(ctx context.Context) *Request { r := &Request{ ctx: ctx, header: nil, query: nil, httpClient: h, } return r } func PcRequest(ctx context.Context) *Request { return defaultClient.PcRequest(ctx) } func MobileRequest(ctx context.Context) *Request { return defaultClient.MobileRequest(ctx) } func (h *HttpClient) PcRequest(ctx context.Context) *Request { r := h.Request(ctx) r.SetHeaderPcAgent() return r } func (h *HttpClient) MobileRequest(ctx context.Context) *Request { r := h.Request(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, v string) *Request { if r.query == nil { r.query = make(map[string]string) } r.query[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) 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) 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) } func (r *Request) Do(method, rawUrl string) (*Response, error) { reqUrl, err := url.Parse(rawUrl) if err != nil { return nil, err } var reqBody io.Reader = nil if r.body != nil { switch v := r.body.(type) { case string: reqBody = strings.NewReader(v) case []byte: reqBody = bytes.NewReader(v) default: marshal, err := jsoniter.Marshal(r.body) if err != nil { return nil, err } reqBody = bytes.NewReader(marshal) } } req, err := http.NewRequest(method, reqUrl.String(), reqBody) if err != nil { return nil, err } query := req.URL.Query() for k, v := range r.query { query.Add(k, v) } req.URL.RawQuery = query.Encode() req.Header.Set("Content-Type", r.contentType) for k := range r.header { req.Header.Set(k, r.header.Get(k)) } res, err := r.httpClient.client.Do(req) if err != nil { return nil, err } defer res.Body.Close() body, err := io.ReadAll(res.Body) if err != nil { return nil, err } if res.StatusCode != http.StatusOK { return nil, fmt.Errorf("status code err: %d (%s)", res.StatusCode, body) } resp := &Response{ body: body, Response: res, } return resp, nil } func Client() *HttpClient { return defaultClient } func NoRedirectClient() *HttpClient { return noRedirectClient } 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 } func (r *Request) GetRedirectUrl(reqUrl string) ( *url.URL, *Response, error) { //r.SetDebug(true) r.SetHeaderAcceptHtml() resp, err := r.Get(reqUrl) if err != nil && !errors.Is(err, http.ErrUseLastResponse) { return nil, nil, err } location, err := resp.Response.Location() if err != nil { mylog.Warnf("location code: %v", resp.Response.StatusCode) return nil, nil, err } return location, resp, nil } type Response struct { body []byte Response *http.Response } func (r *Response) GetBody() []byte { return r.body }