package fetch import ( "bytes" "context" "crypto/tls" "encoding/json" "encoding/xml" "fmt" "io" "net" "net/http" "net/url" "strings" "time" ) var ( httpClient = http.Client{ Timeout: time.Second * 15, Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, DialContext: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }).DialContext, ForceAttemptHTTP2: false, MaxIdleConns: 10, IdleConnTimeout: 30 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, }, } ) func Get(ctx context.Context, urlString string, cbs ...Option) (res *http.Response, err error) { var ( uri *url.URL req *http.Request ) opts := newOptions() for _, cb := range cbs { cb(opts) } if uri, err = url.Parse(urlString); err != nil { return } if opts.Params != nil { qs := uri.Query() for k, v := range opts.Params { qs.Set(k, v) } uri.RawQuery = qs.Encode() } if req, err = http.NewRequest(http.MethodGet, uri.String(), nil); err != nil { return } if opts.Header != nil { for k, v := range opts.Header { req.Header.Set(k, v) } } return Do(ctx, req) } func Post(ctx context.Context, urlString string, cbs ...Option) (res *http.Response, err error) { var ( buf []byte uri *url.URL req *http.Request contentType string reader io.Reader ) opts := newOptions() for _, cb := range cbs { cb(opts) } if uri, err = url.Parse(urlString); err != nil { return } if opts.Params != nil { qs := uri.Query() for k, v := range opts.Params { qs.Set(k, v) } uri.RawQuery = qs.Encode() } if opts.Data != nil { switch v := opts.Data.(type) { case string: reader = strings.NewReader(v) contentType = "x-www-form-urlencoded" case []byte: reader = bytes.NewReader(v) contentType = "x-www-form-urlencoded" default: if buf, err = json.Marshal(v); err == nil { reader = bytes.NewReader(buf) contentType = "application/json" } else { return } } } if req, err = http.NewRequest(http.MethodPost, uri.String(), reader); err != nil { return } if opts.Header != nil { for k, v := range opts.Header { req.Header.Set(k, v) } } req.Header.Set("Content-Type", contentType) return Do(ctx, req) } func Do(ctx context.Context, req *http.Request) (res *http.Response, err error) { return httpClient.Do(req.WithContext(ctx)) } func Request(ctx context.Context, urlString string, response any, cbs ...Option) (err error) { var ( buf []byte uri *url.URL res *http.Response req *http.Request contentType string ) opts := newOptions() for _, cb := range cbs { cb(opts) } if uri, err = url.Parse(urlString); err != nil { return } if opts.Params != nil { qs := uri.Query() for k, v := range opts.Params { qs.Set(k, v) } uri.RawQuery = qs.Encode() } if req, err = http.NewRequest(http.MethodGet, uri.String(), nil); err != nil { return } if opts.Header != nil { for k, v := range opts.Header { req.Header.Set(k, v) } } if res, err = Do(ctx, req); err != nil { return } defer func() { _ = res.Body.Close() }() if res.StatusCode != http.StatusOK { if buf, err = io.ReadAll(res.Body); err == nil && len(buf) > 0 { err = fmt.Errorf("remote server response %s(%d): %s", res.Status, res.StatusCode, string(buf)) } else { err = fmt.Errorf("remote server response %d: %s", res.StatusCode, res.Status) } return } contentType = strings.ToLower(res.Header.Get("Content-Type")) if strings.Contains(contentType, JSON) { err = json.NewDecoder(res.Body).Decode(response) } else if strings.Contains(contentType, XML) { err = xml.NewDecoder(res.Body).Decode(response) } else { err = fmt.Errorf("unsupported content type: %s", contentType) } return }