UNPKG

3.35 kBPlain TextView Raw
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT License. See License.txt in the project root for license information.
3
4import {
5 BaseRequestPolicy,
6 RequestPolicy,
7 RequestPolicyOptionsLike,
8 RequestPolicyFactory,
9} from "./requestPolicy";
10import { WebResourceLike } from "../webResource";
11import { HttpOperationResponse } from "../httpOperationResponse";
12import { Constants } from "../util/constants";
13import { delay } from "../util/utils";
14
15const StatusCodes = Constants.HttpConstants.StatusCodes;
16const DEFAULT_RETRY_COUNT = 3;
17
18/**
19 * Options that control how to retry on response status code 429.
20 */
21export interface ThrottlingRetryOptions {
22 /**
23 * The maximum number of retry attempts. Defaults to 3.
24 */
25 maxRetries?: number;
26}
27
28export function throttlingRetryPolicy(
29 maxRetries: number = DEFAULT_RETRY_COUNT
30): RequestPolicyFactory {
31 return {
32 create: (nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike) => {
33 return new ThrottlingRetryPolicy(nextPolicy, options, maxRetries);
34 },
35 };
36}
37
38/**
39 * To learn more, please refer to
40 * https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-manager-request-limits,
41 * https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits and
42 * https://docs.microsoft.com/en-us/azure/virtual-machines/troubleshooting/troubleshooting-throttling-errors
43 */
44export class ThrottlingRetryPolicy extends BaseRequestPolicy {
45 private retryLimit: number;
46
47 constructor(nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike, retryLimit: number) {
48 super(nextPolicy, options);
49 this.retryLimit = retryLimit;
50 }
51
52 public async sendRequest(httpRequest: WebResourceLike): Promise<HttpOperationResponse> {
53 return this._nextPolicy.sendRequest(httpRequest.clone()).then((response) => {
54 return this.retry(httpRequest, response, 0);
55 });
56 }
57
58 private async retry(
59 httpRequest: WebResourceLike,
60 httpResponse: HttpOperationResponse,
61 retryCount: number
62 ): Promise<HttpOperationResponse> {
63 if (httpResponse.status !== StatusCodes.TooManyRequests) {
64 return httpResponse;
65 }
66
67 const retryAfterHeader: string | undefined = httpResponse.headers.get(
68 Constants.HeaderConstants.RETRY_AFTER
69 );
70
71 if (retryAfterHeader && retryCount < this.retryLimit) {
72 const delayInMs: number | undefined = ThrottlingRetryPolicy.parseRetryAfterHeader(
73 retryAfterHeader
74 );
75 if (delayInMs) {
76 await delay(delayInMs);
77 const res = await this._nextPolicy.sendRequest(httpRequest);
78 return this.retry(httpRequest, res, retryCount + 1);
79 }
80 }
81
82 return httpResponse;
83 }
84
85 public static parseRetryAfterHeader(headerValue: string): number | undefined {
86 const retryAfterInSeconds = Number(headerValue);
87 if (Number.isNaN(retryAfterInSeconds)) {
88 return ThrottlingRetryPolicy.parseDateRetryAfterHeader(headerValue);
89 } else {
90 return retryAfterInSeconds * 1000;
91 }
92 }
93
94 public static parseDateRetryAfterHeader(headerValue: string): number | undefined {
95 try {
96 const now: number = Date.now();
97 const date: number = Date.parse(headerValue);
98 const diff = date - now;
99
100 return Number.isNaN(diff) ? undefined : diff;
101 } catch (error) {
102 return undefined;
103 }
104 }
105}