github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/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 specificPage := false 99 for _, query := range requestParams.Query { 100 if query.Key == Page { 101 specificPage = true 102 break 103 } 104 } 105 106 return requester.paginate(request, requestParams.ResponseBody, requestParams.AppendToList, specificPage) 107 } 108 109 func (requester *RealRequester) MakeRequest(requestParams RequestParams) (JobURL, Warnings, error) { 110 request, err := requester.buildRequest(requestParams) 111 if err != nil { 112 return "", nil, err 113 } 114 115 response := cloudcontroller.Response{} 116 if requestParams.ResponseBody != nil { 117 response.DecodeJSONResponseInto = requestParams.ResponseBody 118 } 119 120 err = requester.connection.Make(request, &response) 121 122 return JobURL(response.ResourceLocationURL), response.Warnings, err 123 } 124 125 func (requester *RealRequester) MakeRequestReceiveRaw( 126 requestName string, 127 uriParams internal.Params, 128 responseBodyMimeType string, 129 ) ([]byte, Warnings, error) { 130 request, err := requester.newHTTPRequest(requestOptions{ 131 RequestName: requestName, 132 URIParams: uriParams, 133 }) 134 if err != nil { 135 return nil, nil, err 136 } 137 138 response := cloudcontroller.Response{} 139 140 request.Header.Set("Accept", responseBodyMimeType) 141 142 err = requester.connection.Make(request, &response) 143 144 return response.RawResponse, response.Warnings, err 145 } 146 147 func (requester *RealRequester) MakeRequestSendReceiveRaw( 148 Method string, 149 URL string, 150 headers http.Header, 151 requestBody []byte, 152 ) ([]byte, *http.Response, error) { 153 request, err := requester.newHTTPRequest(requestOptions{ 154 URL: URL, 155 Method: Method, 156 Body: bytes.NewReader(requestBody), 157 Header: headers, 158 }) 159 if err != nil { 160 return nil, nil, err 161 } 162 163 response := cloudcontroller.Response{} 164 165 err = requester.connection.Make(request, &response) 166 167 return response.RawResponse, response.HTTPResponse, err 168 } 169 170 func (requester *RealRequester) MakeRequestSendRaw( 171 requestName string, 172 uriParams internal.Params, 173 requestBody []byte, 174 requestBodyMimeType string, 175 responseBody interface{}, 176 ) (string, Warnings, error) { 177 request, err := requester.newHTTPRequest(requestOptions{ 178 RequestName: requestName, 179 URIParams: uriParams, 180 Body: bytes.NewReader(requestBody), 181 }) 182 if err != nil { 183 return "", nil, err 184 } 185 186 request.Header.Set("Content-type", requestBodyMimeType) 187 188 response := cloudcontroller.Response{ 189 DecodeJSONResponseInto: responseBody, 190 } 191 192 err = requester.connection.Make(request, &response) 193 194 return response.ResourceLocationURL, response.Warnings, err 195 } 196 197 func (requester *RealRequester) MakeRequestUploadAsync( 198 requestName string, 199 uriParams internal.Params, 200 requestBodyMimeType string, 201 requestBody io.ReadSeeker, 202 dataLength int64, 203 responseBody interface{}, 204 writeErrors <-chan error, 205 ) (string, Warnings, error) { 206 request, err := requester.newHTTPRequest(requestOptions{ 207 RequestName: requestName, 208 URIParams: uriParams, 209 Body: requestBody, 210 }) 211 if err != nil { 212 return "", nil, err 213 } 214 215 request.Header.Set("Content-Type", requestBodyMimeType) 216 request.ContentLength = dataLength 217 218 return requester.uploadAsynchronously(request, responseBody, writeErrors) 219 } 220 221 func NewRequester(config Config) *RealRequester { 222 userAgent := fmt.Sprintf( 223 "%s/%s (%s; %s %s)", 224 config.AppName, 225 config.AppVersion, 226 runtime.Version(), 227 runtime.GOARCH, 228 runtime.GOOS, 229 ) 230 231 return &RealRequester{ 232 userAgent: userAgent, 233 wrappers: append([]ConnectionWrapper{newErrorWrapper()}, config.Wrappers...), 234 } 235 } 236 237 func (requester *RealRequester) buildRequest(requestParams RequestParams) (*cloudcontroller.Request, error) { 238 options := requestOptions{ 239 RequestName: requestParams.RequestName, 240 URIParams: requestParams.URIParams, 241 Query: requestParams.Query, 242 URL: requestParams.URL, 243 } 244 245 if requestParams.RequestBody != nil { 246 body, err := json.Marshal(requestParams.RequestBody) 247 if err != nil { 248 return nil, err 249 } 250 251 options.Body = bytes.NewReader(body) 252 } 253 254 request, err := requester.newHTTPRequest(options) 255 if err != nil { 256 return nil, err 257 } 258 259 return request, err 260 } 261 262 func (requester *RealRequester) uploadAsynchronously(request *cloudcontroller.Request, responseBody interface{}, writeErrors <-chan error) (string, Warnings, error) { 263 response := cloudcontroller.Response{ 264 DecodeJSONResponseInto: responseBody, 265 } 266 267 httpErrors := make(chan error) 268 269 go func() { 270 defer close(httpErrors) 271 272 err := requester.connection.Make(request, &response) 273 if err != nil { 274 httpErrors <- err 275 } 276 }() 277 278 // The following section makes the following assumptions: 279 // 1) If an error occurs during file reading, an EOF is sent to the request 280 // object. Thus ending the request transfer. 281 // 2) If an error occurs during request transfer, an EOF is sent to the pipe. 282 // Thus ending the writing routine. 283 var firstError error 284 var writeClosed, httpClosed bool 285 286 for { 287 select { 288 case writeErr, ok := <-writeErrors: 289 if !ok { 290 writeClosed = true 291 break // for select 292 } 293 if firstError == nil { 294 firstError = writeErr 295 } 296 case httpErr, ok := <-httpErrors: 297 if !ok { 298 httpClosed = true 299 break // for select 300 } 301 if firstError == nil { 302 firstError = httpErr 303 } 304 } 305 306 if writeClosed && httpClosed { 307 break // for for 308 } 309 } 310 311 return response.ResourceLocationURL, response.Warnings, firstError 312 }