github.com/orange-cloudfoundry/cli@v7.1.0+incompatible/api/cloudcontroller/cloud_controller_connection.go (about)

     1  package cloudcontroller
     2  
     3  import (
     4  	"crypto/x509"
     5  	"io/ioutil"
     6  	"net"
     7  	"net/http"
     8  	"net/url"
     9  	"strings"
    10  	"time"
    11  
    12  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    13  	"code.cloudfoundry.org/cli/util"
    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: util.NewTLSConfig(nil, config.SkipSSLValidation),
    34  		Proxy:           http.ProxyFromEnvironment,
    35  		DialContext: (&net.Dialer{
    36  			KeepAlive: 30 * time.Second,
    37  			Timeout:   config.DialTimeout,
    38  		}).DialContext,
    39  	}
    40  
    41  	return &CloudControllerConnection{
    42  		HTTPClient: &http.Client{Transport: tr},
    43  	}
    44  }
    45  
    46  // Make performs the request and parses the response.
    47  func (connection *CloudControllerConnection) Make(request *Request, passedResponse *Response) error {
    48  	// In case this function is called from a retry, passedResponse may already
    49  	// be populated with a previous response. We reset in case there's an HTTP
    50  	// error and we don't repopulate it in populateResponse.
    51  	passedResponse.reset()
    52  
    53  	response, err := connection.HTTPClient.Do(request.Request)
    54  	if err != nil {
    55  		return connection.processRequestErrors(request.Request, err)
    56  	}
    57  
    58  	return connection.populateResponse(response, passedResponse)
    59  }
    60  
    61  func (*CloudControllerConnection) handleStatusCodes(response *http.Response, passedResponse *Response) error {
    62  	if response.StatusCode == http.StatusNoContent {
    63  		passedResponse.RawResponse = []byte("{}")
    64  	} else {
    65  		rawBytes, err := ioutil.ReadAll(response.Body)
    66  		defer response.Body.Close()
    67  		if err != nil {
    68  			return err
    69  		}
    70  
    71  		passedResponse.RawResponse = rawBytes
    72  	}
    73  
    74  	if response.StatusCode >= 400 {
    75  		return ccerror.RawHTTPStatusError{
    76  			StatusCode:  response.StatusCode,
    77  			RawResponse: passedResponse.RawResponse,
    78  			RequestIDs:  response.Header["X-Vcap-Request-Id"],
    79  		}
    80  	}
    81  
    82  	return nil
    83  }
    84  
    85  // handleWarnings looks for the "X-Cf-Warnings" header in the cloud controller
    86  // response and URI decodes them. The value can contain multiple warnings that
    87  // are comma separated.
    88  func (*CloudControllerConnection) handleWarnings(response *http.Response) ([]string, error) {
    89  	rawWarnings := response.Header["X-Cf-Warnings"]
    90  
    91  	var warnings []string
    92  	for _, rawWarningsCommaSeparated := range rawWarnings {
    93  		for _, rawWarning := range strings.Split(rawWarningsCommaSeparated, ",") {
    94  			warning, err := url.QueryUnescape(rawWarning)
    95  			if err != nil {
    96  				return nil, err
    97  			}
    98  			warnings = append(warnings, strings.Trim(warning, " "))
    99  		}
   100  	}
   101  
   102  	return warnings, nil
   103  }
   104  
   105  func (connection *CloudControllerConnection) populateResponse(response *http.Response, passedResponse *Response) error {
   106  	passedResponse.HTTPResponse = response
   107  
   108  	warnings, err := connection.handleWarnings(response)
   109  	if err != nil {
   110  		return err
   111  	}
   112  	passedResponse.Warnings = warnings
   113  
   114  	if resourceLocationURL := response.Header.Get("Location"); resourceLocationURL != "" {
   115  		passedResponse.ResourceLocationURL = resourceLocationURL
   116  	}
   117  
   118  	err = connection.handleStatusCodes(response, passedResponse)
   119  	if err != nil {
   120  		return err
   121  	}
   122  
   123  	if passedResponse.DecodeJSONResponseInto != nil {
   124  		err = DecodeJSON(passedResponse.RawResponse, passedResponse.DecodeJSONResponseInto)
   125  		if err != nil {
   126  			return err
   127  		}
   128  	}
   129  
   130  	return nil
   131  }
   132  
   133  func (*CloudControllerConnection) processRequestErrors(request *http.Request, err error) error {
   134  	switch e := err.(type) {
   135  	case *url.Error:
   136  		switch urlErr := e.Err.(type) {
   137  		case x509.UnknownAuthorityError:
   138  			return ccerror.UnverifiedServerError{
   139  				URL: request.URL.String(),
   140  			}
   141  		case x509.HostnameError:
   142  			return ccerror.SSLValidationHostnameError{
   143  				Message: urlErr.Error(),
   144  			}
   145  		default:
   146  			return ccerror.RequestError{Err: e}
   147  		}
   148  	default:
   149  		return err
   150  	}
   151  }