github.com/arunkumar7540/cli@v6.45.0+incompatible/api/cloudcontroller/cloud_controller_connection.go (about) 1 package cloudcontroller 2 3 import ( 4 "crypto/tls" 5 "crypto/x509" 6 "io/ioutil" 7 "net" 8 "net/http" 9 "net/url" 10 "strings" 11 "time" 12 13 "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" 14 ) 15 16 // Config is for configuring a CloudControllerConnection. 17 type Config struct { 18 DialTimeout time.Duration 19 SkipSSLValidation bool 20 } 21 22 // CloudControllerConnection represents a connection to the Cloud Controller 23 // server. 24 type CloudControllerConnection struct { 25 HTTPClient *http.Client 26 UserAgent string 27 } 28 29 // NewConnection returns a new CloudControllerConnection with provided 30 // configuration. 31 func NewConnection(config Config) *CloudControllerConnection { 32 tr := &http.Transport{ 33 TLSClientConfig: &tls.Config{ 34 InsecureSkipVerify: config.SkipSSLValidation, 35 }, 36 Proxy: http.ProxyFromEnvironment, 37 DialContext: (&net.Dialer{ 38 KeepAlive: 30 * time.Second, 39 Timeout: config.DialTimeout, 40 }).DialContext, 41 } 42 43 return &CloudControllerConnection{ 44 HTTPClient: &http.Client{Transport: tr}, 45 } 46 } 47 48 // Make performs the request and parses the response. 49 func (connection *CloudControllerConnection) Make(request *Request, passedResponse *Response) error { 50 // In case this function is called from a retry, passedResponse may already 51 // be populated with a previous response. We reset in case there's an HTTP 52 // error and we don't repopulate it in populateResponse. 53 passedResponse.reset() 54 55 response, err := connection.HTTPClient.Do(request.Request) 56 if err != nil { 57 return connection.processRequestErrors(request.Request, err) 58 } 59 60 return connection.populateResponse(response, passedResponse) 61 } 62 63 func (*CloudControllerConnection) handleStatusCodes(response *http.Response, passedResponse *Response) error { 64 if response.StatusCode == http.StatusNoContent { 65 passedResponse.RawResponse = []byte("{}") 66 } else { 67 rawBytes, err := ioutil.ReadAll(response.Body) 68 defer response.Body.Close() 69 if err != nil { 70 return err 71 } 72 73 passedResponse.RawResponse = rawBytes 74 } 75 76 if response.StatusCode >= 400 { 77 return ccerror.RawHTTPStatusError{ 78 StatusCode: response.StatusCode, 79 RawResponse: passedResponse.RawResponse, 80 RequestIDs: response.Header["X-Vcap-Request-Id"], 81 } 82 } 83 84 return nil 85 } 86 87 // handleWarnings looks for the "X-Cf-Warnings" header in the cloud controller 88 // response and URI decodes them. The value can contain multiple warnings that 89 // are comma separated. 90 func (*CloudControllerConnection) handleWarnings(response *http.Response) ([]string, error) { 91 rawWarnings := response.Header.Get("X-Cf-Warnings") 92 if len(rawWarnings) == 0 { 93 return nil, nil 94 } 95 96 var warnings []string 97 for _, rawWarning := range strings.Split(rawWarnings, ",") { 98 warning, err := url.QueryUnescape(rawWarning) 99 if err != nil { 100 return nil, err 101 } 102 warnings = append(warnings, strings.Trim(warning, " ")) 103 } 104 105 return warnings, nil 106 } 107 108 func (connection *CloudControllerConnection) populateResponse(response *http.Response, passedResponse *Response) error { 109 passedResponse.HTTPResponse = response 110 111 warnings, err := connection.handleWarnings(response) 112 if err != nil { 113 return err 114 } 115 passedResponse.Warnings = warnings 116 117 if resourceLocationURL := response.Header.Get("Location"); resourceLocationURL != "" { 118 passedResponse.ResourceLocationURL = resourceLocationURL 119 } 120 121 err = connection.handleStatusCodes(response, passedResponse) 122 if err != nil { 123 return err 124 } 125 126 if passedResponse.DecodeJSONResponseInto != nil { 127 err = DecodeJSON(passedResponse.RawResponse, passedResponse.DecodeJSONResponseInto) 128 if err != nil { 129 return err 130 } 131 } 132 133 return nil 134 } 135 136 func (*CloudControllerConnection) processRequestErrors(request *http.Request, err error) error { 137 switch e := err.(type) { 138 case *url.Error: 139 switch urlErr := e.Err.(type) { 140 case x509.UnknownAuthorityError: 141 return ccerror.UnverifiedServerError{ 142 URL: request.URL.String(), 143 } 144 case x509.HostnameError: 145 return ccerror.SSLValidationHostnameError{ 146 Message: urlErr.Error(), 147 } 148 default: 149 return ccerror.RequestError{Err: e} 150 } 151 default: 152 return err 153 } 154 }