From 27ccc2a0806c754a2c612a61564601105725cdb2 Mon Sep 17 00:00:00 2001 From: kzkzzzz Date: Mon, 7 Jul 2025 22:40:16 +0800 Subject: [PATCH] update --- myhttp/httpc/const.go | 19 +++ myhttp/httpc/httpclient.go | 276 +++++++++++++++++++++++++++++++++++++ 2 files changed, 295 insertions(+) create mode 100644 myhttp/httpc/const.go create mode 100644 myhttp/httpc/httpclient.go diff --git a/myhttp/httpc/const.go b/myhttp/httpc/const.go new file mode 100644 index 0000000..197dad0 --- /dev/null +++ b/myhttp/httpc/const.go @@ -0,0 +1,19 @@ +package httpc + +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/httpc/httpclient.go b/myhttp/httpc/httpclient.go new file mode 100644 index 0000000..4154037 --- /dev/null +++ b/myhttp/httpc/httpclient.go @@ -0,0 +1,276 @@ +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 +}