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 }