responder.go raw

   1  package autorest
   2  
   3  // Copyright 2017 Microsoft Corporation
   4  //
   5  //  Licensed under the Apache License, Version 2.0 (the "License");
   6  //  you may not use this file except in compliance with the License.
   7  //  You may obtain a copy of the License at
   8  //
   9  //      http://www.apache.org/licenses/LICENSE-2.0
  10  //
  11  //  Unless required by applicable law or agreed to in writing, software
  12  //  distributed under the License is distributed on an "AS IS" BASIS,
  13  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14  //  See the License for the specific language governing permissions and
  15  //  limitations under the License.
  16  
  17  import (
  18  	"bytes"
  19  	"encoding/json"
  20  	"encoding/xml"
  21  	"fmt"
  22  	"io"
  23  	"net/http"
  24  	"strings"
  25  )
  26  
  27  // Responder is the interface that wraps the Respond method.
  28  //
  29  // Respond accepts and reacts to an http.Response. Implementations must ensure to not share or hold
  30  // state since Responders may be shared and re-used.
  31  type Responder interface {
  32  	Respond(*http.Response) error
  33  }
  34  
  35  // ResponderFunc is a method that implements the Responder interface.
  36  type ResponderFunc func(*http.Response) error
  37  
  38  // Respond implements the Responder interface on ResponderFunc.
  39  func (rf ResponderFunc) Respond(r *http.Response) error {
  40  	return rf(r)
  41  }
  42  
  43  // RespondDecorator takes and possibly decorates, by wrapping, a Responder. Decorators may react to
  44  // the http.Response and pass it along or, first, pass the http.Response along then react.
  45  type RespondDecorator func(Responder) Responder
  46  
  47  // CreateResponder creates, decorates, and returns a Responder. Without decorators, the returned
  48  // Responder returns the passed http.Response unmodified. Responders may or may not be safe to share
  49  // and re-used: It depends on the applied decorators. For example, a standard decorator that closes
  50  // the response body is fine to share whereas a decorator that reads the body into a passed struct
  51  // is not.
  52  //
  53  // To prevent memory leaks, ensure that at least one Responder closes the response body.
  54  func CreateResponder(decorators ...RespondDecorator) Responder {
  55  	return DecorateResponder(
  56  		Responder(ResponderFunc(func(r *http.Response) error { return nil })),
  57  		decorators...)
  58  }
  59  
  60  // DecorateResponder accepts a Responder and a, possibly empty, set of RespondDecorators, which it
  61  // applies to the Responder. Decorators are applied in the order received, but their affect upon the
  62  // request depends on whether they are a pre-decorator (react to the http.Response and then pass it
  63  // along) or a post-decorator (pass the http.Response along and then react).
  64  func DecorateResponder(r Responder, decorators ...RespondDecorator) Responder {
  65  	for _, decorate := range decorators {
  66  		r = decorate(r)
  67  	}
  68  	return r
  69  }
  70  
  71  // Respond accepts an http.Response and a, possibly empty, set of RespondDecorators.
  72  // It creates a Responder from the decorators it then applies to the passed http.Response.
  73  func Respond(r *http.Response, decorators ...RespondDecorator) error {
  74  	if r == nil {
  75  		return nil
  76  	}
  77  	return CreateResponder(decorators...).Respond(r)
  78  }
  79  
  80  // ByIgnoring returns a RespondDecorator that ignores the passed http.Response passing it unexamined
  81  // to the next RespondDecorator.
  82  func ByIgnoring() RespondDecorator {
  83  	return func(r Responder) Responder {
  84  		return ResponderFunc(func(resp *http.Response) error {
  85  			return r.Respond(resp)
  86  		})
  87  	}
  88  }
  89  
  90  // ByCopying copies the contents of the http.Response Body into the passed bytes.Buffer as
  91  // the Body is read.
  92  func ByCopying(b *bytes.Buffer) RespondDecorator {
  93  	return func(r Responder) Responder {
  94  		return ResponderFunc(func(resp *http.Response) error {
  95  			err := r.Respond(resp)
  96  			if err == nil && resp != nil && resp.Body != nil {
  97  				resp.Body = TeeReadCloser(resp.Body, b)
  98  			}
  99  			return err
 100  		})
 101  	}
 102  }
 103  
 104  // ByDiscardingBody returns a RespondDecorator that first invokes the passed Responder after which
 105  // it copies the remaining bytes (if any) in the response body to ioutil.Discard. Since the passed
 106  // Responder is invoked prior to discarding the response body, the decorator may occur anywhere
 107  // within the set.
 108  func ByDiscardingBody() RespondDecorator {
 109  	return func(r Responder) Responder {
 110  		return ResponderFunc(func(resp *http.Response) error {
 111  			err := r.Respond(resp)
 112  			if err == nil && resp != nil && resp.Body != nil {
 113  				if _, err := io.Copy(io.Discard, resp.Body); err != nil {
 114  					return fmt.Errorf("Error discarding the response body: %v", err)
 115  				}
 116  			}
 117  			return err
 118  		})
 119  	}
 120  }
 121  
 122  // ByClosing returns a RespondDecorator that first invokes the passed Responder after which it
 123  // closes the response body. Since the passed Responder is invoked prior to closing the response
 124  // body, the decorator may occur anywhere within the set.
 125  func ByClosing() RespondDecorator {
 126  	return func(r Responder) Responder {
 127  		return ResponderFunc(func(resp *http.Response) error {
 128  			err := r.Respond(resp)
 129  			if resp != nil && resp.Body != nil {
 130  				if err := resp.Body.Close(); err != nil {
 131  					return fmt.Errorf("Error closing the response body: %v", err)
 132  				}
 133  			}
 134  			return err
 135  		})
 136  	}
 137  }
 138  
 139  // ByClosingIfError returns a RespondDecorator that first invokes the passed Responder after which
 140  // it closes the response if the passed Responder returns an error and the response body exists.
 141  func ByClosingIfError() RespondDecorator {
 142  	return func(r Responder) Responder {
 143  		return ResponderFunc(func(resp *http.Response) error {
 144  			err := r.Respond(resp)
 145  			if err != nil && resp != nil && resp.Body != nil {
 146  				if err := resp.Body.Close(); err != nil {
 147  					return fmt.Errorf("Error closing the response body: %v", err)
 148  				}
 149  			}
 150  			return err
 151  		})
 152  	}
 153  }
 154  
 155  // ByUnmarshallingBytes returns a RespondDecorator that copies the Bytes returned in the
 156  // response Body into the value pointed to by v.
 157  func ByUnmarshallingBytes(v *[]byte) RespondDecorator {
 158  	return func(r Responder) Responder {
 159  		return ResponderFunc(func(resp *http.Response) error {
 160  			err := r.Respond(resp)
 161  			if err == nil {
 162  				bytes, errInner := io.ReadAll(resp.Body)
 163  				if errInner != nil {
 164  					err = fmt.Errorf("Error occurred reading http.Response#Body - Error = '%v'", errInner)
 165  				} else {
 166  					*v = bytes
 167  				}
 168  			}
 169  			return err
 170  		})
 171  	}
 172  }
 173  
 174  // ByUnmarshallingJSON returns a RespondDecorator that decodes a JSON document returned in the
 175  // response Body into the value pointed to by v.
 176  func ByUnmarshallingJSON(v interface{}) RespondDecorator {
 177  	return func(r Responder) Responder {
 178  		return ResponderFunc(func(resp *http.Response) error {
 179  			err := r.Respond(resp)
 180  			if err == nil {
 181  				b, errInner := io.ReadAll(resp.Body)
 182  				// Some responses might include a BOM, remove for successful unmarshalling
 183  				b = bytes.TrimPrefix(b, []byte("\xef\xbb\xbf"))
 184  				if errInner != nil {
 185  					err = fmt.Errorf("Error occurred reading http.Response#Body - Error = '%v'", errInner)
 186  				} else if len(strings.Trim(string(b), " ")) > 0 {
 187  					errInner = json.Unmarshal(b, v)
 188  					if errInner != nil {
 189  						err = fmt.Errorf("Error occurred unmarshalling JSON - Error = '%v' JSON = '%s'", errInner, string(b))
 190  					}
 191  				}
 192  			}
 193  			return err
 194  		})
 195  	}
 196  }
 197  
 198  // ByUnmarshallingXML returns a RespondDecorator that decodes a XML document returned in the
 199  // response Body into the value pointed to by v.
 200  func ByUnmarshallingXML(v interface{}) RespondDecorator {
 201  	return func(r Responder) Responder {
 202  		return ResponderFunc(func(resp *http.Response) error {
 203  			err := r.Respond(resp)
 204  			if err == nil {
 205  				b, errInner := io.ReadAll(resp.Body)
 206  				if errInner != nil {
 207  					err = fmt.Errorf("Error occurred reading http.Response#Body - Error = '%v'", errInner)
 208  				} else {
 209  					errInner = xml.Unmarshal(b, v)
 210  					if errInner != nil {
 211  						err = fmt.Errorf("Error occurred unmarshalling Xml - Error = '%v' Xml = '%s'", errInner, string(b))
 212  					}
 213  				}
 214  			}
 215  			return err
 216  		})
 217  	}
 218  }
 219  
 220  // WithErrorUnlessStatusCode returns a RespondDecorator that emits an error unless the response
 221  // StatusCode is among the set passed. On error, response body is fully read into a buffer and
 222  // presented in the returned error, as well as in the response body.
 223  func WithErrorUnlessStatusCode(codes ...int) RespondDecorator {
 224  	return func(r Responder) Responder {
 225  		return ResponderFunc(func(resp *http.Response) error {
 226  			err := r.Respond(resp)
 227  			if err == nil && !ResponseHasStatusCode(resp, codes...) {
 228  				derr := NewErrorWithResponse("autorest", "WithErrorUnlessStatusCode", resp, "%v %v failed with %s",
 229  					resp.Request.Method,
 230  					resp.Request.URL,
 231  					resp.Status)
 232  				if resp.Body != nil {
 233  					defer resp.Body.Close()
 234  					b, _ := io.ReadAll(resp.Body)
 235  					derr.ServiceError = b
 236  					resp.Body = io.NopCloser(bytes.NewReader(b))
 237  				}
 238  				err = derr
 239  			}
 240  			return err
 241  		})
 242  	}
 243  }
 244  
 245  // WithErrorUnlessOK returns a RespondDecorator that emits an error if the response StatusCode is
 246  // anything other than HTTP 200.
 247  func WithErrorUnlessOK() RespondDecorator {
 248  	return WithErrorUnlessStatusCode(http.StatusOK)
 249  }
 250  
 251  // ExtractHeader extracts all values of the specified header from the http.Response. It returns an
 252  // empty string slice if the passed http.Response is nil or the header does not exist.
 253  func ExtractHeader(header string, resp *http.Response) []string {
 254  	if resp != nil && resp.Header != nil {
 255  		return resp.Header[http.CanonicalHeaderKey(header)]
 256  	}
 257  	return nil
 258  }
 259  
 260  // ExtractHeaderValue extracts the first value of the specified header from the http.Response. It
 261  // returns an empty string if the passed http.Response is nil or the header does not exist.
 262  func ExtractHeaderValue(header string, resp *http.Response) string {
 263  	h := ExtractHeader(header, resp)
 264  	if len(h) > 0 {
 265  		return h[0]
 266  	}
 267  	return ""
 268  }
 269