github.com/arunkumar7540/cli@v6.45.0+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  	if len(rawWarnings) == 0 {
    93  		return nil, nil
    94  	}
    95  
    96  	var warnings []string
    97  	for _, rawWarning := range strings.Split(rawWarnings, ",") {
    98  		warning, err := url.QueryUnescape(rawWarning)
    99  		if err != nil {
   100  			return nil, err
   101  		}
   102  		warnings = append(warnings, strings.Trim(warning, " "))
   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  	if passedResponse.DecodeJSONResponseInto != nil {
   127  		err = DecodeJSON(passedResponse.RawResponse, passedResponse.DecodeJSONResponseInto)
   128  		if err != nil {
   129  			return err
   130  		}
   131  	}
   132  
   133  	return nil
   134  }
   135  
   136  func (*CloudControllerConnection) processRequestErrors(request *http.Request, err error) error {
   137  	switch e := err.(type) {
   138  	case *url.Error:
   139  		switch urlErr := e.Err.(type) {
   140  		case x509.UnknownAuthorityError:
   141  			return ccerror.UnverifiedServerError{
   142  				URL: request.URL.String(),
   143  			}
   144  		case x509.HostnameError:
   145  			return ccerror.SSLValidationHostnameError{
   146  				Message: urlErr.Error(),
   147  			}
   148  		default:
   149  			return ccerror.RequestError{Err: e}
   150  		}
   151  	default:
   152  		return err
   153  	}
   154  }