Source/TNLRequestRetryPolicyConfiguration.m (316 lines of code) (raw):
//
// TNLRequestRetryPolicyConfiguration.m
// TwitterNetworkLayer
//
// Created on 5/26/14.
// Copyright © 2020 Twitter, Inc. All rights reserved.
//
#import "TNL_Project.h"
#import "TNLRequest.h"
#import "TNLRequestOperation.h"
#import "TNLRequestRetryPolicyConfiguration.h"
#import "TNLResponse.h"
NS_ASSUME_NONNULL_BEGIN
static NSArray<NSString *> * _GenerateMethodStrings(NSUInteger methodMask);
static NSUInteger _GenerateMethodMask(NSArray * __nullable methods);
static NSMutableIndexSet * _GenerateStatusCodes(NSArray<NSNumber *> * __nullable statusCodes);
static NSMutableIndexSet * _GenerateURLErrorCodes(NSArray<NSNumber *> * __nullable URLErrorCodes);
static NSMutableIndexSet * _GeneratePOSIXErrorCodes(NSArray<NSNumber *> * __nullable POSIXErrorCodes);
NS_INLINE NSUInteger _POSIXErrorCodeToIndex(int code)
{
return (NSUInteger)code;
}
//NS_INLINE int _POSIXErrorCodeFromIndex(NSUInteger index)
//{
// return (int)index;
//}
NS_INLINE NSUInteger _URLErrorCodeToIndex(NSInteger code)
{
// NSURL error codes are all negative values
// But we'll store in a postive index set
if (code < 0) {
code *= -1;
}
return (NSUInteger)code;
}
//NS_INLINE NSInteger _URLErrorCodeFromIndex(NSUInteger index)
//{
// const NSInteger codeInt = (NSInteger)index;
// return codeInt * -1;
//}
NS_INLINE NSUInteger _HTTPStatusCodeToIndex(TNLHTTPStatusCode code)
{
return (NSUInteger)code;
}
@interface TNLRequestRetryPolicyConfiguration ()
@property (nonatomic, readonly, nullable) NSIndexSet *POSIXErrorCodes;
@property (nonatomic, readonly, nullable) NSIndexSet *URLErrorCodes;
@property (nonatomic, readonly, nullable) NSIndexSet *statusCodes;
@property (nonatomic, readonly) NSUInteger methodMask;
- (instancetype)initWithMethodMask:(NSUInteger)methodMask
statusCodes:(nullable NSIndexSet *)statusCodes
URLErrorCodes:(nullable NSIndexSet *)URLErrorCodes
POSIXErrorCodes:(nullable NSIndexSet *)POSIXErrorCodes;
+ (BOOL)tnl_isMutableClass;
@end
@implementation TNLRequestRetryPolicyConfiguration
{
@protected
NSUInteger _methodMask;
NSIndexSet *_statusCodes;
NSIndexSet *_URLErrorCodes;
NSIndexSet *_POSIXErrorCodes;
}
@synthesize methodMask = _methodMask;
@synthesize statusCodes = _statusCodes;
@synthesize URLErrorCodes = _URLErrorCodes;
@synthesize POSIXErrorCodes = _POSIXErrorCodes;
+ (BOOL)tnl_isMutableClass
{
return NO;
}
+ (instancetype)defaultConfiguration
{
return [[self alloc] initWithRetriableMethods:@[@"GET"]
statusCodes:@[@503]
URLErrorCodes:nil
POSIXErrorCodes:nil];
}
+ (instancetype)standardConfiguration
{
return [[self alloc] initWithRetriableMethods:@[@"GET"]
statusCodes:@[@503]
URLErrorCodes:TNLStandardRetriableURLErrorCodes()
POSIXErrorCodes:TNLStandardRetriablePOSIXErrorCodes()];
}
- (instancetype)initWithRetriableMethods:(nullable NSArray *)methods
statusCodes:(nullable NSArray<NSNumber *> *)statusCodes
URLErrorCodes:(nullable NSArray<NSNumber *> *)URLErrorCodes
POSIXErrorCodes:(nullable NSArray<NSNumber *> *)POSIXErrorCodes
{
return [self initWithMethodMask:_GenerateMethodMask(methods)
statusCodes:_GenerateStatusCodes(statusCodes)
URLErrorCodes:_GenerateURLErrorCodes(URLErrorCodes)
POSIXErrorCodes:_GeneratePOSIXErrorCodes(POSIXErrorCodes)];
}
- (instancetype)initWithAllMethodsRetriableAndRetriableStatusCodes:(nullable NSArray<NSNumber *> *)statusCodes URLErrorCodes:(nullable NSArray<NSNumber *> *)URLErrorCodes POSIXErrorCodes:(nullable NSArray<NSNumber *> *)POSIXErrorCodes
{
return [self initWithMethodMask:NSUIntegerMax
statusCodes:_GenerateStatusCodes(statusCodes)
URLErrorCodes:_GenerateURLErrorCodes(URLErrorCodes)
POSIXErrorCodes:_GeneratePOSIXErrorCodes(POSIXErrorCodes)];
}
- (instancetype)initWithMethodMask:(NSUInteger)methodMask
statusCodes:(nullable NSIndexSet *)statusCodes
URLErrorCodes:(nullable NSIndexSet *)URLErrorCodes
POSIXErrorCodes:(nullable NSIndexSet *)POSIXErrorCodes
{
// Internal only method so we will just assign/retain instead of copy
if (self = [super init]) {
_methodMask = methodMask;
const BOOL mutable = [[self class] tnl_isMutableClass];
_statusCodes = (mutable) ? [statusCodes mutableCopy] : [statusCodes copy];
_URLErrorCodes = (mutable) ? [URLErrorCodes mutableCopy] : [URLErrorCodes copy];
_POSIXErrorCodes = (mutable) ? [POSIXErrorCodes mutableCopy] : [POSIXErrorCodes copy];
}
return self;
}
- (instancetype)init
{
return [self initWithMethodMask:0 statusCodes:nil URLErrorCodes:nil POSIXErrorCodes:nil];
}
- (BOOL)methodCanBeRetried:(TNLHTTPMethod)method
{
const NSUInteger mask = (1 << method);
return TNL_BITMASK_HAS_SUBSET_FLAGS(self.methodMask, mask);
}
- (BOOL)statusCodeCanBeRetried:(TNLHTTPStatusCode)code
{
return [self.statusCodes containsIndex:_HTTPStatusCodeToIndex(code)];
}
- (BOOL)URLErrorCodeCanBeRetried:(NSInteger)code
{
return [self.URLErrorCodes containsIndex:_URLErrorCodeToIndex(code)];
}
- (BOOL)POSIXErrorCodeCanBeRetried:(int)code
{
return [self.POSIXErrorCodes containsIndex:_POSIXErrorCodeToIndex(code)];
}
- (BOOL)requestCanBeRetriedForResponse:(TNLResponse *)response
{
TNLResponseInfo *info = response.info;
// check if the method can be retried
if (![self methodCanBeRetried:TNLRequestGetHTTPMethodValue(info.finalURLRequest)]) {
return NO;
}
// can we retry this status code?
if ([self statusCodeCanBeRetried:info.statusCode]) {
return YES;
}
// was there an error?
NSError *error = response.operationError;
if (error) {
// can we retry this error?
if ([self _tnl_errorCanBeRetried:error]) {
return YES;
}
}
// can't retry
return NO;
}
- (BOOL)_tnl_errorCanBeRetried:(NSError *)error
{
if ([error.domain isEqualToString:NSURLErrorDomain]) {
if ([self URLErrorCodeCanBeRetried:error.code]) {
return YES;
}
} else if ([error.domain isEqualToString:NSPOSIXErrorDomain]) {
if ([self POSIXErrorCodeCanBeRetried:(int)error.code]) {
return YES;
}
}
return NO;
}
- (id)copyWithZone:(nullable NSZone *)zone
{
return self;
}
- (id)mutableCopyWithZone:(nullable NSZone *)zone
{
return [[TNLMutableRequestRetryPolicyConfiguration allocWithZone:zone]
initWithMethodMask:self.methodMask
statusCodes:self.statusCodes
URLErrorCodes:self.URLErrorCodes
POSIXErrorCodes:self.POSIXErrorCodes];
}
- (NSString *)description
{
NSMutableDictionary *info = [[NSMutableDictionary alloc] init];
if (self.statusCodes) {
info[@"HTTPStatusCodes"] = self.statusCodes;
}
if (self.URLErrorCodes) {
info[@"URLErrorCodes"] = self.URLErrorCodes;
}
if (self.POSIXErrorCodes) {
info[@"POSIXErrorCodes"] = self.POSIXErrorCodes;
}
if (self.methodMask) {
info[@"HTTPMethods"] = _GenerateMethodStrings(self.methodMask);
}
return [NSString stringWithFormat:@"<%@ %p: %@>", NSStringFromClass([self class]), self, info];
}
@end
@implementation TNLMutableRequestRetryPolicyConfiguration
+ (BOOL)tnl_isMutableClass
{
return YES;
}
- (void)setMethod:(TNLHTTPMethod)method canBeRetried:(BOOL)canRetry
{
if (canRetry) {
_methodMask |= (1 << method);
} else {
_methodMask &= ~(1 << method);
}
}
- (void)setStatusCode:(TNLHTTPStatusCode)code canBeRetried:(BOOL)canRetry
{
if (!_statusCodes) {
_statusCodes = [[NSMutableIndexSet alloc] init];
}
TNLAssert([_statusCodes isKindOfClass:[NSMutableIndexSet class]]);
if (canRetry) {
[(NSMutableIndexSet *)_statusCodes addIndex:_HTTPStatusCodeToIndex(code)];
} else {
[(NSMutableIndexSet *)_statusCodes removeIndex:_HTTPStatusCodeToIndex(code)];
}
}
- (void)setURLErrorCode:(NSInteger)code canBeRetried:(BOOL)canRetry
{
if (!_URLErrorCodes) {
_URLErrorCodes = [[NSMutableIndexSet alloc] init];
}
TNLAssert([_URLErrorCodes isKindOfClass:[NSMutableIndexSet class]]);
if (canRetry) {
[(NSMutableIndexSet *)_URLErrorCodes addIndex:_URLErrorCodeToIndex(code)];
} else {
[(NSMutableIndexSet *)_URLErrorCodes removeIndex:_URLErrorCodeToIndex(code)];
}
}
- (void)setPOSIXErrorCode:(int)code canBeRetried:(BOOL)canRetry
{
if (!_POSIXErrorCodes) {
_POSIXErrorCodes = [[NSMutableIndexSet alloc] init];
}
TNLAssert([_POSIXErrorCodes isKindOfClass:[NSMutableIndexSet class]]);
if (canRetry) {
[(NSMutableIndexSet *)_POSIXErrorCodes addIndex:_POSIXErrorCodeToIndex(code)];
} else {
[(NSMutableIndexSet *)_POSIXErrorCodes removeIndex:_POSIXErrorCodeToIndex(code)];
}
}
- (void)setMethodsThatCanBeRetried:(nullable NSArray *)methods
{
_methodMask = _GenerateMethodMask(methods);
}
- (void)setStatusCodesThatCanBeRetried:(nullable NSArray<NSNumber *> *)codes
{
_statusCodes = _GenerateStatusCodes(codes);
}
- (void)setURLErrorCodesThatCanBeRetried:(nullable NSArray<NSNumber *> *)codes
{
_URLErrorCodes = _GenerateURLErrorCodes(codes);
}
- (void)setPOSIXErrorCodesThatCanBeRetried:(nullable NSArray<NSNumber *> *)codes
{
_POSIXErrorCodes = _GeneratePOSIXErrorCodes(codes);
}
- (id)copyWithZone:(nullable NSZone *)zone
{
return [[TNLRequestRetryPolicyConfiguration allocWithZone:zone] initWithMethodMask:self.methodMask
statusCodes:self.statusCodes
URLErrorCodes:self.URLErrorCodes
POSIXErrorCodes:self.POSIXErrorCodes];
}
@end
static NSArray<NSString *> *_GenerateMethodStrings(NSUInteger methodMask)
{
NSMutableArray<NSString *> *methods = [[NSMutableArray alloc] init];
// OPTIONS == min
// CONNECT == max
for (TNLHTTPMethod method = TNLHTTPMethodOPTIONS; methodMask != 0 && method <= TNLHTTPMethodCONNECT; method++) {
if (0x1 & methodMask) {
NSString *methodString = TNLHTTPMethodToString(method);
if (methodString) {
[methods addObject:methodString];
}
}
methodMask >>= 1;
}
return methods;
}
static NSUInteger _GenerateMethodMask(NSArray * __nullable methods)
{
NSUInteger newMask = 0;
for (id methodObj in methods) {
TNLHTTPMethod method = TNLHTTPMethodUnknown;
if ([methodObj isKindOfClass:[NSString class]]) {
method = TNLHTTPMethodFromString(methodObj);
} else if ([methodObj isKindOfClass:[NSNumber class]]) {
method = [methodObj integerValue];
if (!TNLHTTPMethodToString([methodObj integerValue])) {
method = TNLHTTPMethodUnknown;
}
}
if (method != TNLHTTPMethodUnknown) {
newMask |= (1 << method);
}
}
return newMask;
}
static NSMutableIndexSet *_GenerateStatusCodes(NSArray * __nullable statusCodes)
{
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init];
for (NSNumber *code in statusCodes) {
if ([code isKindOfClass:[NSNumber class]]) {
[indexSet addIndex:code.unsignedIntegerValue];
}
}
return indexSet;
}
static NSMutableIndexSet *_GenerateURLErrorCodes(NSArray * __nullable URLErrorCodes)
{
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init];
for (NSNumber *code in URLErrorCodes) {
if ([code isKindOfClass:[NSNumber class]]) {
[indexSet addIndex:_URLErrorCodeToIndex(code.integerValue)];
}
}
return indexSet;
}
static NSMutableIndexSet *_GeneratePOSIXErrorCodes(NSArray * __nullable POSIXErrorCodes)
{
NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] init];
for (NSNumber *code in POSIXErrorCodes) {
if ([code isKindOfClass:[NSNumber class]]) {
[indexSet addIndex:_POSIXErrorCodeToIndex(code.intValue)];
}
}
return indexSet;
}
NS_ASSUME_NONNULL_END