github.com/decred/politeia@v1.4.0/politeiad/client/client.go (about)

     1  // Copyright (c) 2020-2021 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package client
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"encoding/json"
    11  	"fmt"
    12  	"net/http"
    13  
    14  	"github.com/decred/politeia/politeiad/api/v1/identity"
    15  	"github.com/decred/politeia/util"
    16  )
    17  
    18  // Client provides a client for interacting with the politeiad API.
    19  type Client struct {
    20  	rpcHost string
    21  	rpcCert string
    22  	rpcUser string
    23  	rpcPass string
    24  	http    *http.Client
    25  	pid     *identity.PublicIdentity
    26  }
    27  
    28  // ErrorReply represents the request body that is returned from politeaid when
    29  // an error occurs. PluginID will only be populated if the error occurred
    30  // during execution of a plugin command.
    31  type ErrorReply struct {
    32  	PluginID     string `json:"pluginid"`
    33  	ErrorCode    uint32 `json:"errorcode"`
    34  	ErrorContext string `json:"errorcontext"`
    35  }
    36  
    37  // RespError represents a politeiad response error. A RespError is returned
    38  // anytime the politeiad response is not a 200.
    39  type RespError struct {
    40  	HTTPCode   int
    41  	ErrorReply ErrorReply
    42  }
    43  
    44  // Error satisfies the error interface.
    45  func (e RespError) Error() string {
    46  	if e.ErrorReply.PluginID != "" {
    47  		return fmt.Sprintf("politeiad plugin error: %v %v %v",
    48  			e.HTTPCode, e.ErrorReply.PluginID, e.ErrorReply.ErrorCode)
    49  	}
    50  	return fmt.Sprintf("politeiad error: %v %v",
    51  		e.HTTPCode, e.ErrorReply.ErrorCode)
    52  }
    53  
    54  // makeReq makes a politeiad http request to the method and route provided,
    55  // serializing the provided object as the request body, and returning a byte
    56  // slice of the response body. A RespError is returned if politeiad responds
    57  // with anything other than a 200 http status code.
    58  func (c *Client) makeReq(ctx context.Context, method, api, route string, v interface{}) ([]byte, error) {
    59  	// Serialize body
    60  	var (
    61  		reqBody []byte
    62  		err     error
    63  	)
    64  	if v != nil {
    65  		reqBody, err = json.Marshal(v)
    66  		if err != nil {
    67  			return nil, err
    68  		}
    69  	}
    70  
    71  	// Send request
    72  	fullRoute := c.rpcHost + api + route
    73  	req, err := http.NewRequestWithContext(ctx, method,
    74  		fullRoute, bytes.NewReader(reqBody))
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	req.SetBasicAuth(c.rpcUser, c.rpcPass)
    79  	r, err := c.http.Do(req)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	defer r.Body.Close()
    84  
    85  	// Handle reply
    86  	if r.StatusCode != http.StatusOK {
    87  		var e ErrorReply
    88  		decoder := json.NewDecoder(r.Body)
    89  		if err := decoder.Decode(&e); err != nil {
    90  			return nil, fmt.Errorf("status code %v: %v", r.StatusCode, err)
    91  		}
    92  		return nil, RespError{
    93  			HTTPCode:   r.StatusCode,
    94  			ErrorReply: e,
    95  		}
    96  	}
    97  
    98  	return util.RespBody(r), nil
    99  }
   100  
   101  // New returns a new politeiad client.
   102  func New(rpcHost, rpcCert, rpcUser, rpcPass string, pid *identity.PublicIdentity) (*Client, error) {
   103  	h, err := util.NewHTTPClient(false, rpcCert)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	return &Client{
   108  		rpcHost: rpcHost,
   109  		rpcCert: rpcCert,
   110  		rpcUser: rpcUser,
   111  		rpcPass: rpcPass,
   112  		http:    h,
   113  		pid:     pid,
   114  	}, nil
   115  }