github.com/LukasHeimann/cloudfoundrycli@v7.1.0+incompatible/api/cloudcontroller/ccv3/requester.go (about)

     1  package ccv3
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"runtime"
     9  
    10  	"code.cloudfoundry.org/cli/api/cloudcontroller"
    11  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/internal"
    12  )
    13  
    14  //go:generate counterfeiter . Requester
    15  
    16  type RequestParams struct {
    17  	RequestName    string
    18  	URIParams      internal.Params
    19  	Query          []Query
    20  	RequestBody    interface{}
    21  	RequestHeaders [][]string
    22  	ResponseBody   interface{}
    23  	URL            string
    24  	AppendToList   func(item interface{}) error
    25  }
    26  
    27  type Requester interface {
    28  	InitializeConnection(settings TargetSettings)
    29  
    30  	InitializeRouter(resources map[string]string)
    31  
    32  	MakeListRequest(requestParams RequestParams) (IncludedResources, Warnings, error)
    33  
    34  	MakeRequest(requestParams RequestParams) (JobURL, Warnings, error)
    35  
    36  	MakeRequestReceiveRaw(
    37  		requestName string,
    38  		uriParams internal.Params,
    39  		responseBodyMimeType string,
    40  	) ([]byte, Warnings, error)
    41  
    42  	MakeRequestSendRaw(
    43  		requestName string,
    44  		uriParams internal.Params,
    45  		requestBody []byte,
    46  		requestBodyMimeType string,
    47  		responseBody interface{},
    48  	) (string, Warnings, error)
    49  
    50  	MakeRequestUploadAsync(
    51  		requestName string,
    52  		uriParams internal.Params,
    53  		requestBodyMimeType string,
    54  		requestBody io.ReadSeeker,
    55  		dataLength int64,
    56  		responseBody interface{},
    57  		writeErrors <-chan error,
    58  	) (string, Warnings, error)
    59  
    60  	WrapConnection(wrapper ConnectionWrapper)
    61  }
    62  
    63  type RealRequester struct {
    64  	connection cloudcontroller.Connection
    65  	router     *internal.Router
    66  	userAgent  string
    67  	wrappers   []ConnectionWrapper
    68  }
    69  
    70  func (requester *RealRequester) InitializeConnection(settings TargetSettings) {
    71  	requester.connection = cloudcontroller.NewConnection(cloudcontroller.Config{
    72  		DialTimeout:       settings.DialTimeout,
    73  		SkipSSLValidation: settings.SkipSSLValidation,
    74  	})
    75  
    76  	for _, wrapper := range requester.wrappers {
    77  		requester.connection = wrapper.Wrap(requester.connection)
    78  	}
    79  }
    80  
    81  func (requester *RealRequester) InitializeRouter(resources map[string]string) {
    82  	requester.router = internal.NewRouter(internal.APIRoutes, resources)
    83  }
    84  
    85  func (requester *RealRequester) MakeListRequest(requestParams RequestParams) (IncludedResources, Warnings, error) {
    86  	request, err := requester.buildRequest(requestParams)
    87  	if err != nil {
    88  		return IncludedResources{}, nil, err
    89  	}
    90  
    91  	return requester.paginate(request, requestParams.ResponseBody, requestParams.AppendToList)
    92  }
    93  
    94  func (requester *RealRequester) MakeRequest(requestParams RequestParams) (JobURL, Warnings, error) {
    95  	request, err := requester.buildRequest(requestParams)
    96  	if err != nil {
    97  		return "", nil, err
    98  	}
    99  
   100  	response := cloudcontroller.Response{}
   101  	if requestParams.ResponseBody != nil {
   102  		response.DecodeJSONResponseInto = requestParams.ResponseBody
   103  	}
   104  
   105  	err = requester.connection.Make(request, &response)
   106  
   107  	return JobURL(response.ResourceLocationURL), response.Warnings, err
   108  }
   109  
   110  func (requester *RealRequester) MakeRequestReceiveRaw(
   111  	requestName string,
   112  	uriParams internal.Params,
   113  	responseBodyMimeType string,
   114  ) ([]byte, Warnings, error) {
   115  	request, err := requester.newHTTPRequest(requestOptions{
   116  		RequestName: requestName,
   117  		URIParams:   uriParams,
   118  	})
   119  	if err != nil {
   120  		return nil, nil, err
   121  	}
   122  
   123  	response := cloudcontroller.Response{}
   124  
   125  	request.Header.Set("Accept", responseBodyMimeType)
   126  
   127  	err = requester.connection.Make(request, &response)
   128  
   129  	return response.RawResponse, response.Warnings, err
   130  }
   131  
   132  func (requester *RealRequester) MakeRequestSendRaw(
   133  	requestName string,
   134  	uriParams internal.Params,
   135  	requestBody []byte,
   136  	requestBodyMimeType string,
   137  	responseBody interface{},
   138  ) (string, Warnings, error) {
   139  	request, err := requester.newHTTPRequest(requestOptions{
   140  		RequestName: requestName,
   141  		URIParams:   uriParams,
   142  		Body:        bytes.NewReader(requestBody),
   143  	})
   144  	if err != nil {
   145  		return "", nil, err
   146  	}
   147  
   148  	request.Header.Set("Content-type", requestBodyMimeType)
   149  
   150  	response := cloudcontroller.Response{
   151  		DecodeJSONResponseInto: responseBody,
   152  	}
   153  
   154  	err = requester.connection.Make(request, &response)
   155  
   156  	return response.ResourceLocationURL, response.Warnings, err
   157  }
   158  
   159  func (requester *RealRequester) MakeRequestUploadAsync(
   160  	requestName string,
   161  	uriParams internal.Params,
   162  	requestBodyMimeType string,
   163  	requestBody io.ReadSeeker,
   164  	dataLength int64,
   165  	responseBody interface{},
   166  	writeErrors <-chan error,
   167  ) (string, Warnings, error) {
   168  	request, err := requester.newHTTPRequest(requestOptions{
   169  		RequestName: requestName,
   170  		URIParams:   uriParams,
   171  		Body:        requestBody,
   172  	})
   173  	if err != nil {
   174  		return "", nil, err
   175  	}
   176  
   177  	request.Header.Set("Content-Type", requestBodyMimeType)
   178  	request.ContentLength = dataLength
   179  
   180  	return requester.uploadAsynchronously(request, responseBody, writeErrors)
   181  }
   182  
   183  func NewRequester(config Config) *RealRequester {
   184  	userAgent := fmt.Sprintf(
   185  		"%s/%s (%s; %s %s)",
   186  		config.AppName,
   187  		config.AppVersion,
   188  		runtime.Version(),
   189  		runtime.GOARCH,
   190  		runtime.GOOS,
   191  	)
   192  
   193  	return &RealRequester{
   194  		userAgent: userAgent,
   195  		wrappers:  append([]ConnectionWrapper{newErrorWrapper()}, config.Wrappers...),
   196  	}
   197  }
   198  
   199  func (requester *RealRequester) buildRequest(requestParams RequestParams) (*cloudcontroller.Request, error) {
   200  	options := requestOptions{
   201  		RequestName: requestParams.RequestName,
   202  		URIParams:   requestParams.URIParams,
   203  		Query:       requestParams.Query,
   204  		URL:         requestParams.URL,
   205  	}
   206  
   207  	if requestParams.RequestBody != nil {
   208  		body, err := json.Marshal(requestParams.RequestBody)
   209  		if err != nil {
   210  			return nil, err
   211  		}
   212  
   213  		options.Body = bytes.NewReader(body)
   214  	}
   215  
   216  	request, err := requester.newHTTPRequest(options)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  
   221  	return request, err
   222  }
   223  
   224  func (requester *RealRequester) uploadAsynchronously(request *cloudcontroller.Request, responseBody interface{}, writeErrors <-chan error) (string, Warnings, error) {
   225  	response := cloudcontroller.Response{
   226  		DecodeJSONResponseInto: responseBody,
   227  	}
   228  
   229  	httpErrors := make(chan error)
   230  
   231  	go func() {
   232  		defer close(httpErrors)
   233  
   234  		err := requester.connection.Make(request, &response)
   235  		if err != nil {
   236  			httpErrors <- err
   237  		}
   238  	}()
   239  
   240  	// The following section makes the following assumptions:
   241  	// 1) If an error occurs during file reading, an EOF is sent to the request
   242  	// object. Thus ending the request transfer.
   243  	// 2) If an error occurs during request transfer, an EOF is sent to the pipe.
   244  	// Thus ending the writing routine.
   245  	var firstError error
   246  	var writeClosed, httpClosed bool
   247  
   248  	for {
   249  		select {
   250  		case writeErr, ok := <-writeErrors:
   251  			if !ok {
   252  				writeClosed = true
   253  				break // for select
   254  			}
   255  			if firstError == nil {
   256  				firstError = writeErr
   257  			}
   258  		case httpErr, ok := <-httpErrors:
   259  			if !ok {
   260  				httpClosed = true
   261  				break // for select
   262  			}
   263  			if firstError == nil {
   264  				firstError = httpErr
   265  			}
   266  		}
   267  
   268  		if writeClosed && httpClosed {
   269  			break // for for
   270  		}
   271  	}
   272  
   273  	return response.ResourceLocationURL, response.Warnings, firstError
   274  }