github.com/jghiloni/cli@v6.28.1-0.20170628223758-0ce05fe032a2+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 rawBytes, err := ioutil.ReadAll(response.Body) 99 defer response.Body.Close() 100 if err != nil { 101 return err 102 } 103 104 passedResponse.RawResponse = rawBytes 105 106 err = connection.handleStatusCodes(response, passedResponse) 107 if err != nil { 108 return err 109 } 110 111 if passedResponse.Result != nil { 112 decoder := json.NewDecoder(bytes.NewBuffer(passedResponse.RawResponse)) 113 decoder.UseNumber() 114 err = decoder.Decode(passedResponse.Result) 115 if err != nil { 116 return err 117 } 118 } 119 120 return nil 121 } 122 123 func (_ *CloudControllerConnection) handleStatusCodes(response *http.Response, passedResponse *Response) error { 124 if response.StatusCode >= 400 { 125 return ccerror.RawHTTPStatusError{ 126 StatusCode: response.StatusCode, 127 RawResponse: passedResponse.RawResponse, 128 RequestIDs: response.Header["X-Vcap-Request-Id"], 129 } 130 } 131 132 return nil 133 }