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