3 // Copyright 2017 Microsoft Corporation
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
9 // http://www.apache.org/licenses/LICENSE-2.0
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.
28 "github.com/Azure/go-autorest/autorest"
29 "github.com/Azure/go-autorest/tracing"
33 headerAsyncOperation = "Azure-AsyncOperation"
37 operationInProgress string = "InProgress"
38 operationCanceled string = "Canceled"
39 operationFailed string = "Failed"
40 operationSucceeded string = "Succeeded"
43 var pollingCodes = [...]int{http.StatusNoContent, http.StatusAccepted, http.StatusCreated, http.StatusOK}
45 // Future provides a mechanism to access the status and results of an asynchronous request.
46 // Since futures are stateful they should be passed by value to avoid race conditions.
48 req *http.Request // legacy
52 // NewFuture returns a new Future object initialized with the specified request.
53 // Deprecated: Please use NewFutureFromResponse instead.
54 func NewFuture(req *http.Request) Future {
55 return Future{req: req}
58 // NewFutureFromResponse returns a new Future object initialized
59 // with the initial response from an asynchronous operation.
60 func NewFutureFromResponse(resp *http.Response) (Future, error) {
61 pt, err := createPollingTracker(resp)
62 return Future{pt: pt}, err
65 // Response returns the last HTTP response.
66 func (f Future) Response() *http.Response {
70 return f.pt.latestResponse()
73 // Status returns the last status message of the operation.
74 func (f Future) Status() string {
78 return f.pt.pollingStatus()
81 // PollingMethod returns the method used to monitor the status of the asynchronous operation.
82 func (f Future) PollingMethod() PollingMethodType {
86 return f.pt.pollingMethod()
89 // Done queries the service to see if the operation has completed.
90 // Deprecated: Use DoneWithContext()
91 func (f *Future) Done(sender autorest.Sender) (bool, error) {
92 return f.DoneWithContext(context.Background(), sender)
95 // DoneWithContext queries the service to see if the operation has completed.
96 func (f *Future) DoneWithContext(ctx context.Context, sender autorest.Sender) (done bool, err error) {
97 ctx = tracing.StartSpan(ctx, "github.com/Azure/go-autorest/autorest/azure/async.DoneWithContext")
104 tracing.EndSpan(ctx, sc, err)
107 // support for legacy Future implementation
109 resp, err := sender.Do(f.req)
113 pt, err := createPollingTracker(resp)
122 return false, autorest.NewError("Future", "Done", "future is not initialized")
124 if f.pt.hasTerminated() {
125 return true, f.pt.pollingError()
127 if err := f.pt.pollForStatus(ctx, sender); err != nil {
130 if err := f.pt.checkForErrors(); err != nil {
131 return f.pt.hasTerminated(), err
133 if err := f.pt.updatePollingState(f.pt.provisioningStateApplicable()); err != nil {
136 if err := f.pt.initPollingMethod(); err != nil {
139 if err := f.pt.updatePollingMethod(); err != nil {
142 return f.pt.hasTerminated(), f.pt.pollingError()
145 // GetPollingDelay returns a duration the application should wait before checking
146 // the status of the asynchronous request and true; this value is returned from
147 // the service via the Retry-After response header. If the header wasn't returned
148 // then the function returns the zero-value time.Duration and false.
149 func (f Future) GetPollingDelay() (time.Duration, bool) {
153 resp := f.pt.latestResponse()
158 retry := resp.Header.Get(autorest.HeaderRetryAfter)
163 d, err := time.ParseDuration(retry + "s")
171 // WaitForCompletion will return when one of the following conditions is met: the long
172 // running operation has completed, the provided context is cancelled, or the client's
173 // polling duration has been exceeded. It will retry failed polling attempts based on
174 // the retry value defined in the client up to the maximum retry attempts.
175 // Deprecated: Please use WaitForCompletionRef() instead.
176 func (f Future) WaitForCompletion(ctx context.Context, client autorest.Client) error {
177 return f.WaitForCompletionRef(ctx, client)
180 // WaitForCompletionRef will return when one of the following conditions is met: the long
181 // running operation has completed, the provided context is cancelled, or the client's
182 // polling duration has been exceeded. It will retry failed polling attempts based on
183 // the retry value defined in the client up to the maximum retry attempts.
184 // If no deadline is specified in the context then the client.PollingDuration will be
185 // used to determine if a default deadline should be used.
186 // If PollingDuration is greater than zero the value will be used as the context's timeout.
187 // If PollingDuration is zero then no default deadline will be used.
188 func (f *Future) WaitForCompletionRef(ctx context.Context, client autorest.Client) (err error) {
189 ctx = tracing.StartSpan(ctx, "github.com/Azure/go-autorest/autorest/azure/async.WaitForCompletionRef")
196 tracing.EndSpan(ctx, sc, err)
199 // if the provided context already has a deadline don't override it
200 _, hasDeadline := ctx.Deadline()
201 if d := client.PollingDuration; !hasDeadline && d != 0 {
202 var cancel context.CancelFunc
203 cancelCtx, cancel = context.WithTimeout(ctx, d)
207 done, err := f.DoneWithContext(ctx, client)
208 for attempts := 0; !done; done, err = f.DoneWithContext(ctx, client) {
209 if attempts >= client.RetryAttempts {
210 return autorest.NewErrorWithError(err, "Future", "WaitForCompletion", f.pt.latestResponse(), "the number of retries has been exceeded")
212 // we want delayAttempt to be zero in the non-error case so
213 // that DelayForBackoff doesn't perform exponential back-off
215 var delay time.Duration
217 // check for Retry-After delay, if not present use the client's polling delay
219 delay, ok = f.GetPollingDelay()
221 delay = client.PollingDelay
224 // there was an error polling for status so perform exponential
225 // back-off based on the number of attempts using the client's retry
226 // duration. update attempts after delayAttempt to avoid off-by-one.
227 delayAttempt = attempts
228 delay = client.RetryDuration
231 // wait until the delay elapses or the context is cancelled
232 delayElapsed := autorest.DelayForBackoff(delay, delayAttempt, cancelCtx.Done())
234 return autorest.NewErrorWithError(cancelCtx.Err(), "Future", "WaitForCompletion", f.pt.latestResponse(), "context has been cancelled")
240 // MarshalJSON implements the json.Marshaler interface.
241 func (f Future) MarshalJSON() ([]byte, error) {
242 return json.Marshal(f.pt)
245 // UnmarshalJSON implements the json.Unmarshaler interface.
246 func (f *Future) UnmarshalJSON(data []byte) error {
247 // unmarshal into JSON object to determine the tracker type
248 obj := map[string]interface{}{}
249 err := json.Unmarshal(data, &obj)
253 if obj["method"] == nil {
254 return autorest.NewError("Future", "UnmarshalJSON", "missing 'method' property")
256 method := obj["method"].(string)
257 switch strings.ToUpper(method) {
258 case http.MethodDelete:
259 f.pt = &pollingTrackerDelete{}
260 case http.MethodPatch:
261 f.pt = &pollingTrackerPatch{}
262 case http.MethodPost:
263 f.pt = &pollingTrackerPost{}
265 f.pt = &pollingTrackerPut{}
267 return autorest.NewError("Future", "UnmarshalJSON", "unsupoorted method '%s'", method)
269 // now unmarshal into the tracker
270 return json.Unmarshal(data, &f.pt)
273 // PollingURL returns the URL used for retrieving the status of the long-running operation.
274 func (f Future) PollingURL() string {
278 return f.pt.pollingURL()
281 // GetResult should be called once polling has completed successfully.
282 // It makes the final GET call to retrieve the resultant payload.
283 func (f Future) GetResult(sender autorest.Sender) (*http.Response, error) {
284 if f.pt.finalGetURL() == "" {
285 // we can end up in this situation if the async operation returns a 200
286 // with no polling URLs. in that case return the response which should
287 // contain the JSON payload (only do this for successful terminal cases).
288 if lr := f.pt.latestResponse(); lr != nil && f.pt.hasSucceeded() {
291 return nil, autorest.NewError("Future", "GetResult", "missing URL for retrieving result")
293 req, err := http.NewRequest(http.MethodGet, f.pt.finalGetURL(), nil)
297 return sender.Do(req)
300 type pollingTracker interface {
301 // these methods can differ per tracker
303 // checks the response headers and status code to determine the polling mechanism
304 updatePollingMethod() error
306 // checks the response for tracker-specific error conditions
307 checkForErrors() error
309 // returns true if provisioning state should be checked
310 provisioningStateApplicable() bool
312 // methods common to all trackers
314 // initializes a tracker's polling URL and method, called for each iteration.
315 // these values can be overridden by each polling tracker as required.
316 initPollingMethod() error
318 // initializes the tracker's internal state, call this when the tracker is created
319 initializeState() error
321 // makes an HTTP request to check the status of the LRO
322 pollForStatus(ctx context.Context, sender autorest.Sender) error
324 // updates internal tracker state, call this after each call to pollForStatus
325 updatePollingState(provStateApl bool) error
327 // returns the error response from the service, can be nil
330 // returns the polling method being used
331 pollingMethod() PollingMethodType
333 // returns the state of the LRO as returned from the service
334 pollingStatus() string
336 // returns the URL used for polling status
339 // returns the URL used for the final GET to retrieve the resource
342 // returns true if the LRO is in a terminal state
345 // returns true if the LRO is in a failed terminal state
348 // returns true if the LRO is in a successful terminal state
351 // returns the cached HTTP response after a call to pollForStatus(), can be nil
352 latestResponse() *http.Response
355 type pollingTrackerBase struct {
356 // resp is the last response, either from the submission of the LRO or from polling
359 // method is the HTTP verb, this is needed for deserialization
360 Method string `json:"method"`
362 // rawBody is the raw JSON response body
363 rawBody map[string]interface{}
365 // denotes if polling is using async-operation or location header
366 Pm PollingMethodType `json:"pollingMethod"`
368 // the URL to poll for status
369 URI string `json:"pollingURI"`
371 // the state of the LRO as returned from the service
372 State string `json:"lroState"`
374 // the URL to GET for the final result
375 FinalGetURI string `json:"resultURI"`
377 // used to hold an error object returned from the service
378 Err *ServiceError `json:"error,omitempty"`
381 func (pt *pollingTrackerBase) initializeState() error {
382 // determine the initial polling state based on response body and/or HTTP status
383 // code. this is applicable to the initial LRO response, not polling responses!
384 pt.Method = pt.resp.Request.Method
385 if err := pt.updateRawBody(); err != nil {
388 switch pt.resp.StatusCode {
390 if ps := pt.getProvisioningState(); ps != nil {
393 pt.updateErrorFromResponse()
394 return pt.pollingError()
397 pt.State = operationSucceeded
399 case http.StatusCreated:
400 if ps := pt.getProvisioningState(); ps != nil {
403 pt.State = operationInProgress
405 case http.StatusAccepted:
406 pt.State = operationInProgress
407 case http.StatusNoContent:
408 pt.State = operationSucceeded
410 pt.State = operationFailed
411 pt.updateErrorFromResponse()
412 return pt.pollingError()
414 return pt.initPollingMethod()
417 func (pt pollingTrackerBase) getProvisioningState() *string {
418 if pt.rawBody != nil && pt.rawBody["properties"] != nil {
419 p := pt.rawBody["properties"].(map[string]interface{})
420 if ps := p["provisioningState"]; ps != nil {
428 func (pt *pollingTrackerBase) updateRawBody() error {
429 pt.rawBody = map[string]interface{}{}
430 if pt.resp.ContentLength != 0 {
431 defer pt.resp.Body.Close()
432 b, err := ioutil.ReadAll(pt.resp.Body)
434 return autorest.NewErrorWithError(err, "pollingTrackerBase", "updateRawBody", nil, "failed to read response body")
436 // observed in 204 responses over HTTP/2.0; the content length is -1 but body is empty
440 // put the body back so it's available to other callers
441 pt.resp.Body = ioutil.NopCloser(bytes.NewReader(b))
442 if err = json.Unmarshal(b, &pt.rawBody); err != nil {
443 return autorest.NewErrorWithError(err, "pollingTrackerBase", "updateRawBody", nil, "failed to unmarshal response body")
449 func (pt *pollingTrackerBase) pollForStatus(ctx context.Context, sender autorest.Sender) error {
450 req, err := http.NewRequest(http.MethodGet, pt.URI, nil)
452 return autorest.NewErrorWithError(err, "pollingTrackerBase", "pollForStatus", nil, "failed to create HTTP request")
455 req = req.WithContext(ctx)
456 pt.resp, err = sender.Do(req)
458 return autorest.NewErrorWithError(err, "pollingTrackerBase", "pollForStatus", nil, "failed to send HTTP request")
460 if autorest.ResponseHasStatusCode(pt.resp, pollingCodes[:]...) {
461 // reset the service error on success case
463 err = pt.updateRawBody()
465 // check response body for error content
466 pt.updateErrorFromResponse()
467 err = pt.pollingError()
472 // attempts to unmarshal a ServiceError type from the response body.
473 // if that fails then make a best attempt at creating something meaningful.
474 // NOTE: this assumes that the async operation has failed.
475 func (pt *pollingTrackerBase) updateErrorFromResponse() {
477 if pt.resp.ContentLength != 0 {
478 type respErr struct {
479 ServiceError *ServiceError `json:"error"`
482 defer pt.resp.Body.Close()
484 if b, err = ioutil.ReadAll(pt.resp.Body); err != nil || len(b) == 0 {
487 if err = json.Unmarshal(b, &re); err != nil {
490 // unmarshalling the error didn't yield anything, try unwrapped error
491 if re.ServiceError == nil {
492 err = json.Unmarshal(b, &re.ServiceError)
497 // the unmarshaller will ensure re.ServiceError is non-nil
498 // even if there was no content unmarshalled so check the code.
499 if re.ServiceError.Code != "" {
500 pt.Err = re.ServiceError
506 Code: pt.pollingStatus(),
507 Message: "The async operation failed.",
510 se.InnerError = make(map[string]interface{})
511 se.InnerError["unmarshalError"] = err.Error()
513 // stick the response body into the error object in hopes
514 // it contains something useful to help diagnose the failure.
515 if len(pt.rawBody) > 0 {
516 se.AdditionalInfo = []map[string]interface{}{
523 func (pt *pollingTrackerBase) updatePollingState(provStateApl bool) error {
524 if pt.Pm == PollingAsyncOperation && pt.rawBody["status"] != nil {
525 pt.State = pt.rawBody["status"].(string)
527 if pt.resp.StatusCode == http.StatusAccepted {
528 pt.State = operationInProgress
529 } else if provStateApl {
530 if ps := pt.getProvisioningState(); ps != nil {
533 pt.State = operationSucceeded
536 return autorest.NewError("pollingTrackerBase", "updatePollingState", "the response from the async operation has an invalid status code")
539 // if the operation has failed update the error state
541 pt.updateErrorFromResponse()
546 func (pt pollingTrackerBase) pollingError() error {
553 func (pt pollingTrackerBase) pollingMethod() PollingMethodType {
557 func (pt pollingTrackerBase) pollingStatus() string {
561 func (pt pollingTrackerBase) pollingURL() string {
565 func (pt pollingTrackerBase) finalGetURL() string {
566 return pt.FinalGetURI
569 func (pt pollingTrackerBase) hasTerminated() bool {
570 return strings.EqualFold(pt.State, operationCanceled) || strings.EqualFold(pt.State, operationFailed) || strings.EqualFold(pt.State, operationSucceeded)
573 func (pt pollingTrackerBase) hasFailed() bool {
574 return strings.EqualFold(pt.State, operationCanceled) || strings.EqualFold(pt.State, operationFailed)
577 func (pt pollingTrackerBase) hasSucceeded() bool {
578 return strings.EqualFold(pt.State, operationSucceeded)
581 func (pt pollingTrackerBase) latestResponse() *http.Response {
585 // error checking common to all trackers
586 func (pt pollingTrackerBase) baseCheckForErrors() error {
587 // for Azure-AsyncOperations the response body cannot be nil or empty
588 if pt.Pm == PollingAsyncOperation {
589 if pt.resp.Body == nil || pt.resp.ContentLength == 0 {
590 return autorest.NewError("pollingTrackerBase", "baseCheckForErrors", "for Azure-AsyncOperation response body cannot be nil")
592 if pt.rawBody["status"] == nil {
593 return autorest.NewError("pollingTrackerBase", "baseCheckForErrors", "missing status property in Azure-AsyncOperation response body")
599 // default initialization of polling URL/method. each verb tracker will update this as required.
600 func (pt *pollingTrackerBase) initPollingMethod() error {
601 if ao, err := getURLFromAsyncOpHeader(pt.resp); err != nil {
605 pt.Pm = PollingAsyncOperation
608 if lh, err := getURLFromLocationHeader(pt.resp); err != nil {
612 pt.Pm = PollingLocation
615 // it's ok if we didn't find a polling header, this will be handled elsewhere
621 type pollingTrackerDelete struct {
625 func (pt *pollingTrackerDelete) updatePollingMethod() error {
626 // for 201 the Location header is required
627 if pt.resp.StatusCode == http.StatusCreated {
628 if lh, err := getURLFromLocationHeader(pt.resp); err != nil {
631 return autorest.NewError("pollingTrackerDelete", "updateHeaders", "missing Location header in 201 response")
635 pt.Pm = PollingLocation
636 pt.FinalGetURI = pt.URI
638 // for 202 prefer the Azure-AsyncOperation header but fall back to Location if necessary
639 if pt.resp.StatusCode == http.StatusAccepted {
640 ao, err := getURLFromAsyncOpHeader(pt.resp)
645 pt.Pm = PollingAsyncOperation
647 // if the Location header is invalid and we already have a polling URL
648 // then we don't care if the Location header URL is malformed.
649 if lh, err := getURLFromLocationHeader(pt.resp); err != nil && pt.URI == "" {
654 pt.Pm = PollingLocation
656 // when both headers are returned we use the value in the Location header for the final GET
659 // make sure a polling URL was found
661 return autorest.NewError("pollingTrackerPost", "updateHeaders", "didn't get any suitable polling URLs in 202 response")
667 func (pt pollingTrackerDelete) checkForErrors() error {
668 return pt.baseCheckForErrors()
671 func (pt pollingTrackerDelete) provisioningStateApplicable() bool {
672 return pt.resp.StatusCode == http.StatusOK || pt.resp.StatusCode == http.StatusNoContent
677 type pollingTrackerPatch struct {
681 func (pt *pollingTrackerPatch) updatePollingMethod() error {
682 // by default we can use the original URL for polling and final GET
684 pt.URI = pt.resp.Request.URL.String()
686 if pt.FinalGetURI == "" {
687 pt.FinalGetURI = pt.resp.Request.URL.String()
689 if pt.Pm == PollingUnknown {
690 pt.Pm = PollingRequestURI
692 // for 201 it's permissible for no headers to be returned
693 if pt.resp.StatusCode == http.StatusCreated {
694 if ao, err := getURLFromAsyncOpHeader(pt.resp); err != nil {
698 pt.Pm = PollingAsyncOperation
701 // for 202 prefer the Azure-AsyncOperation header but fall back to Location if necessary
702 // note the absence of the "final GET" mechanism for PATCH
703 if pt.resp.StatusCode == http.StatusAccepted {
704 ao, err := getURLFromAsyncOpHeader(pt.resp)
709 pt.Pm = PollingAsyncOperation
712 if lh, err := getURLFromLocationHeader(pt.resp); err != nil {
715 return autorest.NewError("pollingTrackerPatch", "updateHeaders", "didn't get any suitable polling URLs in 202 response")
718 pt.Pm = PollingLocation
725 func (pt pollingTrackerPatch) checkForErrors() error {
726 return pt.baseCheckForErrors()
729 func (pt pollingTrackerPatch) provisioningStateApplicable() bool {
730 return pt.resp.StatusCode == http.StatusOK || pt.resp.StatusCode == http.StatusCreated
735 type pollingTrackerPost struct {
739 func (pt *pollingTrackerPost) updatePollingMethod() error {
740 // 201 requires Location header
741 if pt.resp.StatusCode == http.StatusCreated {
742 if lh, err := getURLFromLocationHeader(pt.resp); err != nil {
745 return autorest.NewError("pollingTrackerPost", "updateHeaders", "missing Location header in 201 response")
749 pt.Pm = PollingLocation
752 // for 202 prefer the Azure-AsyncOperation header but fall back to Location if necessary
753 if pt.resp.StatusCode == http.StatusAccepted {
754 ao, err := getURLFromAsyncOpHeader(pt.resp)
759 pt.Pm = PollingAsyncOperation
761 // if the Location header is invalid and we already have a polling URL
762 // then we don't care if the Location header URL is malformed.
763 if lh, err := getURLFromLocationHeader(pt.resp); err != nil && pt.URI == "" {
768 pt.Pm = PollingLocation
770 // when both headers are returned we use the value in the Location header for the final GET
773 // make sure a polling URL was found
775 return autorest.NewError("pollingTrackerPost", "updateHeaders", "didn't get any suitable polling URLs in 202 response")
781 func (pt pollingTrackerPost) checkForErrors() error {
782 return pt.baseCheckForErrors()
785 func (pt pollingTrackerPost) provisioningStateApplicable() bool {
786 return pt.resp.StatusCode == http.StatusOK || pt.resp.StatusCode == http.StatusNoContent
791 type pollingTrackerPut struct {
795 func (pt *pollingTrackerPut) updatePollingMethod() error {
796 // by default we can use the original URL for polling and final GET
798 pt.URI = pt.resp.Request.URL.String()
800 if pt.FinalGetURI == "" {
801 pt.FinalGetURI = pt.resp.Request.URL.String()
803 if pt.Pm == PollingUnknown {
804 pt.Pm = PollingRequestURI
806 // for 201 it's permissible for no headers to be returned
807 if pt.resp.StatusCode == http.StatusCreated {
808 if ao, err := getURLFromAsyncOpHeader(pt.resp); err != nil {
812 pt.Pm = PollingAsyncOperation
815 // for 202 prefer the Azure-AsyncOperation header but fall back to Location if necessary
816 if pt.resp.StatusCode == http.StatusAccepted {
817 ao, err := getURLFromAsyncOpHeader(pt.resp)
822 pt.Pm = PollingAsyncOperation
824 // if the Location header is invalid and we already have a polling URL
825 // then we don't care if the Location header URL is malformed.
826 if lh, err := getURLFromLocationHeader(pt.resp); err != nil && pt.URI == "" {
831 pt.Pm = PollingLocation
834 // make sure a polling URL was found
836 return autorest.NewError("pollingTrackerPut", "updateHeaders", "didn't get any suitable polling URLs in 202 response")
842 func (pt pollingTrackerPut) checkForErrors() error {
843 err := pt.baseCheckForErrors()
847 // if there are no LRO headers then the body cannot be empty
848 ao, err := getURLFromAsyncOpHeader(pt.resp)
852 lh, err := getURLFromLocationHeader(pt.resp)
856 if ao == "" && lh == "" && len(pt.rawBody) == 0 {
857 return autorest.NewError("pollingTrackerPut", "checkForErrors", "the response did not contain a body")
862 func (pt pollingTrackerPut) provisioningStateApplicable() bool {
863 return pt.resp.StatusCode == http.StatusOK || pt.resp.StatusCode == http.StatusCreated
866 // creates a polling tracker based on the verb of the original request
867 func createPollingTracker(resp *http.Response) (pollingTracker, error) {
868 var pt pollingTracker
869 switch strings.ToUpper(resp.Request.Method) {
870 case http.MethodDelete:
871 pt = &pollingTrackerDelete{pollingTrackerBase: pollingTrackerBase{resp: resp}}
872 case http.MethodPatch:
873 pt = &pollingTrackerPatch{pollingTrackerBase: pollingTrackerBase{resp: resp}}
874 case http.MethodPost:
875 pt = &pollingTrackerPost{pollingTrackerBase: pollingTrackerBase{resp: resp}}
877 pt = &pollingTrackerPut{pollingTrackerBase: pollingTrackerBase{resp: resp}}
879 return nil, autorest.NewError("azure", "createPollingTracker", "unsupported HTTP method %s", resp.Request.Method)
881 if err := pt.initializeState(); err != nil {
884 // this initializes the polling header values, we do this during creation in case the
885 // initial response send us invalid values; this way the API call will return a non-nil
886 // error (not doing this means the error shows up in Future.Done)
887 return pt, pt.updatePollingMethod()
890 // gets the polling URL from the Azure-AsyncOperation header.
891 // ensures the URL is well-formed and absolute.
892 func getURLFromAsyncOpHeader(resp *http.Response) (string, error) {
893 s := resp.Header.Get(http.CanonicalHeaderKey(headerAsyncOperation))
898 return "", autorest.NewError("azure", "getURLFromAsyncOpHeader", "invalid polling URL '%s'", s)
903 // gets the polling URL from the Location header.
904 // ensures the URL is well-formed and absolute.
905 func getURLFromLocationHeader(resp *http.Response) (string, error) {
906 s := resp.Header.Get(http.CanonicalHeaderKey(autorest.HeaderLocation))
911 return "", autorest.NewError("azure", "getURLFromLocationHeader", "invalid polling URL '%s'", s)
916 // verify that the URL is valid and absolute
917 func isValidURL(s string) bool {
918 u, err := url.Parse(s)
919 return err == nil && u.IsAbs()
922 // DoPollForAsynchronous returns a SendDecorator that polls if the http.Response is for an Azure
923 // long-running operation. It will delay between requests for the duration specified in the
924 // RetryAfter header or, if the header is absent, the passed delay. Polling may be canceled via
925 // the context associated with the http.Request.
926 // Deprecated: Prefer using Futures to allow for non-blocking async operations.
927 func DoPollForAsynchronous(delay time.Duration) autorest.SendDecorator {
928 return func(s autorest.Sender) autorest.Sender {
929 return autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
934 if !autorest.ResponseHasStatusCode(resp, pollingCodes[:]...) {
937 future, err := NewFutureFromResponse(resp)
941 // retry until either the LRO completes or we receive an error
943 for done, err = future.Done(s); !done && err == nil; done, err = future.Done(s) {
944 // check for Retry-After delay, if not present use the specified polling delay
945 if pd, ok := future.GetPollingDelay(); ok {
948 // wait until the delay elapses or the context is cancelled
949 if delayElapsed := autorest.DelayForBackoff(delay, 0, r.Context().Done()); !delayElapsed {
950 return future.Response(),
951 autorest.NewErrorWithError(r.Context().Err(), "azure", "DoPollForAsynchronous", future.Response(), "context has been cancelled")
954 return future.Response(), err
959 // PollingMethodType defines a type used for enumerating polling mechanisms.
960 type PollingMethodType string
963 // PollingAsyncOperation indicates the polling method uses the Azure-AsyncOperation header.
964 PollingAsyncOperation PollingMethodType = "AsyncOperation"
966 // PollingLocation indicates the polling method uses the Location header.
967 PollingLocation PollingMethodType = "Location"
969 // PollingRequestURI indicates the polling method uses the original request URI.
970 PollingRequestURI PollingMethodType = "RequestURI"
972 // PollingUnknown indicates an unknown polling method and is the default value.
973 PollingUnknown PollingMethodType = ""
976 // AsyncOpIncompleteError is the type that's returned from a future that has not completed.
977 type AsyncOpIncompleteError struct {
978 // FutureType is the name of the type composed of a azure.Future.
982 // Error returns an error message including the originating type name of the error.
983 func (e AsyncOpIncompleteError) Error() string {
984 return fmt.Sprintf("%s: asynchronous operation has not completed", e.FutureType)
987 // NewAsyncOpIncompleteError creates a new AsyncOpIncompleteError with the specified parameters.
988 func NewAsyncOpIncompleteError(futureType string) AsyncOpIncompleteError {
989 return AsyncOpIncompleteError{
990 FutureType: futureType,