github.com/asifdxtreme/cli@v6.1.3-0.20150123051144-9ead8700b4ae+incompatible/cf/net/gateway.go (about) 1 package net 2 3 import ( 4 "bytes" 5 "crypto/tls" 6 "encoding/json" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "net" 11 "net/http" 12 "net/url" 13 "os" 14 "runtime" 15 "strings" 16 "time" 17 18 "github.com/cloudfoundry/cli/cf" 19 "github.com/cloudfoundry/cli/cf/configuration/core_config" 20 "github.com/cloudfoundry/cli/cf/errors" 21 . "github.com/cloudfoundry/cli/cf/i18n" 22 "github.com/cloudfoundry/cli/cf/terminal" 23 ) 24 25 const ( 26 JOB_FINISHED = "finished" 27 JOB_FAILED = "failed" 28 DEFAULT_POLLING_THROTTLE = 5 * time.Second 29 ) 30 31 type JobResource struct { 32 Entity struct { 33 Status string 34 ErrorDetails struct { 35 Description string 36 } `json:"error_details"` 37 } 38 } 39 40 type AsyncResource struct { 41 Metadata struct { 42 URL string 43 } 44 } 45 46 type apiErrorHandler func(statusCode int, body []byte) error 47 48 type tokenRefresher interface { 49 RefreshAuthToken() (string, error) 50 } 51 52 type Request struct { 53 HttpReq *http.Request 54 SeekableBody io.ReadSeeker 55 } 56 57 type Gateway struct { 58 authenticator tokenRefresher 59 errHandler apiErrorHandler 60 PollingEnabled bool 61 PollingThrottle time.Duration 62 trustedCerts []tls.Certificate 63 config core_config.Reader 64 warnings *[]string 65 Clock func() time.Time 66 transport *http.Transport 67 ui terminal.UI 68 } 69 70 func newGateway(errHandler apiErrorHandler, config core_config.Reader, ui terminal.UI) (gateway Gateway) { 71 gateway.errHandler = errHandler 72 gateway.config = config 73 gateway.PollingThrottle = DEFAULT_POLLING_THROTTLE 74 gateway.warnings = &[]string{} 75 gateway.Clock = time.Now 76 gateway.ui = ui 77 78 return 79 } 80 81 func (gateway *Gateway) AsyncTimeout() time.Duration { 82 if gateway.config.AsyncTimeout() > 0 { 83 return time.Duration(gateway.config.AsyncTimeout()) * time.Minute 84 } 85 86 return 0 87 } 88 89 func (gateway *Gateway) SetTokenRefresher(auth tokenRefresher) { 90 gateway.authenticator = auth 91 } 92 93 func (gateway Gateway) GetResource(url string, resource interface{}) (err error) { 94 request, err := gateway.NewRequest("GET", url, gateway.config.AccessToken(), nil) 95 if err != nil { 96 return 97 } 98 99 _, err = gateway.PerformRequestForJSONResponse(request, resource) 100 return 101 } 102 103 func (gateway Gateway) CreateResourceFromStruct(endpoint, url string, resource interface{}) error { 104 bytes, err := json.Marshal(resource) 105 if err != nil { 106 return err 107 } 108 109 return gateway.CreateResource(endpoint, url, strings.NewReader(string(bytes))) 110 } 111 112 func (gateway Gateway) UpdateResourceFromStruct(endpoint, apiUrl string, resource interface{}) error { 113 bytes, err := json.Marshal(resource) 114 if err != nil { 115 return err 116 } 117 118 return gateway.UpdateResource(endpoint, apiUrl, strings.NewReader(string(bytes))) 119 } 120 121 func (gateway Gateway) CreateResource(endpoint, apiUrl string, body io.ReadSeeker, resource ...interface{}) (apiErr error) { 122 return gateway.createUpdateOrDeleteResource("POST", endpoint, apiUrl, body, false, resource...) 123 } 124 125 func (gateway Gateway) UpdateResource(endpoint, apiUrl string, body io.ReadSeeker, resource ...interface{}) (apiErr error) { 126 return gateway.createUpdateOrDeleteResource("PUT", endpoint, apiUrl, body, false, resource...) 127 } 128 129 func (gateway Gateway) UpdateResourceSync(endpoint, apiUrl string, body io.ReadSeeker, resource ...interface{}) (apiErr error) { 130 return gateway.createUpdateOrDeleteResource("PUT", endpoint, apiUrl, body, true, resource...) 131 } 132 133 func (gateway Gateway) DeleteResource(endpoint, apiUrl string) (apiErr error) { 134 return gateway.createUpdateOrDeleteResource("DELETE", endpoint, apiUrl, nil, false, &AsyncResource{}) 135 } 136 137 func (gateway Gateway) ListPaginatedResources(target string, 138 path string, 139 resource interface{}, 140 cb func(interface{}) bool) (apiErr error) { 141 for path != "" { 142 pagination := NewPaginatedResources(resource) 143 144 apiErr = gateway.GetResource(fmt.Sprintf("%s%s", target, path), &pagination) 145 if apiErr != nil { 146 return 147 } 148 149 resources, err := pagination.Resources() 150 if err != nil { 151 return errors.NewWithError(T("Error parsing JSON"), err) 152 } 153 154 for _, resource := range resources { 155 if !cb(resource) { 156 return 157 } 158 } 159 160 path = pagination.NextURL 161 } 162 163 return 164 } 165 166 func (gateway Gateway) createUpdateOrDeleteResource(verb, endpoint, apiUrl string, body io.ReadSeeker, sync bool, optionalResource ...interface{}) (apiErr error) { 167 var resource interface{} 168 if len(optionalResource) > 0 { 169 resource = optionalResource[0] 170 } 171 172 request, apiErr := gateway.NewRequest(verb, endpoint+apiUrl, gateway.config.AccessToken(), body) 173 if apiErr != nil { 174 return 175 } 176 177 if resource == nil { 178 _, apiErr = gateway.PerformRequest(request) 179 return 180 } 181 182 if gateway.PollingEnabled && !sync { 183 _, apiErr = gateway.PerformPollingRequestForJSONResponse(endpoint, request, resource, gateway.AsyncTimeout()) 184 return 185 } else { 186 _, apiErr = gateway.PerformRequestForJSONResponse(request, resource) 187 return 188 } 189 190 } 191 192 func (gateway Gateway) newRequest(request *http.Request, accessToken string, body io.ReadSeeker) (*Request, error) { 193 if accessToken != "" { 194 request.Header.Set("Authorization", accessToken) 195 } 196 197 request.Header.Set("accept", "application/json") 198 request.Header.Set("content-type", "application/json") 199 request.Header.Set("User-Agent", "go-cli "+cf.Version+" / "+runtime.GOOS) 200 return &Request{HttpReq: request, SeekableBody: body}, nil 201 } 202 203 func (gateway Gateway) NewRequestForFile(method, fullUrl, accessToken string, body *os.File) (req *Request, apiErr error) { 204 progressReader := NewProgressReader(body, gateway.ui, 5*time.Second) 205 progressReader.Seek(0, 0) 206 fileStats, err := body.Stat() 207 208 if err != nil { 209 apiErr = errors.NewWithError(T("Error getting file info"), err) 210 return 211 } 212 213 request, err := http.NewRequest(method, fullUrl, progressReader) 214 if err != nil { 215 apiErr = errors.NewWithError(T("Error building request"), err) 216 return 217 } 218 219 fileSize := fileStats.Size() 220 progressReader.SetTotalSize(fileSize) 221 request.ContentLength = fileSize 222 223 if err != nil { 224 apiErr = errors.NewWithError(T("Error building request"), err) 225 return 226 } 227 228 return gateway.newRequest(request, accessToken, progressReader) 229 } 230 231 func (gateway Gateway) NewRequest(method, path, accessToken string, body io.ReadSeeker) (req *Request, apiErr error) { 232 request, err := http.NewRequest(method, path, body) 233 if err != nil { 234 apiErr = errors.NewWithError(T("Error building request"), err) 235 return 236 } 237 return gateway.newRequest(request, accessToken, body) 238 } 239 240 func (gateway Gateway) PerformRequest(request *Request) (rawResponse *http.Response, apiErr error) { 241 return gateway.doRequestHandlingAuth(request) 242 } 243 244 func (gateway Gateway) performRequestForResponseBytes(request *Request) (bytes []byte, headers http.Header, rawResponse *http.Response, apiErr error) { 245 rawResponse, apiErr = gateway.doRequestHandlingAuth(request) 246 if apiErr != nil { 247 return 248 } 249 defer rawResponse.Body.Close() 250 251 bytes, err := ioutil.ReadAll(rawResponse.Body) 252 if err != nil { 253 apiErr = errors.NewWithError(T("Error reading response"), err) 254 } 255 256 headers = rawResponse.Header 257 return 258 } 259 260 func (gateway Gateway) PerformRequestForTextResponse(request *Request) (response string, headers http.Header, apiErr error) { 261 bytes, headers, _, apiErr := gateway.performRequestForResponseBytes(request) 262 response = string(bytes) 263 return 264 } 265 266 func (gateway Gateway) PerformRequestForJSONResponse(request *Request, response interface{}) (headers http.Header, apiErr error) { 267 bytes, headers, rawResponse, apiErr := gateway.performRequestForResponseBytes(request) 268 if apiErr != nil { 269 return 270 } 271 272 if rawResponse.StatusCode > 203 || strings.TrimSpace(string(bytes)) == "" { 273 return 274 } 275 276 err := json.Unmarshal(bytes, &response) 277 if err != nil { 278 apiErr = errors.NewWithError(T("Invalid JSON response from server"), err) 279 } 280 return 281 } 282 283 func (gateway Gateway) PerformPollingRequestForJSONResponse(endpoint string, request *Request, response interface{}, timeout time.Duration) (headers http.Header, apiErr error) { 284 query := request.HttpReq.URL.Query() 285 query.Add("async", "true") 286 request.HttpReq.URL.RawQuery = query.Encode() 287 288 bytes, headers, rawResponse, apiErr := gateway.performRequestForResponseBytes(request) 289 if apiErr != nil { 290 return 291 } 292 defer rawResponse.Body.Close() 293 294 if rawResponse.StatusCode > 203 || strings.TrimSpace(string(bytes)) == "" { 295 return 296 } 297 298 err := json.Unmarshal(bytes, &response) 299 if err != nil { 300 apiErr = errors.NewWithError(T("Invalid JSON response from server"), err) 301 return 302 } 303 304 asyncResource := &AsyncResource{} 305 err = json.Unmarshal(bytes, &asyncResource) 306 if err != nil { 307 apiErr = errors.NewWithError(T("Invalid async response from server"), err) 308 return 309 } 310 311 jobUrl := asyncResource.Metadata.URL 312 if jobUrl == "" { 313 return 314 } 315 316 if !strings.Contains(jobUrl, "/jobs/") { 317 return 318 } 319 320 apiErr = gateway.waitForJob(endpoint+jobUrl, request.HttpReq.Header.Get("Authorization"), timeout) 321 322 return 323 } 324 325 func (gateway Gateway) Warnings() []string { 326 return *gateway.warnings 327 } 328 329 func (gateway Gateway) waitForJob(jobUrl, accessToken string, timeout time.Duration) (err error) { 330 startTime := gateway.Clock() 331 for true { 332 if gateway.Clock().Sub(startTime) > timeout && timeout != 0 { 333 return errors.NewAsyncTimeoutError(jobUrl) 334 } 335 var request *Request 336 request, err = gateway.NewRequest("GET", jobUrl, accessToken, nil) 337 response := &JobResource{} 338 _, err = gateway.PerformRequestForJSONResponse(request, response) 339 if err != nil { 340 return 341 } 342 343 switch response.Entity.Status { 344 case JOB_FINISHED: 345 return 346 case JOB_FAILED: 347 err = errors.New(response.Entity.ErrorDetails.Description) 348 return 349 } 350 351 accessToken = request.HttpReq.Header.Get("Authorization") 352 353 time.Sleep(gateway.PollingThrottle) 354 } 355 return 356 } 357 358 func (gateway Gateway) doRequestHandlingAuth(request *Request) (rawResponse *http.Response, err error) { 359 httpReq := request.HttpReq 360 361 if request.SeekableBody != nil { 362 httpReq.Body = ioutil.NopCloser(request.SeekableBody) 363 } 364 365 // perform request 366 rawResponse, err = gateway.doRequestAndHandlerError(request) 367 if err == nil || gateway.authenticator == nil { 368 return 369 } 370 371 switch err.(type) { 372 case *errors.InvalidTokenError: 373 // refresh the auth token 374 var newToken string 375 newToken, err = gateway.authenticator.RefreshAuthToken() 376 if err != nil { 377 return 378 } 379 380 // reset the auth token and request body 381 httpReq.Header.Set("Authorization", newToken) 382 if request.SeekableBody != nil { 383 request.SeekableBody.Seek(0, 0) 384 httpReq.Body = ioutil.NopCloser(request.SeekableBody) 385 } 386 387 // make the request again 388 rawResponse, err = gateway.doRequestAndHandlerError(request) 389 } 390 391 return 392 } 393 394 func (gateway Gateway) doRequestAndHandlerError(request *Request) (rawResponse *http.Response, err error) { 395 rawResponse, err = gateway.doRequest(request.HttpReq) 396 if err != nil { 397 err = WrapNetworkErrors(request.HttpReq.URL.Host, err) 398 return 399 } 400 401 if rawResponse.StatusCode > 299 { 402 jsonBytes, _ := ioutil.ReadAll(rawResponse.Body) 403 rawResponse.Body.Close() 404 rawResponse.Body = ioutil.NopCloser(bytes.NewBuffer(jsonBytes)) 405 err = gateway.errHandler(rawResponse.StatusCode, jsonBytes) 406 } 407 408 return 409 } 410 411 func (gateway Gateway) doRequest(request *http.Request) (response *http.Response, err error) { 412 if gateway.transport == nil { 413 makeHttpTransport(&gateway) 414 } 415 416 httpClient := NewHttpClient(gateway.transport) 417 418 dumpRequest(request) 419 420 for i := 0; i < 3; i++ { 421 response, err = httpClient.Do(request) 422 if response == nil && err != nil { 423 continue 424 } else { 425 break 426 } 427 } 428 429 if err != nil { 430 return 431 } 432 433 dumpResponse(response) 434 435 header := http.CanonicalHeaderKey("X-Cf-Warnings") 436 raw_warnings := response.Header[header] 437 for _, raw_warning := range raw_warnings { 438 warning, _ := url.QueryUnescape(raw_warning) 439 *gateway.warnings = append(*gateway.warnings, warning) 440 } 441 442 return 443 } 444 445 func makeHttpTransport(gateway *Gateway) { 446 gateway.transport = &http.Transport{ 447 Dial: (&net.Dialer{Timeout: 5 * time.Second}).Dial, 448 TLSClientConfig: NewTLSConfig(gateway.trustedCerts, gateway.config.IsSSLDisabled()), 449 Proxy: http.ProxyFromEnvironment, 450 } 451 } 452 453 func (gateway *Gateway) SetTrustedCerts(certificates []tls.Certificate) { 454 gateway.trustedCerts = certificates 455 makeHttpTransport(gateway) 456 }