github.com/apcera/util@v0.0.0-20180322191801-7a50bc84ee48/restclient/restclient.go (about)

     1  // Copyright 2013-2014 Apcera Inc. All rights reserved.
     2  
     3  // Package restclient wraps a REST-ful web service to expose objects from the
     4  // service in Go programs. Construct a client using
     5  // restclient.New("http://service.com/api/endpoint"). Use the client's HTTP-verb
     6  // methods to receive the result of REST operations in a Go type. For example,
     7  // to get a collection of Items, invoke client.Get("items", m) where m is of
     8  // type []Item.
     9  //
    10  // The package also exposes lower level interfaces to receive the raw
    11  // http.Response from the client and to construct requests to a client's service
    12  // that may be sent later, or by an alternate client or transport.
    13  package restclient
    14  
    15  import (
    16  	"bytes"
    17  	"encoding/json"
    18  	"fmt"
    19  	"io"
    20  	"io/ioutil"
    21  	"mime"
    22  	"net"
    23  	"net/http"
    24  	"net/url"
    25  	"path"
    26  	"strings"
    27  	"time"
    28  )
    29  
    30  // Method wraps HTTP verbs for stronger typing.
    31  type Method string
    32  
    33  // HTTP methods for REST
    34  const (
    35  	GET    = Method("GET")
    36  	POST   = Method("POST")
    37  	PUT    = Method("PUT")
    38  	DELETE = Method("DELETE")
    39  )
    40  
    41  const (
    42  	vndMediaTypePrefix  = "application/vnd"
    43  	mediaTypeJSONSuffix = "+json"
    44  )
    45  
    46  // Client represents a client bound to a given REST base URL.
    47  type Client struct {
    48  	// Driver is the *http.Client that performs requests.
    49  	Driver *http.Client
    50  	// base is the URL under which all REST-ful resources are available.
    51  	base *url.URL
    52  	// Headers represents common headers that are added to each request.
    53  	Headers http.Header
    54  	// KeepAlives enabled
    55  	KeepAlives bool
    56  }
    57  
    58  // New returns a *Client with the specified base URL endpoint, expected to
    59  // include the port string and any path, if required. Returns an error if
    60  // baseurl cannot be parsed as an absolute URL.
    61  func New(baseurl string) (*Client, error) {
    62  	base, err := url.ParseRequestURI(baseurl)
    63  	if err != nil {
    64  		return nil, err
    65  	} else if !base.IsAbs() || base.Host == "" {
    66  		return nil, fmt.Errorf("URL is not absolute: %s", baseurl)
    67  	}
    68  
    69  	// create the client
    70  	client := &Client{
    71  		Driver:     &http.Client{}, // Don't use default client; shares by reference
    72  		Headers:    http.Header(make(map[string][]string)),
    73  		base:       base,
    74  		KeepAlives: true,
    75  	}
    76  
    77  	return client, nil
    78  }
    79  
    80  // NewWithoutKeepAlives returns a new client with keepalives disabled.
    81  func NewDisableKeepAlives(baseurl string) (*Client, error) {
    82  	base, err := url.ParseRequestURI(baseurl)
    83  	if err != nil {
    84  		return nil, err
    85  	} else if !base.IsAbs() || base.Host == "" {
    86  		return nil, fmt.Errorf("URL is not absolute: %s", baseurl)
    87  	}
    88  
    89  	transport := http.DefaultTransport.(*http.Transport)
    90  	transport.DisableKeepAlives = true
    91  
    92  	// create the client
    93  	client := &Client{
    94  		Driver: &http.Client{
    95  			Transport: transport,
    96  		}, // Don't use default client; shares by reference
    97  		Headers:    http.Header(make(map[string][]string)),
    98  		base:       base,
    99  		KeepAlives: false,
   100  	}
   101  
   102  	return client, nil
   103  }
   104  
   105  // BaseURL returns a *url.URL to a copy of Client's base so the caller may
   106  // modify it.
   107  func (c *Client) BaseURL() *url.URL {
   108  	return c.base.ResolveReference(&url.URL{})
   109  }
   110  
   111  // Set the access Token
   112  func (c *Client) SetAccessToken(token string) {
   113  	c.Headers.Set(http.CanonicalHeaderKey("Authorization"), "Bearer "+token)
   114  }
   115  
   116  // SetTimeout sets the timeout of a client to the given duration.
   117  func (c *Client) SetTimeout(duration time.Duration) {
   118  	c.Driver.Timeout = duration
   119  }
   120  
   121  // Get issues a GET request to the specified endpoint and parses the response
   122  // into resp. It will return an error if it failed to send the request, a
   123  // *RestError if the response wasn't a 2xx status code, or an error from package
   124  // json's Decode.
   125  func (c *Client) Get(endpoint string, resp interface{}) error {
   126  	return c.Result(c.NewJsonRequest(GET, endpoint, nil), resp)
   127  }
   128  
   129  // Post issues a POST request to the specified endpoint with the req payload
   130  // marshaled to JSON and parses the response into resp. It will return an error
   131  // if it failed to send the request, a *RestError if the response wasn't a 2xx
   132  // status code, or an error from package json's Decode.
   133  func (c *Client) Post(endpoint string, req interface{}, resp interface{}) error {
   134  	return c.Result(c.NewJsonRequest(POST, endpoint, req), resp)
   135  }
   136  
   137  // Put issues a PUT request to the specified endpoint with the req payload
   138  // marshaled to JSON and parses the response into resp. It will return an error
   139  // if it failed to send the request, a *RestError if the response wasn't a 2xx
   140  // status code, or an error from package json's Decode.
   141  func (c *Client) Put(endpoint string, req interface{}, resp interface{}) error {
   142  	return c.Result(c.NewJsonRequest(PUT, endpoint, req), resp)
   143  }
   144  
   145  // Delete issues a DELETE request to the specified endpoint and parses the
   146  // response in to resp. It will return an error if it failed to send the request, a
   147  // *RestError if the response wasn't a 2xx status code, or an error from package
   148  // json's Decode.
   149  func (c *Client) Delete(endpoint string, resp interface{}) error {
   150  	return c.Result(c.NewJsonRequest(DELETE, endpoint, nil), resp)
   151  }
   152  
   153  // Result performs the request described by req and unmarshals a successful
   154  // HTTP response into resp. If resp is nil, the response is discarded.
   155  func (c *Client) Result(req *Request, resp interface{}) error {
   156  	result, err := c.Do(req)
   157  	if err != nil {
   158  		return err
   159  	}
   160  	return unmarshal(result, resp)
   161  }
   162  
   163  // Do performs the HTTP request described by req and returns the *http.Response.
   164  // Also returns a non-nil *RestError if an error occurs or the response is not
   165  // in the 2xx family.
   166  func (c *Client) Do(req *Request) (*http.Response, error) {
   167  	hreq, err := req.HTTPRequest()
   168  	if err != nil {
   169  		return nil, &RestError{Req: hreq, err: fmt.Errorf("error preparing request: %s", err)}
   170  	}
   171  
   172  	if !c.KeepAlives {
   173  		hreq.Close = true
   174  	}
   175  
   176  	// Internally, this uses c.Driver's CheckRedirect policy.
   177  	resp, err := c.Driver.Do(hreq)
   178  	if err != nil {
   179  		if opErr, ok := err.(*net.OpError); ok {
   180  			if opErr.Timeout() {
   181  				return nil, &RestError{Req: hreq, err: fmt.Errorf("timed out making request")}
   182  			}
   183  		}
   184  		return resp, &RestError{Req: hreq, Resp: resp, err: fmt.Errorf("error sending request: %s", err)}
   185  	}
   186  	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
   187  		return resp, &RestError{Req: hreq, Resp: resp, err: fmt.Errorf("error in response: %s", resp.Status)}
   188  	}
   189  	return resp, nil
   190  }
   191  
   192  // NewRequest generates a new Request object that will send bytes read from body
   193  // to the endpoint.
   194  func (c *Client) NewRequest(method Method, endpoint string, ctype string, body io.Reader) (req *Request) {
   195  	req = c.newRequest(method, endpoint)
   196  	if body == nil {
   197  		return
   198  	}
   199  
   200  	req.prepare = func(hr *http.Request) error {
   201  		rc, ok := body.(io.ReadCloser)
   202  		if !ok {
   203  			rc = ioutil.NopCloser(body)
   204  		}
   205  		hr.Body = rc
   206  		hr.Header.Set("Content-Type", ctype)
   207  		return nil
   208  	}
   209  	return
   210  }
   211  
   212  // NewJsonRequest generates a new Request object and JSON encodes the provided
   213  // obj. The JSON object will be set as the body and included in the request.
   214  func (c *Client) NewJsonRequest(method Method, endpoint string, obj interface{}) (req *Request) {
   215  	req = c.newRequest(method, endpoint)
   216  	if obj == nil {
   217  		return
   218  	}
   219  
   220  	req.prepare = func(httpReq *http.Request) error {
   221  		var buffer bytes.Buffer
   222  		encoder := json.NewEncoder(&buffer)
   223  		if err := encoder.Encode(obj); err != nil {
   224  			return err
   225  		}
   226  
   227  		// set to the request
   228  		httpReq.Body = ioutil.NopCloser(&buffer)
   229  		httpReq.ContentLength = int64(buffer.Len())
   230  		httpReq.Header.Set("Content-Type", "application/json")
   231  		return nil
   232  	}
   233  
   234  	return req
   235  }
   236  
   237  // NewFormRequest generates a new Request object with a form encoded body based
   238  // on the params map.
   239  func (c *Client) NewFormRequest(method Method, endpoint string, params map[string]string) *Request {
   240  	req := c.newRequest(method, endpoint)
   241  
   242  	// set how to generate the body
   243  	req.prepare = func(httpReq *http.Request) error {
   244  		form := url.Values{}
   245  		for k, v := range params {
   246  			form.Set(k, v)
   247  		}
   248  		encoded := form.Encode()
   249  
   250  		// set to the request
   251  		httpReq.Body = ioutil.NopCloser(bytes.NewReader([]byte(encoded)))
   252  		httpReq.ContentLength = int64(len(encoded))
   253  		httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
   254  		return nil
   255  	}
   256  
   257  	return req
   258  }
   259  
   260  // newRequest returns a *Request ready to be used by one of Client's exported
   261  // methods like NewFormRequest.
   262  func (c *Client) newRequest(method Method, endpoint string) *Request {
   263  	req := &Request{
   264  		Method:  method,
   265  		URL:     resourceURL(c.BaseURL(), endpoint),
   266  		Headers: http.Header(make(map[string][]string)),
   267  	}
   268  
   269  	// Copy over the headers. Don't set them directly to ensure changing
   270  	// them on the request doesn't change them on the client.
   271  	for k, vv := range c.Headers {
   272  		for _, v := range vv {
   273  			req.Headers.Add(k, v)
   274  		}
   275  	}
   276  
   277  	return req
   278  }
   279  
   280  // Request encapsulates functionality making it easier to build REST requests.
   281  type Request struct {
   282  	Method  Method
   283  	URL     *url.URL
   284  	Headers http.Header
   285  
   286  	prepare func(*http.Request) error
   287  }
   288  
   289  // HTTPRequest returns an *http.Request populated with data from r. It may be
   290  // executed by any http.Client.
   291  func (r *Request) HTTPRequest() (*http.Request, error) {
   292  	req, err := http.NewRequest(string(r.Method), r.URL.String(), nil)
   293  	if err != nil {
   294  		return nil, err
   295  	}
   296  
   297  	// merge headers
   298  	req.Header = r.Headers
   299  
   300  	// generate the body
   301  	if r.prepare != nil {
   302  		if err := r.prepare(req); err != nil {
   303  			return nil, err
   304  		}
   305  	}
   306  
   307  	return req, nil
   308  }
   309  
   310  // resourceURL returns a *url.URL with the path resolved for a resource under base.
   311  func resourceURL(base *url.URL, relPath string) *url.URL {
   312  	relPath, rawQuery := splitPathQuery(relPath)
   313  	ref := &url.URL{Path: path.Join(base.Path, relPath), RawQuery: rawQuery}
   314  	return base.ResolveReference(ref)
   315  }
   316  
   317  func splitPathQuery(relPath string) (retPath, rawQuery string) {
   318  	parsedPath, _ := url.Parse(relPath)
   319  	rawQuery = parsedPath.RawQuery
   320  	retPath = strings.TrimSuffix(relPath, fmt.Sprintf("?%s", rawQuery))
   321  	return
   322  }
   323  
   324  // unmarshal unmarshals a JSON object from the response object's body. If the
   325  // Content-Type is not application/json or application/vnd* (or we can't detect
   326  // the media type) an error is returned. The response body is always closed.
   327  func unmarshal(resp *http.Response, v interface{}) error {
   328  	defer resp.Body.Close()
   329  
   330  	// Don't Unmarshal Body if v is nil
   331  	if v == nil {
   332  		return nil
   333  	}
   334  
   335  	ctype, _, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
   336  	if err != nil {
   337  		return err
   338  	}
   339  
   340  	if !isJSONContentType(ctype) {
   341  		return fmt.Errorf("unexpected response: %s %s", resp.Status, ctype)
   342  	}
   343  
   344  	return json.NewDecoder(resp.Body).Decode(v)
   345  }
   346  
   347  // isJSONContentType returns whether or not the media type should be expected to
   348  // contain JSON. Explicit application/json as well as types of the form
   349  // application/vnd* and *+json are permitted. The simple checks prevent us from
   350  // running http.DetectContentType on every request or trying to decode things
   351  // that are clearly not JSON.
   352  func isJSONContentType(mediaType string) bool {
   353  	if mediaType == "application/json" ||
   354  		strings.HasPrefix(mediaType, vndMediaTypePrefix) ||
   355  		strings.HasSuffix(mediaType, mediaTypeJSONSuffix) {
   356  		return true
   357  	}
   358  	return false
   359  }
   360  
   361  // RestError is returned from REST transmissions to allow for inspection of
   362  // failed request and response contents.
   363  type RestError struct {
   364  	// The Request that triggered the error.
   365  	Req *http.Request
   366  	// The Resposne that the request returned.
   367  	Resp *http.Response
   368  	// err is the original error
   369  	err error
   370  	// ErrBody is the body of the request that errored.
   371  	// Not named Body since there is an accessor method.
   372  	ErrBody *string
   373  }
   374  
   375  func (r *RestError) Error() string {
   376  	msg := r.err.Error()
   377  	prefix := msg + " - "
   378  
   379  	// Make sure the Error reads the cached body so
   380  	// you can call error multiple times with no issues.
   381  	// Also handle json from the endpoint and look for
   382  	// the error field.
   383  	if r.Body() != "" {
   384  		type body struct {
   385  			Error string `json:"error"`
   386  		}
   387  
   388  		var b *body
   389  
   390  		jerr := json.Unmarshal([]byte(r.Body()), &b)
   391  		if jerr != nil {
   392  			return prefix + r.Body()
   393  		}
   394  
   395  		if b.Error != "" {
   396  			return prefix + b.Error
   397  		}
   398  
   399  		return prefix + r.Body()
   400  	}
   401  
   402  	return msg
   403  }
   404  
   405  func (r *RestError) Body() string {
   406  	// Return the body if we have it.
   407  	if r.ErrBody != nil {
   408  		return *r.ErrBody
   409  	}
   410  
   411  	// Easier to deal with body as regular string.
   412  	// ErrBody is a pointer so that I can tell if it was
   413  	// actually set to "".
   414  	strBody := ""
   415  
   416  	// If we don't have a body, return "".
   417  	if r.Resp == nil || r.Resp.Body == nil {
   418  		r.ErrBody = &strBody
   419  		return *r.ErrBody
   420  	}
   421  
   422  	// Read the body, then set a new buffer
   423  	// to the body field so the original
   424  	// response still has a body.
   425  	b, _ := ioutil.ReadAll(r.Resp.Body)
   426  	defer r.Resp.Body.Close()
   427  	buf := bytes.NewBuffer(b)
   428  	r.Resp.Body = ioutil.NopCloser(buf)
   429  
   430  	// Set ErrBody to the new body.
   431  	strBody = string(b)
   432  	r.ErrBody = &strBody
   433  
   434  	return *r.ErrBody
   435  }