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 }