Skip to content
This repository was archived by the owner on Apr 18, 2023. It is now read-only.

Commit 8d361f1

Browse files
authored
Merge pull request #3 from microsoftgraph/vidadhee/RetryHandler
Add Retry Handler
2 parents 58b0665 + e39f7c3 commit 8d361f1

File tree

11 files changed

+380
-4
lines changed

11 files changed

+380
-4
lines changed

MSGraphCoreSDK/MSGraphCoreSDK.xcodeproj/project.pbxproj

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
E5069AD9211C1AD500432372 /* MSGraphCoreSDKTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E5069AD8211C1AD500432372 /* MSGraphCoreSDKTests.m */; };
1313
E5069ADB211C1AD500432372 /* MSGraphCoreSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E5DC73DA211412BF0040BAB6 /* MSGraphCoreSDK.framework */; };
1414
E5069AE2211C323D00432372 /* MSURLSessionManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E5069AE1211C323D00432372 /* MSURLSessionManagerTests.m */; };
15+
E51E616C216DE4D300F1B3C0 /* MSRetryHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = E51E616A216DE4D300F1B3C0 /* MSRetryHandler.h */; settings = {ATTRIBUTES = (Public, ); }; };
16+
E51E616D216DE4D300F1B3C0 /* MSRetryHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = E51E616B216DE4D300F1B3C0 /* MSRetryHandler.m */; };
17+
E51E616F216E073F00F1B3C0 /* MSRetryHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E51E616E216E073F00F1B3C0 /* MSRetryHandlerTests.m */; };
1518
E52AD978212D27DC005FED47 /* MSURLSessionDataTask.m in Sources */ = {isa = PBXBuildFile; fileRef = E52AD970212D27DB005FED47 /* MSURLSessionDataTask.m */; };
1619
E52AD979212D27DC005FED47 /* MSURLSessionTask.m in Sources */ = {isa = PBXBuildFile; fileRef = E52AD971212D27DB005FED47 /* MSURLSessionTask.m */; };
1720
E52AD97A212D27DC005FED47 /* MSURLSessionUploadTask.h in Headers */ = {isa = PBXBuildFile; fileRef = E52AD972212D27DB005FED47 /* MSURLSessionUploadTask.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -71,6 +74,9 @@
7174
E5069AD8211C1AD500432372 /* MSGraphCoreSDKTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSGraphCoreSDKTests.m; sourceTree = "<group>"; };
7275
E5069ADA211C1AD500432372 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
7376
E5069AE1211C323D00432372 /* MSURLSessionManagerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSURLSessionManagerTests.m; sourceTree = "<group>"; };
77+
E51E616A216DE4D300F1B3C0 /* MSRetryHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSRetryHandler.h; sourceTree = "<group>"; };
78+
E51E616B216DE4D300F1B3C0 /* MSRetryHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSRetryHandler.m; sourceTree = "<group>"; };
79+
E51E616E216E073F00F1B3C0 /* MSRetryHandlerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSRetryHandlerTests.m; sourceTree = "<group>"; };
7480
E52AD970212D27DB005FED47 /* MSURLSessionDataTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSURLSessionDataTask.m; sourceTree = "<group>"; };
7581
E52AD971212D27DB005FED47 /* MSURLSessionTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSURLSessionTask.m; sourceTree = "<group>"; };
7682
E52AD972212D27DB005FED47 /* MSURLSessionUploadTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSURLSessionUploadTask.h; sourceTree = "<group>"; };
@@ -156,6 +162,7 @@
156162
E5069AC3211AACCD00432372 /* Implementations */ = {
157163
isa = PBXGroup;
158164
children = (
165+
E5190C7F216DE371001699B5 /* RetryHandler */,
159166
E5ECE0A12146655000A289C2 /* RedirectHandler */,
160167
E5069AC4211AACED00432372 /* Authentication */,
161168
E5DC73ED211418040040BAB6 /* HTTPProvider */,
@@ -198,6 +205,15 @@
198205
path = MSGraphCoreSDKTests;
199206
sourceTree = "<group>";
200207
};
208+
E5190C7F216DE371001699B5 /* RetryHandler */ = {
209+
isa = PBXGroup;
210+
children = (
211+
E51E616A216DE4D300F1B3C0 /* MSRetryHandler.h */,
212+
E51E616B216DE4D300F1B3C0 /* MSRetryHandler.m */,
213+
);
214+
path = RetryHandler;
215+
sourceTree = "<group>";
216+
};
201217
E52AD96F212D271C005FED47 /* Session Tasks */ = {
202218
isa = PBXGroup;
203219
children = (
@@ -257,6 +273,7 @@
257273
E573EE0A213427ED00C5E884 /* MSURLSessionTaskDelegateTests.m */,
258274
E566782D2150BC4F00C6B7AE /* MSRedirectHandlerTests.m */,
259275
E566782F2150E1AF00C6B7AE /* MSAuthenticationHandlerTests.m */,
276+
E51E616E216E073F00F1B3C0 /* MSRetryHandlerTests.m */,
260277
);
261278
path = Implementations;
262279
sourceTree = "<group>";
@@ -399,6 +416,7 @@
399416
E5DC7406211418050040BAB6 /* MSURLSessionTaskDelegate.h in Headers */,
400417
E5ECE09721427A6400A289C2 /* MSMiddlewareFactory.h in Headers */,
401418
E5ECE0A42146657900A289C2 /* MSRedirectHandler.h in Headers */,
419+
E51E616C216DE4D300F1B3C0 /* MSRetryHandler.h in Headers */,
402420
E5DC73DF211412BF0040BAB6 /* MSGraphCoreSDK.h in Headers */,
403421
);
404422
runOnlyForDeploymentPostprocessing = 0;
@@ -503,6 +521,7 @@
503521
buildActionMask = 2147483647;
504522
files = (
505523
E573EE0D213525BA00C5E884 /* MSGraphWorkloadsTests.m in Sources */,
524+
E51E616F216E073F00F1B3C0 /* MSRetryHandlerTests.m in Sources */,
506525
E573EE072133FFF200C5E884 /* MSHTTPClientTests.m in Sources */,
507526
E52AD9E6212E8845005FED47 /* MSGraphCoreSDKTests.h in Sources */,
508527
E56678302150E1AF00C6B7AE /* MSAuthenticationHandlerTests.m in Sources */,
@@ -530,6 +549,7 @@
530549
E52AD97F212D27DC005FED47 /* MSURLSessionDownloadTask.m in Sources */,
531550
E5DC7405211418050040BAB6 /* MSURLSessionManager.m in Sources */,
532551
E52AD979212D27DC005FED47 /* MSURLSessionTask.m in Sources */,
552+
E51E616D216DE4D300F1B3C0 /* MSRetryHandler.m in Sources */,
533553
E5DC7408211418050040BAB6 /* MSURLSessionTaskDelegate.m in Sources */,
534554
E52AD97E212D27DC005FED47 /* MSURLSessionUploadTask.m in Sources */,
535555
E52AD978212D27DC005FED47 /* MSURLSessionDataTask.m in Sources */,

MSGraphCoreSDK/MSGraphCoreSDK/Common/MSConstants.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,14 @@ extern NSString *const MSErrorDomain;
5151
extern NSString *const MSErrorTooManyRedirectsFormatString;
5252
extern NSString *const MSErrorLocationHeaderNotFoundString;
5353
extern NSString *const MSErrorOperationUnsuccessfulString;
54+
55+
extern NSString *const MSErrorTooManyRetries;
56+
extern NSString *const MSErrorTooManyRetriesFormatString;
57+
58+
extern NSString *const HTTPMethodGet;
59+
extern NSString *const HTTPMethodPut;
60+
extern NSString *const HTTPMethodPatch;
61+
extern NSString *const HTTPMethodPost;
62+
extern NSString *const HTTPMethodDelete;
63+
5464
#endif

MSGraphCoreSDK/MSGraphCoreSDK/Common/MSConstants.m

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,18 @@
1010

1111
NSString *const MSErrorDomain = @"com.microsoft.graph.errors";
1212

13-
NSString *const MSErrorTooManyRedirectsFormatString = @"More than %d redirects encountered while sending the request.";
13+
NSString *const MSErrorTooManyRedirectsFormatString = @"More than %ld redirects encountered while sending the request.";
1414

1515
NSString *const MSErrorLocationHeaderNotFoundString = @"There is no location header in the redirect response.";
1616
NSString *const MSErrorOperationUnsuccessfulString = @"Operation was unsuccessful.";
17+
18+
NSString *const MSErrorTooManyRetries = @"Too many retries";
19+
20+
NSString *const MSErrorTooManyRetriesFormatString = @"More than %ld retries encountered while sending the request.";
21+
22+
23+
NSString *const HTTPMethodGet = @"GET";
24+
NSString *const HTTPMethodPut = @"PUT";
25+
NSString *const HTTPMethodPatch = @"PATCH";
26+
NSString *const HTTPMethodPost = @"POST";
27+
NSString *const HTTPMethodDelete = @"DELETE";

MSGraphCoreSDK/MSGraphCoreSDK/HTTPClient/MSClientFactory.m

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#import "MSURLSessionManager.h"
88
#import "MSMiddlewareFactory.h"
99
#import "MSRedirectHandler.h"
10+
#import "MSRetryHandler.h"
1011

1112
@implementation MSClientFactory
1213

@@ -20,10 +21,12 @@ +(MSHTTPClient *)createHTTPClientWithAuthenticationProvider:(id<MSAuthentication
2021
MSAuthenticationHandler *authenticationHandler = (MSAuthenticationHandler *)[MSMiddlewareFactory createMiddleware:MSMiddlewareTypeAuthentication];
2122
authenticationHandler.authProvider = authenticationProvider;
2223
MSRedirectHandler *redirectHandler = (MSRedirectHandler *)[MSMiddlewareFactory createMiddleware:MSMiddlewareTypeRedirect];
24+
MSRetryHandler *retryHandler = (MSRetryHandler *)[MSMiddlewareFactory createMiddleware:MSMiddlewareTypeRetry];
2325
MSURLSessionManager *sessionManager = (MSURLSessionManager *)[MSMiddlewareFactory createMiddleware:MSMiddlewareTypeHTTP];
2426
//Creating a default chain
2527
[authenticationHandler setNext:redirectHandler];
26-
[redirectHandler setNext:sessionManager];
28+
[redirectHandler setNext:retryHandler];
29+
[retryHandler setNext:sessionManager];
2730

2831
return [MSClientFactory createHTTPClientWithMiddleware:authenticationHandler];
2932
}

MSGraphCoreSDK/MSGraphCoreSDK/HTTPClient/MSMiddlewareFactory.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
typedef NS_ENUM(NSInteger, MSMiddlewareType)
1414
{
1515
MSMiddlewareTypeAuthentication,
16+
MSMiddlewareTypeHTTP,
1617
MSMiddlewareTypeRedirect,
17-
MSMiddlewareTypeHTTP
18+
MSMiddlewareTypeRetry
1819
};
1920

2021
/*

MSGraphCoreSDK/MSGraphCoreSDK/HTTPClient/MSMiddlewareFactory.m

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#import "MSURLSessionManager.h"
77
#import "MSAuthenticationHandler.h"
88
#import "MSRedirectHandler.h"
9+
#import "MSRetryHandler.h"
910

1011
@implementation MSMiddlewareFactory
1112

@@ -28,6 +29,11 @@ @implementation MSMiddlewareFactory
2829
MSAuthenticationHandler *authenticationHandler = [[MSAuthenticationHandler alloc] init];
2930
return authenticationHandler;
3031
}
32+
case MSMiddlewareTypeRetry:
33+
{
34+
MSRetryHandler *retryHandler = [[MSRetryHandler alloc] init];
35+
return retryHandler;
36+
}
3137
default:
3238
break;
3339
}

MSGraphCoreSDK/MSGraphCoreSDK/MSGraphCoreSDK.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ FOUNDATION_EXPORT const unsigned char MSGraphSDKVersionString[];
2828
#import "MSGraphCoreSDK/MSURLSessionTaskDelegate.h"
2929
#import "MSGraphCoreSDK/MSAuthenticationHandler.h"
3030
#import "MSGraphCoreSDK/MSRedirectHandler.h"
31+
#import "MSGraphCoreSDK/MSRetryHandler.h"
3132

3233
#import "MSGraphCoreSDK/MSURLSessionTask.h"
3334
#import "MSGraphCoreSDK/MSURLSessionDataTask.h"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
3+
//
4+
5+
#import <Foundation/Foundation.h>
6+
#import "MSGraphMiddleware.h"
7+
8+
/*
9+
This class provides the mechanism to handle retry response codes from Graph server. It is implmented as a middleware so it will be called during the execution of network calls.
10+
*/
11+
12+
@interface MSRetryHandler : NSObject<MSGraphMiddleware>
13+
14+
@end
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
//
2+
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
3+
//
4+
5+
#import "MSRetryHandler.h"
6+
#import "MSURLSessionTask.h"
7+
#import "MSConstants.h"
8+
9+
NSString * const RETRY_AFTER = @"Retry-After";
10+
NSString * const RETRY_ATTEMPT = @"Retry-Attempt";
11+
NSString * const TRANSFER_ENCODING = @"Transfer-Encoding";
12+
13+
#define DELAY_SECONDS 10;
14+
#define DEFAULT_MAX_RETRIES 10;
15+
#define DEFAULT_RETRY_POWER 1;
16+
17+
@interface MSURLSessionTask()
18+
19+
-(void)setRequest:(NSMutableURLRequest *)request;
20+
21+
@end
22+
23+
@interface MSRetryHandler()
24+
25+
@property (nonatomic, strong) id<MSGraphMiddleware> nextMiddleware;
26+
27+
@end
28+
29+
@implementation MSRetryHandler
30+
{
31+
NSInteger maxRetries;
32+
double retryPower;
33+
}
34+
35+
- (instancetype)init
36+
{
37+
self = [super init];
38+
if(self)
39+
{
40+
maxRetries = DEFAULT_MAX_RETRIES;
41+
retryPower = DEFAULT_RETRY_POWER;
42+
}
43+
return self;
44+
}
45+
46+
#pragma mark - MSGraphMiddleware method implmentation
47+
- (void)execute:(MSURLSessionTask *)task withCompletionHandler:(HTTPRequestCompletionHandler)completionHandler
48+
{
49+
[self execute:task retriesAttempted:0 withCompletionHandler:completionHandler];
50+
}
51+
52+
- (void)setNext:(id<MSGraphMiddleware>)nextMiddleware
53+
{
54+
if(_nextMiddleware)
55+
{
56+
[nextMiddleware setNext:_nextMiddleware];
57+
}
58+
_nextMiddleware = nextMiddleware;
59+
60+
}
61+
62+
#pragma mark - Retry Handler methods
63+
- (void)execute:(MSURLSessionTask *)task retriesAttempted:(NSInteger)retriesAttempted withCompletionHandler:(HTTPRequestCompletionHandler)completionHandler
64+
{
65+
__block NSInteger localRetriesAttempted = retriesAttempted;
66+
NSInteger localMaxRetriesAllowed = maxRetries;
67+
__block MSURLSessionTask *blockTask = task;
68+
[self.nextMiddleware execute:blockTask withCompletionHandler:^(id data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
69+
if(response && [response isKindOfClass:[NSHTTPURLResponse class]])
70+
{
71+
if([self isRetry:[(NSHTTPURLResponse *)response statusCode]] && [self isBuffered:blockTask.request forResponse:(NSHTTPURLResponse *)response])
72+
{
73+
//Return an error if maximum retry attempt limit is reached
74+
if(localRetriesAttempted==localMaxRetriesAllowed)
75+
{
76+
NSDictionary *userInfo = @{
77+
NSLocalizedDescriptionKey: MSErrorTooManyRetries,
78+
NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:MSErrorTooManyRetriesFormatString,(long)localRetriesAttempted]
79+
};
80+
completionHandler(data, response, [NSError errorWithDomain:MSErrorDomain code:[(NSHTTPURLResponse *)response statusCode] userInfo:userInfo]);
81+
return ;
82+
}
83+
84+
//Get the delay before next retry attempt
85+
double delay = [self retryDelay:(NSHTTPURLResponse *)response andRetryCount:localRetriesAttempted];
86+
87+
localRetriesAttempted++;
88+
89+
//Set Retry-Attempt header on request to track how many requests were made via retry handler
90+
[blockTask setRequest:[self updateRetryAttemptHeader:blockTask.request withRetryAttemptCount:localRetriesAttempted]];
91+
92+
//Dispatch the execution of next retry on Global queue after the delay
93+
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC);
94+
dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
95+
//code to be executed on the global queue after delay
96+
[self execute:blockTask retriesAttempted:localRetriesAttempted withCompletionHandler:completionHandler];
97+
});
98+
}
99+
else
100+
{
101+
completionHandler(data, response, error);
102+
}
103+
}
104+
else
105+
{
106+
completionHandler(data, response, error);
107+
}
108+
}];
109+
}
110+
111+
- (BOOL)isRetry:(NSInteger)statusCode
112+
{
113+
if (statusCode == MSClientErrorCodeTooManyRequests ||
114+
statusCode == MSClientErrorCodeInternalServerError)
115+
{
116+
return true;
117+
}
118+
return false;
119+
}
120+
121+
- (BOOL)isBuffered:(NSURLRequest *)request forResponse:(NSHTTPURLResponse *)response
122+
{
123+
NSData *requestBody = request.HTTPBody;
124+
125+
BOOL isTransferEncodingChunked = [response.allHeaderFields valueForKey:TRANSFER_ENCODING] && [[response.allHeaderFields valueForKey:TRANSFER_ENCODING] isEqualToString:@"chunked"];
126+
127+
BOOL isHTTPMethodPutPatchOrPost = ([request.HTTPMethod isEqualToString:HTTPMethodPut] || [request.HTTPMethod isEqualToString:HTTPMethodPatch] || [request.HTTPMethod isEqualToString:HTTPMethodPost]);
128+
129+
if(isHTTPMethodPutPatchOrPost && requestBody && isTransferEncodingChunked)
130+
{
131+
return false;
132+
}
133+
return true;
134+
}
135+
136+
- (double)retryDelay:(NSHTTPURLResponse *)response andRetryCount:(NSInteger)retryCount
137+
{
138+
double delay = 0;
139+
140+
NSDictionary *responseHeaders = [response allHeaderFields];
141+
if(responseHeaders && [responseHeaders objectForKey:RETRY_AFTER])
142+
{
143+
delay = [[responseHeaders objectForKey:RETRY_AFTER] doubleValue];
144+
}
145+
else
146+
{
147+
retryPower = pow(2, retryCount);
148+
delay = retryPower*DELAY_SECONDS;
149+
}
150+
return delay;
151+
}
152+
153+
- (NSMutableURLRequest *)updateRetryAttemptHeader:(NSMutableURLRequest *)request withRetryAttemptCount:(NSInteger)retryAttempt
154+
{
155+
[request setValue:[NSString stringWithFormat:@"%ld",(long)retryAttempt] forHTTPHeaderField:RETRY_ATTEMPT];
156+
return request;
157+
}
158+
159+
@end

MSGraphCoreSDK/MSGraphCoreSDKTests/MSGraphWorkloadsTests.m

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,12 @@ - (void)setUp {
4040

4141
MSURLSessionManager *sessionManager = [[MSURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
4242
MSRedirectHandler *redirectHandler = [[MSRedirectHandler alloc] init];
43+
MSRetryHandler *retryHandler = [[MSRetryHandler alloc] init];
4344
MSAuthenticationHandler *authHandler = [[MSAuthenticationHandler alloc] init];
4445
authHandler.authProvider = self.mockAuthProvider;
4546
[authHandler setNext:redirectHandler];
46-
[redirectHandler setNext:sessionManager];
47+
[redirectHandler setNext:retryHandler];
48+
[retryHandler setNext:sessionManager];
4749

4850
_httpClient = [MSClientFactory createHTTPClientWithMiddleware:authHandler];
4951

0 commit comments

Comments
 (0)