package chartserver import ( "fmt" "io" "io/ioutil" "net/http" "net/url" "strings" "sync" "time" commonhttp "github.com/goharbor/harbor/src/common/http" "github.com/goharbor/harbor/src/lib/errors" ) const ( clientTimeout = 30 * time.Second maxIdleConnections = 10 idleConnectionTimeout = 30 * time.Second ) var ( once sync.Once chartTransport *http.Transport ) // ChartClient is a http client to get the content from the external http server type ChartClient struct { // HTTP client httpClient *http.Client // Auth info credential *Credential } // NewChartClient is constructor of ChartClient // credential can be nil func NewChartClient(credential *Credential) *ChartClient { // Create http client with customized timeouts once.Do(func() { chartTransport = commonhttp.GetHTTPTransport(commonhttp.SecureTransport).Clone() chartTransport.MaxIdleConns = maxIdleConnections chartTransport.IdleConnTimeout = idleConnectionTimeout }) client := &http.Client{ Timeout: clientTimeout, Transport: chartTransport, } return &ChartClient{ httpClient: client, credential: credential, } } // GetContent get the bytes from the specified url func (cc *ChartClient) GetContent(addr string) ([]byte, error) { response, err := cc.sendRequest(addr, http.MethodGet, nil) if err != nil { err = errors.Wrap(err, "get content failed") return nil, err } content, err := ioutil.ReadAll(response.Body) if err != nil { err = errors.Wrap(err, "Read response body error") return nil, err } defer response.Body.Close() if response.StatusCode != http.StatusOK { text, err := extractError(content) if err != nil { err = errors.Wrap(err, "Extract content error failed") return nil, err } return nil, &commonhttp.Error{ Code: response.StatusCode, Message: text, } } return content, nil } // DeleteContent sends deleting request to the addr to delete content func (cc *ChartClient) DeleteContent(addr string) error { response, err := cc.sendRequest(addr, http.MethodDelete, nil) if err != nil { return err } content, err := ioutil.ReadAll(response.Body) if err != nil { return err } defer response.Body.Close() if response.StatusCode != http.StatusOK { text, err := extractError(content) if err != nil { return err } return &commonhttp.Error{ Code: response.StatusCode, Message: text, } } return nil } // sendRequest sends requests to the addr with the specified spec func (cc *ChartClient) sendRequest(addr string, method string, body io.Reader) (*http.Response, error) { if len(strings.TrimSpace(addr)) == 0 { return nil, errors.New("empty url is not allowed") } fullURI, err := url.Parse(addr) if err != nil { err = errors.Wrap(err, "Invalid url") return nil, err } request, err := http.NewRequest(method, addr, body) if err != nil { return nil, err } // Set basic auth if cc.credential != nil { request.SetBasicAuth(cc.credential.Username, cc.credential.Password) } response, err := cc.httpClient.Do(request) if err != nil { err = errors.Wrap(err, fmt.Sprintf("send request %s %s failed", method, fullURI.Path)) return nil, err } return response, nil }