github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/lib/rest/rest.go (about) 1 // Package rest implements a simple REST wrapper 2 // 3 // All methods are safe for concurrent calling. 4 package rest 5 6 import ( 7 "bytes" 8 "encoding/json" 9 "encoding/xml" 10 "io" 11 "io/ioutil" 12 "mime/multipart" 13 "net/http" 14 "net/url" 15 "sync" 16 17 "github.com/ncw/rclone/fs" 18 "github.com/pkg/errors" 19 ) 20 21 // Client contains the info to sustain the API 22 type Client struct { 23 mu sync.RWMutex 24 c *http.Client 25 rootURL string 26 errorHandler func(resp *http.Response) error 27 headers map[string]string 28 signer SignerFn 29 } 30 31 // NewClient takes an oauth http.Client and makes a new api instance 32 func NewClient(c *http.Client) *Client { 33 api := &Client{ 34 c: c, 35 errorHandler: defaultErrorHandler, 36 headers: make(map[string]string), 37 } 38 return api 39 } 40 41 // ReadBody reads resp.Body into result, closing the body 42 func ReadBody(resp *http.Response) (result []byte, err error) { 43 defer fs.CheckClose(resp.Body, &err) 44 return ioutil.ReadAll(resp.Body) 45 } 46 47 // defaultErrorHandler doesn't attempt to parse the http body, just 48 // returns it in the error message 49 func defaultErrorHandler(resp *http.Response) (err error) { 50 body, err := ReadBody(resp) 51 if err != nil { 52 return errors.Wrap(err, "error reading error out of body") 53 } 54 return errors.Errorf("HTTP error %v (%v) returned body: %q", resp.StatusCode, resp.Status, body) 55 } 56 57 // SetErrorHandler sets the handler to decode an error response when 58 // the HTTP status code is not 2xx. The handler should close resp.Body. 59 func (api *Client) SetErrorHandler(fn func(resp *http.Response) error) *Client { 60 api.mu.Lock() 61 defer api.mu.Unlock() 62 api.errorHandler = fn 63 return api 64 } 65 66 // SetRoot sets the default RootURL. You can override this on a per 67 // call basis using the RootURL field in Opts. 68 func (api *Client) SetRoot(RootURL string) *Client { 69 api.mu.Lock() 70 defer api.mu.Unlock() 71 api.rootURL = RootURL 72 return api 73 } 74 75 // SetHeader sets a header for all requests 76 func (api *Client) SetHeader(key, value string) *Client { 77 api.mu.Lock() 78 defer api.mu.Unlock() 79 api.headers[key] = value 80 return api 81 } 82 83 // RemoveHeader unsets a header for all requests 84 func (api *Client) RemoveHeader(key string) *Client { 85 api.mu.Lock() 86 defer api.mu.Unlock() 87 delete(api.headers, key) 88 return api 89 } 90 91 // SignerFn is used to sign an outgoing request 92 type SignerFn func(*http.Request) error 93 94 // SetSigner sets a signer for all requests 95 func (api *Client) SetSigner(signer SignerFn) *Client { 96 api.mu.Lock() 97 defer api.mu.Unlock() 98 api.signer = signer 99 return api 100 } 101 102 // SetUserPass creates an Authorization header for all requests with 103 // the UserName and Password passed in 104 func (api *Client) SetUserPass(UserName, Password string) *Client { 105 req, _ := http.NewRequest("GET", "http://example.com", nil) 106 req.SetBasicAuth(UserName, Password) 107 api.SetHeader("Authorization", req.Header.Get("Authorization")) 108 return api 109 } 110 111 // SetCookie creates an Cookies Header for all requests with the supplied 112 // cookies passed in. 113 // All cookies have to be supplied at once, all cookies will be overwritten 114 // on a new call to the method 115 func (api *Client) SetCookie(cks ...*http.Cookie) *Client { 116 req, _ := http.NewRequest("GET", "http://example.com", nil) 117 for _, ck := range cks { 118 req.AddCookie(ck) 119 } 120 api.SetHeader("Cookie", req.Header.Get("Cookie")) 121 return api 122 } 123 124 // Opts contains parameters for Call, CallJSON etc 125 type Opts struct { 126 Method string // GET, POST etc 127 Path string // relative to RootURL 128 RootURL string // override RootURL passed into SetRoot() 129 Body io.Reader 130 NoResponse bool // set to close Body 131 ContentType string 132 ContentLength *int64 133 ContentRange string 134 ExtraHeaders map[string]string 135 UserName string // username for Basic Auth 136 Password string // password for Basic Auth 137 Options []fs.OpenOption 138 IgnoreStatus bool // if set then we don't check error status or parse error body 139 MultipartParams url.Values // if set do multipart form upload with attached file 140 MultipartMetadataName string // ..this is used for the name of the metadata form part if set 141 MultipartContentName string // ..name of the parameter which is the attached file 142 MultipartFileName string // ..name of the file for the attached file 143 Parameters url.Values // any parameters for the final URL 144 TransferEncoding []string // transfer encoding, set to "identity" to disable chunked encoding 145 Close bool // set to close the connection after this transaction 146 NoRedirect bool // if this is set then the client won't follow redirects 147 } 148 149 // Copy creates a copy of the options 150 func (o *Opts) Copy() *Opts { 151 newOpts := *o 152 return &newOpts 153 } 154 155 // DecodeJSON decodes resp.Body into result 156 func DecodeJSON(resp *http.Response, result interface{}) (err error) { 157 defer fs.CheckClose(resp.Body, &err) 158 decoder := json.NewDecoder(resp.Body) 159 return decoder.Decode(result) 160 } 161 162 // DecodeXML decodes resp.Body into result 163 func DecodeXML(resp *http.Response, result interface{}) (err error) { 164 defer fs.CheckClose(resp.Body, &err) 165 decoder := xml.NewDecoder(resp.Body) 166 return decoder.Decode(result) 167 } 168 169 // ClientWithNoRedirects makes a new http client which won't follow redirects 170 func ClientWithNoRedirects(c *http.Client) *http.Client { 171 clientCopy := *c 172 clientCopy.CheckRedirect = func(req *http.Request, via []*http.Request) error { 173 return http.ErrUseLastResponse 174 } 175 return &clientCopy 176 } 177 178 // Call makes the call and returns the http.Response 179 // 180 // if err != nil then resp.Body will need to be closed unless 181 // opt.NoResponse is set 182 // 183 // it will return resp if at all possible, even if err is set 184 func (api *Client) Call(opts *Opts) (resp *http.Response, err error) { 185 api.mu.RLock() 186 defer api.mu.RUnlock() 187 if opts == nil { 188 return nil, errors.New("call() called with nil opts") 189 } 190 url := api.rootURL 191 if opts.RootURL != "" { 192 url = opts.RootURL 193 } 194 if url == "" { 195 return nil, errors.New("RootURL not set") 196 } 197 url += opts.Path 198 if opts.Parameters != nil && len(opts.Parameters) > 0 { 199 url += "?" + opts.Parameters.Encode() 200 } 201 body := opts.Body 202 // If length is set and zero then nil out the body to stop use 203 // use of chunked encoding and insert a "Content-Length: 0" 204 // header. 205 // 206 // If we don't do this we get "Content-Length" headers for all 207 // files except 0 length files. 208 if opts.ContentLength != nil && *opts.ContentLength == 0 { 209 body = nil 210 } 211 req, err := http.NewRequest(opts.Method, url, body) 212 if err != nil { 213 return 214 } 215 headers := make(map[string]string) 216 // Set default headers 217 for k, v := range api.headers { 218 headers[k] = v 219 } 220 if opts.ContentType != "" { 221 headers["Content-Type"] = opts.ContentType 222 } 223 if opts.ContentLength != nil { 224 req.ContentLength = *opts.ContentLength 225 } 226 if opts.ContentRange != "" { 227 headers["Content-Range"] = opts.ContentRange 228 } 229 if len(opts.TransferEncoding) != 0 { 230 req.TransferEncoding = opts.TransferEncoding 231 } 232 if opts.Close { 233 req.Close = true 234 } 235 // Set any extra headers 236 if opts.ExtraHeaders != nil { 237 for k, v := range opts.ExtraHeaders { 238 headers[k] = v 239 } 240 } 241 // add any options to the headers 242 fs.OpenOptionAddHeaders(opts.Options, headers) 243 // Now set the headers 244 for k, v := range headers { 245 if v != "" { 246 req.Header.Add(k, v) 247 } 248 } 249 if opts.UserName != "" || opts.Password != "" { 250 req.SetBasicAuth(opts.UserName, opts.Password) 251 } 252 var c *http.Client 253 if opts.NoRedirect { 254 c = ClientWithNoRedirects(api.c) 255 } else { 256 c = api.c 257 } 258 if api.signer != nil { 259 err = api.signer(req) 260 if err != nil { 261 return nil, errors.Wrap(err, "signer failed") 262 } 263 } 264 api.mu.RUnlock() 265 resp, err = c.Do(req) 266 api.mu.RLock() 267 if err != nil { 268 return nil, err 269 } 270 if !opts.IgnoreStatus { 271 if resp.StatusCode < 200 || resp.StatusCode > 299 { 272 err = api.errorHandler(resp) 273 if err.Error() == "" { 274 // replace empty errors with something 275 err = errors.Errorf("http error %d: %v", resp.StatusCode, resp.Status) 276 } 277 return resp, err 278 } 279 } 280 if opts.NoResponse { 281 return resp, resp.Body.Close() 282 } 283 return resp, nil 284 } 285 286 // MultipartUpload creates an io.Reader which produces an encoded a 287 // multipart form upload from the params passed in and the passed in 288 // 289 // in - the body of the file (may be nil) 290 // params - the form parameters 291 // fileName - is the name of the attached file 292 // contentName - the name of the parameter for the file 293 // 294 // the int64 returned is the overhead in addition to the file contents, in case Content-Length is required 295 // 296 // NB This doesn't allow setting the content type of the attachment 297 func MultipartUpload(in io.Reader, params url.Values, contentName, fileName string) (io.ReadCloser, string, int64, error) { 298 bodyReader, bodyWriter := io.Pipe() 299 writer := multipart.NewWriter(bodyWriter) 300 contentType := writer.FormDataContentType() 301 302 // Create a Multipart Writer as base for calculating the Content-Length 303 buf := &bytes.Buffer{} 304 dummyMultipartWriter := multipart.NewWriter(buf) 305 err := dummyMultipartWriter.SetBoundary(writer.Boundary()) 306 if err != nil { 307 return nil, "", 0, err 308 } 309 310 for key, vals := range params { 311 for _, val := range vals { 312 err := dummyMultipartWriter.WriteField(key, val) 313 if err != nil { 314 return nil, "", 0, err 315 } 316 } 317 } 318 if in != nil { 319 _, err = dummyMultipartWriter.CreateFormFile(contentName, fileName) 320 if err != nil { 321 return nil, "", 0, err 322 } 323 } 324 325 err = dummyMultipartWriter.Close() 326 if err != nil { 327 return nil, "", 0, err 328 } 329 330 multipartLength := int64(buf.Len()) 331 332 // Pump the data in the background 333 go func() { 334 var err error 335 336 for key, vals := range params { 337 for _, val := range vals { 338 err = writer.WriteField(key, val) 339 if err != nil { 340 _ = bodyWriter.CloseWithError(errors.Wrap(err, "create metadata part")) 341 return 342 } 343 } 344 } 345 346 if in != nil { 347 part, err := writer.CreateFormFile(contentName, fileName) 348 if err != nil { 349 _ = bodyWriter.CloseWithError(errors.Wrap(err, "failed to create form file")) 350 return 351 } 352 353 _, err = io.Copy(part, in) 354 if err != nil { 355 _ = bodyWriter.CloseWithError(errors.Wrap(err, "failed to copy data")) 356 return 357 } 358 } 359 360 err = writer.Close() 361 if err != nil { 362 _ = bodyWriter.CloseWithError(errors.Wrap(err, "failed to close form")) 363 return 364 } 365 366 _ = bodyWriter.Close() 367 }() 368 369 return bodyReader, contentType, multipartLength, nil 370 } 371 372 // CallJSON runs Call and decodes the body as a JSON object into response (if not nil) 373 // 374 // If request is not nil then it will be JSON encoded as the body of the request 375 // 376 // If response is not nil then the response will be JSON decoded into 377 // it and resp.Body will be closed. 378 // 379 // If response is nil then the resp.Body will be closed only if 380 // opts.NoResponse is set. 381 // 382 // If (opts.MultipartParams or opts.MultipartContentName) and 383 // opts.Body are set then CallJSON will do a multipart upload with a 384 // file attached. opts.MultipartContentName is the name of the 385 // parameter and opts.MultipartFileName is the name of the file. If 386 // MultpartContentName is set, and request != nil is supplied, then 387 // the request will be marshalled into JSON and added to the form with 388 // parameter name MultipartMetadataName. 389 // 390 // It will return resp if at all possible, even if err is set 391 func (api *Client) CallJSON(opts *Opts, request interface{}, response interface{}) (resp *http.Response, err error) { 392 return api.callCodec(opts, request, response, json.Marshal, DecodeJSON, "application/json") 393 } 394 395 // CallXML runs Call and decodes the body as a XML object into response (if not nil) 396 // 397 // If request is not nil then it will be XML encoded as the body of the request 398 // 399 // If response is not nil then the response will be XML decoded into 400 // it and resp.Body will be closed. 401 // 402 // If response is nil then the resp.Body will be closed only if 403 // opts.NoResponse is set. 404 // 405 // See CallJSON for a description of MultipartParams and related opts 406 // 407 // It will return resp if at all possible, even if err is set 408 func (api *Client) CallXML(opts *Opts, request interface{}, response interface{}) (resp *http.Response, err error) { 409 return api.callCodec(opts, request, response, xml.Marshal, DecodeXML, "application/xml") 410 } 411 412 type marshalFn func(v interface{}) ([]byte, error) 413 type decodeFn func(resp *http.Response, result interface{}) (err error) 414 415 func (api *Client) callCodec(opts *Opts, request interface{}, response interface{}, marshal marshalFn, decode decodeFn, contentType string) (resp *http.Response, err error) { 416 var requestBody []byte 417 // Marshal the request if given 418 if request != nil { 419 requestBody, err = marshal(request) 420 if err != nil { 421 return nil, err 422 } 423 // Set the body up as a marshalled object if no body passed in 424 if opts.Body == nil { 425 opts = opts.Copy() 426 opts.ContentType = contentType 427 opts.Body = bytes.NewBuffer(requestBody) 428 } 429 } 430 if opts.MultipartParams != nil || opts.MultipartContentName != "" { 431 params := opts.MultipartParams 432 if params == nil { 433 params = url.Values{} 434 } 435 if opts.MultipartMetadataName != "" { 436 params.Add(opts.MultipartMetadataName, string(requestBody)) 437 } 438 opts = opts.Copy() 439 440 var overhead int64 441 opts.Body, opts.ContentType, overhead, err = MultipartUpload(opts.Body, params, opts.MultipartContentName, opts.MultipartFileName) 442 if opts.ContentLength != nil { 443 *opts.ContentLength += overhead 444 } 445 } 446 resp, err = api.Call(opts) 447 if err != nil { 448 return resp, err 449 } 450 // if opts.NoResponse is set, resp.Body will have been closed by Call() 451 if response == nil || opts.NoResponse { 452 return resp, nil 453 } 454 err = decode(resp, response) 455 return resp, err 456 }