github.com/jenspinney/cli@v6.42.1-0.20190207184520-7450c600020e+incompatible/api/router/router_connection.go (about) 1 package router 2 3 import ( 4 "bytes" 5 "crypto/tls" 6 "crypto/x509" 7 "encoding/json" 8 "io/ioutil" 9 "net" 10 "net/http" 11 "net/url" 12 "time" 13 14 "code.cloudfoundry.org/cli/api/cloudcontroller/ccerror" 15 "code.cloudfoundry.org/cli/api/router/routererror" 16 ) 17 18 // ConnectionConfig is for configuring the RouterConnection 19 type ConnectionConfig struct { 20 DialTimeout time.Duration 21 SkipSSLValidation bool 22 } 23 24 // RouterConnection represents the connection to Router 25 type RouterConnection struct { 26 HTTPClient *http.Client 27 } 28 29 // NewConnection returns a pointer to a new RouterConnection with the provided configuration 30 func NewConnection(config ConnectionConfig) *RouterConnection { 31 tr := &http.Transport{ 32 TLSClientConfig: &tls.Config{ 33 InsecureSkipVerify: config.SkipSSLValidation, 34 }, 35 Proxy: http.ProxyFromEnvironment, 36 DialContext: (&net.Dialer{ 37 KeepAlive: 30 * time.Second, 38 Timeout: config.DialTimeout, 39 }).DialContext, 40 } 41 42 return &RouterConnection{ 43 HTTPClient: &http.Client{Transport: tr}, 44 } 45 } 46 47 // Make performs the request and parses the response. 48 func (connection *RouterConnection) Make(request *Request, responseToPopulate *Response) error { 49 // In case this function is called from a retry, passedResponse may already 50 // be populated with a previous response. We reset in case there's an HTTP 51 // error and we don't repopulate it in populateResponse. 52 responseToPopulate.reset() 53 54 httpResponse, err := connection.HTTPClient.Do(request.Request) 55 if err != nil { 56 // request could not be made, e.g., ssl handshake or tcp dial timeout 57 return connection.processRequestErrors(request.Request, err) 58 } 59 60 return connection.populateResponse(httpResponse, responseToPopulate) 61 } 62 63 func (*RouterConnection) handleStatusCodes(httpResponse *http.Response, responseToPopulate *Response) error { 64 if httpResponse.StatusCode >= 400 { 65 return routererror.RawHTTPStatusError{ 66 StatusCode: httpResponse.StatusCode, 67 RawResponse: responseToPopulate.RawResponse, 68 } 69 } 70 return nil 71 } 72 73 func (connection *RouterConnection) populateResponse(httpResponse *http.Response, responseToPopulate *Response) error { 74 responseToPopulate.HTTPResponse = httpResponse 75 76 rawBytes, err := ioutil.ReadAll(httpResponse.Body) 77 defer httpResponse.Body.Close() 78 if err != nil { 79 return err 80 } 81 responseToPopulate.RawResponse = rawBytes 82 83 err = connection.handleStatusCodes(httpResponse, responseToPopulate) 84 if err != nil { 85 return err 86 } 87 88 if responseToPopulate.Result != nil { 89 decoder := json.NewDecoder(bytes.NewBuffer(responseToPopulate.RawResponse)) 90 decoder.UseNumber() 91 err = decoder.Decode(responseToPopulate.Result) 92 if err != nil { 93 return err 94 } 95 } 96 97 return nil 98 } 99 100 func (*RouterConnection) processRequestErrors(request *http.Request, err error) error { 101 switch e := err.(type) { 102 case *url.Error: 103 switch urlErr := e.Err.(type) { 104 case x509.UnknownAuthorityError: 105 return ccerror.UnverifiedServerError{ 106 URL: request.URL.String(), 107 } 108 case x509.HostnameError: 109 return ccerror.SSLValidationHostnameError{ 110 Message: urlErr.Error(), 111 } 112 default: 113 return ccerror.RequestError{Err: e} 114 } 115 default: 116 return err 117 } 118 }