autorest.go raw

   1  /*
   2  Package autorest implements an HTTP request pipeline suitable for use across multiple go-routines
   3  and provides the shared routines relied on by AutoRest (see https://github.com/Azure/autorest/)
   4  generated Go code.
   5  
   6  The package breaks sending and responding to HTTP requests into three phases: Preparing, Sending,
   7  and Responding. A typical pattern is:
   8  
   9  	req, err := Prepare(&http.Request{},
  10  	  token.WithAuthorization())
  11  
  12  	resp, err := Send(req,
  13  	  WithLogging(logger),
  14  	  DoErrorIfStatusCode(http.StatusInternalServerError),
  15  	  DoCloseIfError(),
  16  	  DoRetryForAttempts(5, time.Second))
  17  
  18  	err = Respond(resp,
  19  	  ByDiscardingBody(),
  20  	  ByClosing())
  21  
  22  Each phase relies on decorators to modify and / or manage processing. Decorators may first modify
  23  and then pass the data along, pass the data first and then modify the result, or wrap themselves
  24  around passing the data (such as a logger might do). Decorators run in the order provided. For
  25  example, the following:
  26  
  27  	req, err := Prepare(&http.Request{},
  28  	  WithBaseURL("https://microsoft.com/"),
  29  	  WithPath("a"),
  30  	  WithPath("b"),
  31  	  WithPath("c"))
  32  
  33  will set the URL to:
  34  
  35  	https://microsoft.com/a/b/c
  36  
  37  Preparers and Responders may be shared and re-used (assuming the underlying decorators support
  38  sharing and re-use). Performant use is obtained by creating one or more Preparers and Responders
  39  shared among multiple go-routines, and a single Sender shared among multiple sending go-routines,
  40  all bound together by means of input / output channels.
  41  
  42  Decorators hold their passed state within a closure (such as the path components in the example
  43  above). Be careful to share Preparers and Responders only in a context where such held state
  44  applies. For example, it may not make sense to share a Preparer that applies a query string from a
  45  fixed set of values. Similarly, sharing a Responder that reads the response body into a passed
  46  struct (e.g., ByUnmarshallingJson) is likely incorrect.
  47  
  48  Lastly, the Swagger specification (https://swagger.io) that drives AutoRest
  49  (https://github.com/Azure/autorest/) precisely defines two date forms: date and date-time. The
  50  github.com/Azure/go-autorest/autorest/date package provides time.Time derivations to ensure
  51  correct parsing and formatting.
  52  
  53  Errors raised by autorest objects and methods will conform to the autorest.Error interface.
  54  
  55  See the included examples for more detail. For details on the suggested use of this package by
  56  generated clients, see the Client described below.
  57  */
  58  package autorest
  59  
  60  // Copyright 2017 Microsoft Corporation
  61  //
  62  //  Licensed under the Apache License, Version 2.0 (the "License");
  63  //  you may not use this file except in compliance with the License.
  64  //  You may obtain a copy of the License at
  65  //
  66  //      http://www.apache.org/licenses/LICENSE-2.0
  67  //
  68  //  Unless required by applicable law or agreed to in writing, software
  69  //  distributed under the License is distributed on an "AS IS" BASIS,
  70  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  71  //  See the License for the specific language governing permissions and
  72  //  limitations under the License.
  73  
  74  import (
  75  	"context"
  76  	"net/http"
  77  	"time"
  78  )
  79  
  80  const (
  81  	// HeaderLocation specifies the HTTP Location header.
  82  	HeaderLocation = "Location"
  83  
  84  	// HeaderRetryAfter specifies the HTTP Retry-After header.
  85  	HeaderRetryAfter = "Retry-After"
  86  )
  87  
  88  // ResponseHasStatusCode returns true if the status code in the HTTP Response is in the passed set
  89  // and false otherwise.
  90  func ResponseHasStatusCode(resp *http.Response, codes ...int) bool {
  91  	if resp == nil {
  92  		return false
  93  	}
  94  	return containsInt(codes, resp.StatusCode)
  95  }
  96  
  97  // GetLocation retrieves the URL from the Location header of the passed response.
  98  func GetLocation(resp *http.Response) string {
  99  	return resp.Header.Get(HeaderLocation)
 100  }
 101  
 102  // GetRetryAfter extracts the retry delay from the Retry-After header of the passed response. If
 103  // the header is absent or is malformed, it will return the supplied default delay time.Duration.
 104  func GetRetryAfter(resp *http.Response, defaultDelay time.Duration) time.Duration {
 105  	retry := resp.Header.Get(HeaderRetryAfter)
 106  	if retry == "" {
 107  		return defaultDelay
 108  	}
 109  
 110  	d, err := time.ParseDuration(retry + "s")
 111  	if err != nil {
 112  		return defaultDelay
 113  	}
 114  
 115  	return d
 116  }
 117  
 118  // NewPollingRequest allocates and returns a new http.Request to poll for the passed response.
 119  func NewPollingRequest(resp *http.Response, cancel <-chan struct{}) (*http.Request, error) {
 120  	location := GetLocation(resp)
 121  	if location == "" {
 122  		return nil, NewErrorWithResponse("autorest", "NewPollingRequest", resp, "Location header missing from response that requires polling")
 123  	}
 124  
 125  	req, err := Prepare(&http.Request{Cancel: cancel},
 126  		AsGet(),
 127  		WithBaseURL(location))
 128  	if err != nil {
 129  		return nil, NewErrorWithError(err, "autorest", "NewPollingRequest", nil, "Failure creating poll request to %s", location)
 130  	}
 131  
 132  	return req, nil
 133  }
 134  
 135  // NewPollingRequestWithContext allocates and returns a new http.Request with the specified context to poll for the passed response.
 136  func NewPollingRequestWithContext(ctx context.Context, resp *http.Response) (*http.Request, error) {
 137  	location := GetLocation(resp)
 138  	if location == "" {
 139  		return nil, NewErrorWithResponse("autorest", "NewPollingRequestWithContext", resp, "Location header missing from response that requires polling")
 140  	}
 141  
 142  	req, err := Prepare((&http.Request{}).WithContext(ctx),
 143  		AsGet(),
 144  		WithBaseURL(location))
 145  	if err != nil {
 146  		return nil, NewErrorWithError(err, "autorest", "NewPollingRequestWithContext", nil, "Failure creating poll request to %s", location)
 147  	}
 148  
 149  	return req, nil
 150  }
 151