github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/api/api.go (about) 1 // Package api provides low-level primitives for HTTP APIs. 2 package api 3 4 import ( 5 "bytes" 6 "encoding/json" 7 "io" 8 "io/ioutil" 9 "net" 10 "net/http" 11 "net/url" 12 "time" 13 14 "github.com/apex/log" 15 "github.com/pkg/errors" 16 ) 17 18 type errorResponse struct { 19 Error string `json:"error"` 20 } 21 22 var defaultClient = http.Client{ 23 Timeout: 60 * time.Second, 24 Transport: &http.Transport{ 25 DisableKeepAlives: true, 26 Proxy: http.ProxyFromEnvironment, 27 }, 28 } 29 30 // Get is a convenience method for MakeAPIRequest. 31 func Get(endpoint *url.URL, apiKey string, body []byte) (res string, statusCode int, err error) { 32 return stringAPIRequest(http.MethodGet, endpoint, apiKey, body) 33 } 34 35 // Post is a convenience method for MakeAPIRequest. 36 func Post(endpoint *url.URL, apiKey string, body []byte) (res string, statusCode int, err error) { 37 return stringAPIRequest(http.MethodPost, endpoint, apiKey, body) 38 } 39 40 // GetJSON is a convenience method for MakeAPIRequest. 41 func GetJSON(endpoint *url.URL, apiKey string, body []byte, v interface{}) (statusCode int, err error) { 42 return jsonAPIRequest(http.MethodGet, endpoint, apiKey, body, v) 43 } 44 45 // PostJSON is a convenience method for MakeAPIRequest. 46 func PostJSON(endpoint *url.URL, apiKey string, body []byte, v interface{}) (statusCode int, err error) { 47 return jsonAPIRequest(http.MethodPost, endpoint, apiKey, body, v) 48 } 49 50 func stringAPIRequest(method string, endpoint *url.URL, APIKey string, body []byte) (string, int, error) { 51 res, code, err := MakeAPIRequest(method, endpoint, APIKey, body) 52 if err != nil { 53 return "", code, err 54 } 55 return string(res), code, nil 56 } 57 58 func jsonAPIRequest(method string, endpoint *url.URL, APIKey string, body []byte, v interface{}) (int, error) { 59 res, code, err := MakeAPIRequest(method, endpoint, APIKey, body) 60 if err != nil { 61 return code, err 62 } 63 err = json.Unmarshal(res, v) 64 if err != nil { 65 return code, errors.Wrap(err, "could not unmarshal JSON API response") 66 } 67 return code, nil 68 } 69 70 func isTimeout(err error) bool { 71 switch e := err.(type) { 72 case net.Error: 73 return e.Timeout() 74 case *url.Error: 75 return e.Err == io.EOF 76 } 77 return false 78 } 79 80 // TimeoutError is an error caused by an HTTP request timeout. 81 type TimeoutError error 82 83 // MakeAPIRequest runs and logs a request backed by a default `http.Client`. 84 func MakeAPIRequest(method string, endpoint *url.URL, APIKey string, body []byte) (res []byte, statusCode int, err error) { 85 log.WithFields(log.Fields{ 86 "endpoint": *endpoint, 87 "method": method, 88 "API key": APIKey, 89 "body": string(body), 90 }).Debug("making API request") 91 92 // Construct request. 93 req, err := http.NewRequest(method, endpoint.String(), bytes.NewReader(body)) 94 if err != nil { 95 return nil, 0, errors.Wrap(err, "could not construct API HTTP request") 96 } 97 req.Close = true 98 req.Header.Set("Authorization", "token "+APIKey) 99 req.Header.Set("Content-Type", "application/json") 100 101 // Send request. 102 response, err := defaultClient.Do(req) 103 if err != nil { 104 if isTimeout(err) { 105 return nil, 0, TimeoutError(errors.Wrap(err, "API request timed out")) 106 } 107 return nil, 0, errors.Wrap(err, "could not send API HTTP request") 108 } 109 defer response.Body.Close() 110 111 // Read request. 112 res, err = ioutil.ReadAll(response.Body) 113 if err != nil { 114 return nil, 0, errors.Wrap(err, "could not read API HTTP response") 115 } 116 log.WithField("response", string(res)).Debug("got API response") 117 118 apiError := errorResponse{} 119 err = json.Unmarshal(res, &apiError) 120 if apiError.Error != "" && err == nil { 121 return res, response.StatusCode, errors.New(apiError.Error) 122 } 123 124 return res, response.StatusCode, nil 125 }