github.com/algorand/go-algorand-sdk@v1.24.0/client/algod/algod.go (about)

     1  package algod
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"net/url"
    11  	"strings"
    12  
    13  	"github.com/google/go-querystring/query"
    14  )
    15  
    16  const (
    17  	authHeader           = "X-Algo-API-Token"
    18  	healthCheckEndpoint  = "/health"
    19  	apiVersionPathPrefix = "/v1"
    20  )
    21  
    22  // unversionedPaths ais a set of paths that should not be prefixed by the API version
    23  // Deprecated: v1 algod client is deprecated, please use the v2 algod client.
    24  var unversionedPaths = map[string]bool{
    25  	"/versions": true,
    26  	"/health":   true,
    27  }
    28  
    29  // rawRequestPaths is a set of paths where the body should not be urlencoded
    30  // Deprecated: v1 algod client is deprecated, please use the v2 algod client.
    31  var rawRequestPaths = map[string]bool{
    32  	"/transactions": true,
    33  }
    34  
    35  // Header is a struct for custom headers.
    36  // Deprecated: v1 algod client is deprecated, please use the v2 algod client.
    37  type Header struct {
    38  	Key   string
    39  	Value string
    40  }
    41  
    42  // Client manages the REST interface for a calling user.
    43  // Deprecated: v1 algod client is deprecated, please use the v2 algod client.
    44  type Client struct {
    45  	serverURL url.URL
    46  	apiToken  string
    47  	headers   []*Header
    48  }
    49  
    50  // MakeClient is the factory for constructing a Client for a given endpoint.
    51  // Deprecated: v1 algod client is deprecated, please use the v2 algod client.
    52  func MakeClient(address string, apiToken string) (c Client, err error) {
    53  	url, err := url.Parse(address)
    54  	if err != nil {
    55  		return
    56  	}
    57  
    58  	c = Client{
    59  		serverURL: *url,
    60  		apiToken:  apiToken,
    61  	}
    62  	return
    63  }
    64  
    65  // MakeClientWithHeaders is the factory for constructing a Client for a given endpoint with additional user defined headers.
    66  // Deprecated: v1 algod client is deprecated, please use the v2 algod client.
    67  func MakeClientWithHeaders(address string, apiToken string, headers []*Header) (c Client, err error) {
    68  	c, err = MakeClient(address, apiToken)
    69  	if err != nil {
    70  		return
    71  	}
    72  
    73  	c.headers = append(c.headers, headers...)
    74  
    75  	return
    76  }
    77  
    78  // extractError checks if the response signifies an error (for now, StatusCode != 200).
    79  // If so, it returns the error.
    80  // Otherwise, it returns nil.
    81  // Deprecated: v1 algod client is deprecated, please use the v2 algod client.
    82  func extractError(resp *http.Response) error {
    83  	if resp.StatusCode == 200 {
    84  		return nil
    85  	}
    86  
    87  	errorBuf, _ := ioutil.ReadAll(resp.Body) // ignore returned error
    88  	return fmt.Errorf("HTTP %v: %s", resp.Status, errorBuf)
    89  }
    90  
    91  // stripTransaction gets a transaction of the form "tx-XXXXXXXX" and truncates the "tx-" part, if it starts with "tx-"
    92  // Deprecated: v1 algod client is deprecated, please use the v2 algod client.
    93  func stripTransaction(tx string) string {
    94  	if strings.HasPrefix(tx, "tx-") {
    95  		return strings.SplitAfter(tx, "-")[1]
    96  	}
    97  	return tx
    98  }
    99  
   100  // mergeRawQueries merges two raw queries, appending an "&" if both are non-empty
   101  // Deprecated: v1 algod client is deprecated, please use the v2 algod client.
   102  func mergeRawQueries(q1, q2 string) string {
   103  	if q1 == "" {
   104  		return q2
   105  	} else if q2 == "" {
   106  		return q1
   107  	} else {
   108  		return q1 + "&" + q2
   109  	}
   110  }
   111  
   112  // RawRequest submits the requests, and returns a map of the response fields
   113  // Deprecated: v1 algod client is deprecated, please use the v2 algod client.
   114  func (client Client) RawRequest(path string, request interface{}, requestMethod string, encodeJSON bool, headers []*Header) (
   115  	v map[string]interface{}, err error) {
   116  	response, err := client.submitFormRaw(path, request, requestMethod, encodeJSON, headers)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	body, err := ioutil.ReadAll(response.Body)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	if err = json.Unmarshal(body, &v); err != nil {
   127  		return nil, err
   128  	}
   129  	return v, err
   130  }
   131  
   132  // submitForm is a helper used for submitting (ex.) GETs and POSTs to the server
   133  // Deprecated: v1 algod client is deprecated, please use the v2 algod client.
   134  func (client Client) submitFormRaw(path string, request interface{}, requestMethod string, encodeJSON bool, headers []*Header) (resp *http.Response, err error) {
   135  	queryURL := client.serverURL
   136  
   137  	// Handle version prefix
   138  	if !unversionedPaths[path] {
   139  		queryURL.Path += strings.Join([]string{apiVersionPathPrefix, path}, "")
   140  	} else {
   141  		queryURL.Path += path
   142  	}
   143  
   144  	var req *http.Request
   145  	var body io.Reader
   146  
   147  	if request != nil {
   148  		if rawRequestPaths[path] {
   149  			reqBytes, ok := request.([]byte)
   150  			if !ok {
   151  				return nil, fmt.Errorf("couldn't decode raw request as bytes")
   152  			}
   153  			body = bytes.NewBuffer(reqBytes)
   154  		} else {
   155  			v, err := query.Values(request)
   156  			if err != nil {
   157  				return nil, err
   158  			}
   159  
   160  			queryURL.RawQuery = mergeRawQueries(queryURL.RawQuery, v.Encode())
   161  			if encodeJSON {
   162  				jsonValue, _ := json.Marshal(request)
   163  				body = bytes.NewBuffer(jsonValue)
   164  			}
   165  		}
   166  	}
   167  
   168  	req, err = http.NewRequest(requestMethod, queryURL.String(), body)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	// Normally this would not always be set, but due to https://github.com/algorand/go-algorand/issues/1009 it is always needed
   174  	req.Header.Set(authHeader, client.apiToken)
   175  
   176  	for _, header := range client.headers {
   177  		req.Header.Add(header.Key, header.Value)
   178  	}
   179  	// Add the request headers.
   180  	for _, header := range headers {
   181  		req.Header.Add(header.Key, header.Value)
   182  	}
   183  
   184  	httpClient := &http.Client{}
   185  	resp, err = httpClient.Do(req)
   186  
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  
   191  	err = extractError(resp)
   192  	if err != nil {
   193  		resp.Body.Close()
   194  		return nil, err
   195  	}
   196  	return resp, nil
   197  }
   198  
   199  // Deprecated: v1 algod client is deprecated, please use the v2 algod client.
   200  func (client Client) submitForm(response interface{}, path string, request interface{}, requestMethod string, encodeJSON bool, headers []*Header) error {
   201  	resp, err := client.submitFormRaw(path, request, requestMethod, encodeJSON, headers)
   202  	if err != nil {
   203  		return err
   204  	}
   205  
   206  	defer resp.Body.Close()
   207  
   208  	dec := json.NewDecoder(resp.Body)
   209  	return dec.Decode(&response)
   210  }
   211  
   212  // get performs a GET request to the specific path against the server
   213  // Deprecated: v1 algod client is deprecated, please use the v2 algod client.
   214  func (client Client) get(response interface{}, path string, request interface{}, headers []*Header) error {
   215  	return client.submitForm(response, path, request, "GET", false /* encodeJSON */, headers)
   216  }
   217  
   218  // post sends a POST request to the given path with the given request object.
   219  // No query parameters will be sent if request is nil.
   220  // response must be a pointer to an object as post writes the response there.
   221  func (client Client) post(response interface{}, path string, request interface{}, headers []*Header) error {
   222  	return client.submitForm(response, path, request, "POST", true /* encodeJSON */, headers)
   223  }
   224  
   225  // as post, but with MethodPut
   226  // Deprecated: v1 algod client is deprecated, please use the v2 algod client.
   227  func (client Client) put(response interface{}, path string, request interface{}, headers []*Header) error {
   228  	return client.submitForm(response, path, request, "PUT", true /* encodeJSON */, headers)
   229  }
   230  
   231  // as post, but with MethodPatch
   232  // Deprecated: v1 algod client is deprecated, please use the v2 algod client.
   233  func (client Client) patch(response interface{}, path string, request interface{}, headers []*Header) error {
   234  	return client.submitForm(response, path, request, "PATCH", true /* encodeJSON */, headers)
   235  }