package httputils import ( "crypto/tls" "io" "net" "net/http" "time" ) const DefaultTimeout = 10 * time.Second func HttpGet(url string, timeout time.Duration, customHeaders map[string]string, cookies []*http.Cookie) ([]byte, int, []*http.Cookie, error) { req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, 0, nil, err } for _, c := range cookies { req.AddCookie(c) } transport, err := CreateTransport(&http.Transport{}, timeout, customHeaders) if err != nil { return nil, 0, nil, err } client := http.Client{Transport: transport, Timeout: timeout} resp, err := client.Do(req) if err != nil { return nil, 0, nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) return body, resp.StatusCode, resp.Cookies(), err } type customHeadersRoundTripper struct { headers map[string]string originalRT http.RoundTripper } func (rt *customHeadersRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { // note: no need to check for nil or empty map - newCustomHeadersRoundTripper will assure us there will always be at least 1 for k, v := range rt.headers { req.Header.Set(k, v) } return rt.originalRT.RoundTrip(req) } func newCustomHeadersRoundTripper(headers map[string]string, rt http.RoundTripper) http.RoundTripper { if len(headers) == 0 { // if there are no custom headers then there is no need for a special RoundTripper; therefore just return the original RoundTripper return rt } return &customHeadersRoundTripper{ headers: headers, originalRT: rt, } } // Creates a new HTTP Transport with TLS, Timeouts, and optional custom headers. // // Please remember that setting long timeouts is not recommended as it can make // idle connections stay open for as long as 2 * timeout. This should only be // done in cases where you know the request is very likely going to be reused at // some point in the near future. func CreateTransport(transportConfig *http.Transport, timeout time.Duration, customHeaders map[string]string) (http.RoundTripper, error) { // Limits the time spent establishing a TCP connection if a new one is // needed. If DialContext is not set, Dial is used, we only create a new one // if neither is defined. if transportConfig.DialContext == nil { transportConfig.DialContext = (&net.Dialer{ Timeout: timeout, }).DialContext } transportConfig.IdleConnTimeout = timeout transportConfig.TLSClientConfig = &tls.Config{ InsecureSkipVerify: true, } // We might need some custom RoundTrippers to manipulate the requests (for auth and other custom request headers). // Chain together the RoundTrippers that we need, retaining the outer-most round tripper so we can return it. outerRoundTripper := newCustomHeadersRoundTripper(customHeaders, transportConfig) return outerRoundTripper, nil }