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