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