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  }