github.com/dcarley/cf-cli@v6.24.1-0.20170220111324-4225ff346898+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 16 // CloudControllerConnection represents a connection to the Cloud Controller 17 // server. 18 type CloudControllerConnection struct { 19 HTTPClient *http.Client 20 UserAgent string 21 } 22 23 // Config is for configuring a CloudControllerConnection. 24 type Config struct { 25 DialTimeout time.Duration 26 SkipSSLValidation bool 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 *http.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) 56 if err != nil { 57 return connection.processRequestErrors(request, err) 58 } 59 60 return connection.populateResponse(response, passedResponse) 61 } 62 63 func (connection *CloudControllerConnection) processRequestErrors(request *http.Request, err error) error { 64 switch e := err.(type) { 65 case *url.Error: 66 switch urlErr := e.Err.(type) { 67 case x509.UnknownAuthorityError: 68 return UnverifiedServerError{ 69 URL: request.URL.String(), 70 } 71 case x509.HostnameError: 72 return SSLValidationHostnameError{ 73 Message: urlErr.Error(), 74 } 75 default: 76 return RequestError{Err: e} 77 } 78 default: 79 return err 80 } 81 } 82 83 func (connection *CloudControllerConnection) populateResponse(response *http.Response, passedResponse *Response) error { 84 passedResponse.HTTPResponse = response 85 86 // The cloud controller returns warnings with key "X-Cf-Warnings", and the 87 // value is a comma seperated string. 88 if rawWarnings := response.Header.Get("X-Cf-Warnings"); rawWarnings != "" { 89 passedResponse.Warnings = []string{} 90 for _, warning := range strings.Split(rawWarnings, ",") { 91 warningTrimmed := strings.Trim(warning, " ") 92 passedResponse.Warnings = append(passedResponse.Warnings, warningTrimmed) 93 } 94 } 95 96 rawBytes, err := ioutil.ReadAll(response.Body) 97 defer response.Body.Close() 98 if err != nil { 99 return err 100 } 101 102 passedResponse.RawResponse = rawBytes 103 104 err = connection.handleStatusCodes(response, passedResponse) 105 if err != nil { 106 return err 107 } 108 109 if passedResponse.Result != nil { 110 decoder := json.NewDecoder(bytes.NewBuffer(passedResponse.RawResponse)) 111 decoder.UseNumber() 112 err = decoder.Decode(passedResponse.Result) 113 if err != nil { 114 return err 115 } 116 } 117 118 return nil 119 } 120 121 func (*CloudControllerConnection) handleStatusCodes(response *http.Response, passedResponse *Response) error { 122 if response.StatusCode >= 400 { 123 return RawHTTPStatusError{ 124 StatusCode: response.StatusCode, 125 RawResponse: passedResponse.RawResponse, 126 RequestIDs: response.Header["X-Vcap-Request-Id"], 127 } 128 } 129 130 return nil 131 }