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