Sources/SPTDataLoader/SPTDataLoaderResponse.m (148 lines of code) (raw):
/*
Copyright 2015-2023 Spotify AB
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import <SPTDataLoader/SPTDataLoaderResponse.h>
#import "SPTDataLoaderResponse+Private.h"
NS_ASSUME_NONNULL_BEGIN
NSString * const SPTDataLoaderResponseErrorDomain = @"com.sptdataloaderresponse.error";
static NSString * const SPTDataLoaderResponseHeaderRetryAfter = @"Retry-After";
@interface SPTDataLoaderResponse ()
@property (nonatomic, strong, readwrite) NSDictionary<NSString *, NSString *> *responseHeaders;
@property (nonatomic, strong, readwrite) NSError *error;
@property (nonatomic, strong, readwrite) NSData *body;
@property (nonatomic, assign, readwrite) NSTimeInterval requestTime;
@end
@implementation SPTDataLoaderResponse
#pragma mark Private
+ (instancetype)dataLoaderResponseWithRequest:(SPTDataLoaderRequest *)request response:(nullable NSURLResponse *)response
{
return [[self alloc] initWithRequest:request response:response];
}
- (instancetype)initWithRequest:(SPTDataLoaderRequest *)request response:(nullable NSURLResponse *)response
{
self = [super init];
if (self) {
_request = request;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode >= SPTDataLoaderResponseHTTPStatusCodeMovedMultipleChoices
|| httpResponse.statusCode <= SPTDataLoaderResponseHTTPStatusCodeSwitchProtocols) {
_error = [NSError errorWithDomain:SPTDataLoaderResponseErrorDomain
code:httpResponse.statusCode
userInfo:nil];
}
_resolvedURL = httpResponse.URL;
_responseHeaders = httpResponse.allHeaderFields;
_statusCode = httpResponse.statusCode;
}
_retryAfter = [self retryAfterForHeaders:_responseHeaders];
}
return self;
}
- (BOOL)shouldRetry
{
if ([self.error.domain isEqualToString:SPTDataLoaderResponseErrorDomain]) {
switch (self.error.code) {
case SPTDataLoaderResponseHTTPStatusCodeInvalid:
case SPTDataLoaderResponseHTTPStatusCodeContinue:
case SPTDataLoaderResponseHTTPStatusCodeSwitchProtocols:
case SPTDataLoaderResponseHTTPStatusCodeOK:
case SPTDataLoaderResponseHTTPStatusCodeCreated:
case SPTDataLoaderResponseHTTPStatusCodeAccepted:
case SPTDataLoaderResponseHTTPStatusCodeNonAuthoritiveInformation:
case SPTDataLoaderResponseHTTPStatusCodeNoContent:
case SPTDataLoaderResponseHTTPStatusCodeResetContent:
case SPTDataLoaderResponseHTTPStatusCodePartialContent:
case SPTDataLoaderResponseHTTPStatusCodeMovedMultipleChoices:
case SPTDataLoaderResponseHTTPStatusCodeMovedPermanently:
case SPTDataLoaderResponseHTTPStatusCodeFound:
case SPTDataLoaderResponseHTTPStatusCodeSeeOther:
case SPTDataLoaderResponseHTTPStatusCodeNotModified:
case SPTDataLoaderResponseHTTPStatusCodeUseProxy:
case SPTDataLoaderResponseHTTPStatusCodeUnused:
case SPTDataLoaderResponseHTTPStatusCodeTemporaryRedirect:
case SPTDataLoaderResponseHTTPStatusCodeBadRequest:
case SPTDataLoaderResponseHTTPStatusCodeUnauthorised:
case SPTDataLoaderResponseHTTPStatusCodePaymentRequired:
case SPTDataLoaderResponseHTTPStatusCodeForbidden:
case SPTDataLoaderResponseHTTPStatusCodeMethodNotAllowed:
case SPTDataLoaderResponseHTTPStatusCodeNotAcceptable:
case SPTDataLoaderResponseHTTPStatusCodeProxyAuthenticationRequired:
case SPTDataLoaderResponseHTTPStatusCodeConflict:
case SPTDataLoaderResponseHTTPStatusCodeGone:
case SPTDataLoaderResponseHTTPStatusCodeLengthRequired: // We always include the content-length header
case SPTDataLoaderResponseHTTPStatusCodePreconditionFailed:
case SPTDataLoaderResponseHTTPStatusCodeRequestEntityTooLarge:
case SPTDataLoaderResponseHTTPStatusCodeRequestURITooLong:
case SPTDataLoaderResponseHTTPStatusCodeRequestRangeUnsatisifiable:
case SPTDataLoaderResponseHTTPStatusCodeExpectationFail:
case SPTDataLoaderResponseHTTPStatusCodeHTTPVersionNotSupported:
case SPTDataLoaderResponseHTTPStatusCodeNotImplemented:
case SPTDataLoaderResponseHTTPStatusCodeNotFound:
return NO;
case SPTDataLoaderResponseHTTPStatusCodeRequestTimeout:
case SPTDataLoaderResponseHTTPStatusCodeUnsupportedMediaTypes:
case SPTDataLoaderResponseHTTPStatusCodeInternalServerError:
case SPTDataLoaderResponseHTTPStatusCodeBadGateway:
case SPTDataLoaderResponseHTTPStatusCodeServiceUnavailable:
case SPTDataLoaderResponseHTTPStatusCodeGatewayTimeout:
return YES;
}
}
if ([self.error.domain isEqualToString:NSURLErrorDomain]) {
switch (self.error.code) {
case NSURLErrorCancelled:
case NSURLErrorUnknown:
case NSURLErrorBadURL:
case NSURLErrorUnsupportedURL:
case NSURLErrorZeroByteResource:
case NSURLErrorCannotDecodeRawData:
case NSURLErrorCannotDecodeContentData:
case NSURLErrorCannotParseResponse:
case NSURLErrorFileDoesNotExist:
case NSURLErrorNoPermissionsToReadFile:
case NSURLErrorDataLengthExceedsMaximum:
case NSURLErrorRedirectToNonExistentLocation:
case NSURLErrorBadServerResponse:
case NSURLErrorUserCancelledAuthentication:
case NSURLErrorUserAuthenticationRequired:
case NSURLErrorServerCertificateHasBadDate:
case NSURLErrorServerCertificateUntrusted:
case NSURLErrorServerCertificateHasUnknownRoot:
case NSURLErrorServerCertificateNotYetValid:
case NSURLErrorClientCertificateRejected:
case NSURLErrorClientCertificateRequired:
return NO;
case NSURLErrorTimedOut:
case NSURLErrorCannotFindHost:
case NSURLErrorCannotConnectToHost:
case NSURLErrorNetworkConnectionLost:
case NSURLErrorDNSLookupFailed:
case NSURLErrorHTTPTooManyRedirects:
case NSURLErrorResourceUnavailable:
case NSURLErrorNotConnectedToInternet:
case NSURLErrorSecureConnectionFailed:
case NSURLErrorCannotLoadFromNetwork:
return YES;
}
}
return NO;
}
- (nullable NSDate *)retryAfterForHeaders:(NSDictionary<NSString *, NSString *> *)headers
{
static NSDateFormatter *httpDateFormatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
httpDateFormatter = [NSDateFormatter new];
[httpDateFormatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss zzz"];
});
NSTimeInterval retryAfterSeconds = [headers[SPTDataLoaderResponseHeaderRetryAfter] doubleValue];
if (retryAfterSeconds != 0.0) {
return [NSDate dateWithTimeIntervalSinceNow:retryAfterSeconds];
}
NSString *retryAfterValue = headers[SPTDataLoaderResponseHeaderRetryAfter];
return [httpDateFormatter dateFromString:retryAfterValue];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p URL = \"%@\"; status-code = %ld; headers = %@>", self.class, (void *)self, self.resolvedURL, (long)self.statusCode, self.responseHeaders];
}
@end
NS_ASSUME_NONNULL_END