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  }