gitlab.com/jokerrs1/Sia@v1.3.2/node/api/client/client.go (about)

     1  package client
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"io"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"strings"
    10  
    11  	"github.com/NebulousLabs/Sia/node/api"
    12  	"github.com/NebulousLabs/errors"
    13  )
    14  
    15  // A Client makes requests to the siad HTTP API.
    16  type Client struct {
    17  	// Address is the API address of the siad server.
    18  	Address string
    19  
    20  	// Password must match the password of the siad server.
    21  	Password string
    22  
    23  	// UserAgent must match the User-Agent required by the siad server. If not
    24  	// set, it defaults to "Sia-Agent".
    25  	UserAgent string
    26  }
    27  
    28  // New creates a new Client using the provided address.
    29  func New(address string) *Client {
    30  	return &Client{
    31  		Address: address,
    32  	}
    33  }
    34  
    35  // NewRequest constructs a request to the siad HTTP API, setting the correct
    36  // User-Agent and Basic Auth. The resource path must begin with /.
    37  func (c *Client) NewRequest(method, resource string, body io.Reader) (*http.Request, error) {
    38  	url := "http://" + c.Address + resource
    39  	req, err := http.NewRequest(method, url, body)
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  	agent := c.UserAgent
    44  	if agent == "" {
    45  		agent = "Sia-Agent"
    46  	}
    47  	req.Header.Set("User-Agent", agent)
    48  	if c.Password != "" {
    49  		req.SetBasicAuth("", c.Password)
    50  	}
    51  	return req, nil
    52  }
    53  
    54  // drainAndClose reads rc until EOF and then closes it. drainAndClose should
    55  // always be called on HTTP response bodies, because if the body is not fully
    56  // read, the underlying connection can't be reused.
    57  func drainAndClose(rc io.ReadCloser) {
    58  	io.Copy(ioutil.Discard, rc)
    59  	rc.Close()
    60  }
    61  
    62  // readAPIError decodes and returns an api.Error.
    63  func readAPIError(r io.Reader) error {
    64  	var apiErr api.Error
    65  	if err := json.NewDecoder(r).Decode(&apiErr); err != nil {
    66  		return errors.AddContext(err, "could not read error response")
    67  	}
    68  	return apiErr
    69  }
    70  
    71  // getRawResponse requests the specified resource. The response, if provided,
    72  // will be returned in a byte slice
    73  func (c *Client) getRawResponse(resource string) ([]byte, error) {
    74  	req, err := c.NewRequest("GET", resource, nil)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	res, err := http.DefaultClient.Do(req)
    79  	if err != nil {
    80  		return nil, errors.AddContext(err, "request failed")
    81  	}
    82  	defer drainAndClose(res.Body)
    83  
    84  	if res.StatusCode == http.StatusNotFound {
    85  		return nil, errors.New("API call not recognized: " + resource)
    86  	}
    87  
    88  	// If the status code is not 2xx, decode and return the accompanying
    89  	// api.Error.
    90  	if res.StatusCode < 200 || res.StatusCode > 299 {
    91  		return nil, readAPIError(res.Body)
    92  	}
    93  
    94  	if res.StatusCode == http.StatusNoContent {
    95  		// no reason to read the response
    96  		return []byte{}, nil
    97  	}
    98  	return ioutil.ReadAll(res.Body)
    99  }
   100  
   101  // get requests the specified resource. The response, if provided, will be
   102  // decoded into obj. The resource path must begin with /.
   103  func (c *Client) get(resource string, obj interface{}) error {
   104  	// Request resource
   105  	data, err := c.getRawResponse(resource)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	if obj == nil {
   110  		// No need to decode response
   111  		return nil
   112  	}
   113  
   114  	// Decode response
   115  	buf := bytes.NewBuffer(data)
   116  	err = json.NewDecoder(buf).Decode(obj)
   117  	if err != nil {
   118  		return errors.AddContext(err, "could not read response")
   119  	}
   120  	return nil
   121  }
   122  
   123  // postRawResponse requests the specified resource. The response, if provided,
   124  // will be returned in a byte slice
   125  func (c *Client) postRawResponse(resource string, data string) ([]byte, error) {
   126  	req, err := c.NewRequest("POST", resource, strings.NewReader(data))
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	// TODO: is this necessary?
   131  	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
   132  	res, err := http.DefaultClient.Do(req)
   133  	if err != nil {
   134  		return nil, errors.AddContext(err, "request failed")
   135  	}
   136  	defer drainAndClose(res.Body)
   137  
   138  	if res.StatusCode == http.StatusNotFound {
   139  		return nil, errors.New("API call not recognized: " + resource)
   140  	}
   141  
   142  	// If the status code is not 2xx, decode and return the accompanying
   143  	// api.Error.
   144  	if res.StatusCode < 200 || res.StatusCode > 299 {
   145  		return nil, readAPIError(res.Body)
   146  	}
   147  
   148  	if res.StatusCode == http.StatusNoContent {
   149  		// no reason to read the response
   150  		return []byte{}, nil
   151  	}
   152  	return ioutil.ReadAll(res.Body)
   153  }
   154  
   155  // post makes a POST request to the resource at `resource`, using `data` as the
   156  // request body. The response, if provided, will be decoded into `obj`.
   157  func (c *Client) post(resource string, data string, obj interface{}) error {
   158  	// Request resource
   159  	body, err := c.postRawResponse(resource, data)
   160  	if err != nil {
   161  		return err
   162  	}
   163  	if obj == nil {
   164  		// No need to decode response
   165  		return nil
   166  	}
   167  
   168  	// Decode response
   169  	buf := bytes.NewBuffer(body)
   170  	err = json.NewDecoder(buf).Decode(obj)
   171  	if err != nil {
   172  		return errors.AddContext(err, "could not read response")
   173  	}
   174  	return nil
   175  }