github.com/ablease/cli@v6.37.1-0.20180613014814-3adbb7d7fb19+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 rawWarnings, err := url.QueryUnescape(rawWarnings) 93 if err != nil { 94 return nil, err 95 } 96 97 var warnings []string 98 if rawWarnings != "" { 99 for _, warning := range strings.Split(rawWarnings, ",") { 100 warningTrimmed := strings.Trim(warning, " ") 101 warnings = append(warnings, warningTrimmed) 102 } 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 // TODO: only unmarshal on 'application/json', skip otherwise - Fixing this 127 // todo will require changing ALL the API tests to include the content-type 128 // in their tests. 129 if passedResponse.Result != nil { 130 err = DecodeJSON(passedResponse.RawResponse, passedResponse.Result) 131 if err != nil { 132 return err 133 } 134 } 135 136 return nil 137 } 138 139 func (*CloudControllerConnection) processRequestErrors(request *http.Request, err error) error { 140 switch e := err.(type) { 141 case *url.Error: 142 switch urlErr := e.Err.(type) { 143 case x509.UnknownAuthorityError: 144 return ccerror.UnverifiedServerError{ 145 URL: request.URL.String(), 146 } 147 case x509.HostnameError: 148 return ccerror.SSLValidationHostnameError{ 149 Message: urlErr.Error(), 150 } 151 default: 152 return ccerror.RequestError{Err: e} 153 } 154 default: 155 return err 156 } 157 }