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  }