diff --git a/Code/Network/RKHTTPRequestOperation.h b/Code/Network/RKHTTPRequestOperation.h index a47d40c1..dca1e925 100644 --- a/Code/Network/RKHTTPRequestOperation.h +++ b/Code/Network/RKHTTPRequestOperation.h @@ -21,17 +21,34 @@ #import "AFNetworking.h" #import "AFHTTPRequestOperation.h" -// TODO: AFNetworking should expose the default headers dictionary... +// Expose the default headers from AFNetworking's AFHTTPClient @interface AFHTTPClient () @property (readonly, nonatomic) NSDictionary *defaultHeaders; @end -// NOTE: Accepts 2xx and 4xx status codes, application/json only. -// TODO: May want to factor this down into another more specific operation subclass to generalize the behavior +/** + The `RKHTTPRequestOperation` class is a subclass of `AFHTTPRequestOperation` for HTTP or HTTPS requests made by RestKit. It provides per-instance configuration of the acceptable status codes and content types and integrates with the `RKLog` system to provide detailed requested and response logging. Instances of `RKHTTPRequest` are created by `RKObjectRequestOperation` and its subclasses to HTTP requests that will be object mapped. When used to make standalone HTTP requests, `RKHTTPRequestOperation` instance behave identically to `AFHTTPRequestOperation` with the exception of emitting logging information. + */ @interface RKHTTPRequestOperation : AFHTTPRequestOperation -// We allow override of status codes and content types on a per-request basis -@property (nonatomic, strong) NSIndexSet *acceptableStatusCodes; // Default nil: means defer to class level -@property (nonatomic, strong) NSSet *acceptableContentTypes; // Default nil: means defer to class level +///------------------------------------------------------------ +/// @name Configuring Acceptable Status Codes and Content Types +///------------------------------------------------------------ + +/** + The set of status codes which the operation considers successful. + + **Default**: nil + */ +@property (nonatomic, strong) NSIndexSet *acceptableStatusCodes; + +/** + The set of content types which the operation considers successful. + + The set may contain `NSString` or `NSRegularExpression` objects. + + **Default**: nil + */ +@property (nonatomic, strong) NSSet *acceptableContentTypes; @end diff --git a/Code/Network/RKHTTPRequestOperation.m b/Code/Network/RKHTTPRequestOperation.m index 5e498a4f..3ab16400 100644 --- a/Code/Network/RKHTTPRequestOperation.m +++ b/Code/Network/RKHTTPRequestOperation.m @@ -22,6 +22,7 @@ #import "RKLog.h" #import "lcl.h" #import "RKHTTPUtilities.h" +#import "RKMIMETypes.h" // Set Logging Component #undef RKLogComponent @@ -112,26 +113,14 @@ @implementation RKHTTPRequestOperation -// RestKit will attempt to parse information about failed client errors from the payload -+ (NSIndexSet *)acceptableStatusCodes +- (BOOL)hasAcceptableStatusCode { - NSMutableIndexSet *statusCodes = [NSMutableIndexSet new]; - [statusCodes addIndexesInRange:RKStatusCodeRangeForClass(RKStatusCodeClassSuccessful)]; - [statusCodes addIndexesInRange:RKStatusCodeRangeForClass(RKStatusCodeClassClientError)]; - return statusCodes; -} - -+ (NSSet *)acceptableContentTypes -{ - return [NSSet setWithObject:@"application/json"]; -} - -- (BOOL)hasAcceptableStatusCode { return self.acceptableStatusCodes ? [self.acceptableStatusCodes containsIndex:[self.response statusCode]] : [super hasAcceptableStatusCode]; } -- (BOOL)hasAcceptableContentType { - return self.acceptableContentTypes ? [self.acceptableContentTypes containsObject:[self.response MIMEType]] : [super hasAcceptableContentType]; +- (BOOL)hasAcceptableContentType +{ + return self.acceptableContentTypes ? RKMIMETypeInSet([self.response MIMEType], self.acceptableContentTypes) : [super hasAcceptableContentType]; } #pragma mark - NSURLConnectionDelegate methods diff --git a/Code/Network/RKObjectRequestOperation.m b/Code/Network/RKObjectRequestOperation.m index ae74753c..d9f6d4b2 100644 --- a/Code/Network/RKObjectRequestOperation.m +++ b/Code/Network/RKObjectRequestOperation.m @@ -20,16 +20,28 @@ #import "RKObjectRequestOperation.h" #import "RKResponseMapperOperation.h" +#import "RKMIMETypeSerialization.h" // Set Logging Component #undef RKLogComponent #define RKLogComponent lcl_cRestKitNetwork -static inline NSString * RKDescriptionForRequest(NSURLRequest *request) +static inline NSString *RKDescriptionForRequest(NSURLRequest *request) { return [NSString stringWithFormat:@"%@ '%@'", request.HTTPMethod, [request.URL absoluteString]]; } +static NSIndexSet *RKObjectRequestOperationAcceptableMIMETypes() +{ + static NSMutableIndexSet *statusCodes = nil; + if (! statusCodes) { + statusCodes = [NSMutableIndexSet indexSet]; + [statusCodes addIndexesInRange:RKStatusCodeRangeForClass(RKStatusCodeClassSuccessful)]; + [statusCodes addIndexesInRange:RKStatusCodeRangeForClass(RKStatusCodeClassClientError)]; + } + return statusCodes; +} + @interface RKObjectRequestOperation () @property (nonatomic, strong, readwrite) RKHTTPRequestOperation *requestOperation; @property (nonatomic, strong, readwrite) NSArray *responseDescriptors; @@ -57,6 +69,8 @@ static inline NSString * RKDescriptionForRequest(NSURLRequest *request) self.request = request; self.responseDescriptors = responseDescriptors; self.requestOperation = [[RKHTTPRequestOperation alloc] initWithRequest:request]; + self.requestOperation.acceptableContentTypes = [RKMIMETypeSerialization registeredMIMETypes]; + self.requestOperation.acceptableStatusCodes = RKObjectRequestOperationAcceptableMIMETypes(); // Initialize avoidsNetworkAccess based on caching preferences switch(self.request.cachePolicy) { @@ -69,7 +83,6 @@ static inline NSString * RKDescriptionForRequest(NSURLRequest *request) self.avoidsNetworkAccess = YES; break; }; - // TODO: set acceptable MIME Types based on available serializations? } return self; diff --git a/Code/Support/RKMIMETypeSerialization.h b/Code/Support/RKMIMETypeSerialization.h index d6ba4e77..4211920c 100644 --- a/Code/Support/RKMIMETypeSerialization.h +++ b/Code/Support/RKMIMETypeSerialization.h @@ -70,6 +70,13 @@ */ + (Class)serializationClassForMIMEType:(NSString *)MIMEType; +/** + Returns a set containing the string values for all MIME Types for which a serialization implementation has been registered. + + @return An `NSSet` object whose elements are `NSString` values enumerating the registered MIME Types. + */ ++ (NSSet *)registeredMIMETypes; + ///----------------------------------------------------------------------------- /// @name Serializing and Deserializing Content by MIME Type ///----------------------------------------------------------------------------- diff --git a/Code/Support/RKMIMETypeSerialization.m b/Code/Support/RKMIMETypeSerialization.m index 2b7c353e..9843aea6 100644 --- a/Code/Support/RKMIMETypeSerialization.m +++ b/Code/Support/RKMIMETypeSerialization.m @@ -58,15 +58,7 @@ - (BOOL)matchesMIMEType:(NSString *)MIMEType { - if ([self.MIMETypeStringOrRegularExpression isKindOfClass:[NSString class]]) { - return [[MIMEType lowercaseString] isEqualToString:[self.MIMETypeStringOrRegularExpression lowercaseString]]; - } else if ([self.MIMETypeStringOrRegularExpression isKindOfClass:[NSRegularExpression class]]) { - NSRegularExpression *regex = (NSRegularExpression *) self.MIMETypeStringOrRegularExpression; - NSUInteger numberOfMatches = [regex numberOfMatchesInString:[MIMEType lowercaseString] options:0 range:NSMakeRange(0, [MIMEType length])]; - return numberOfMatches > 0; - } else { - @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Unable to evaluate match for MIME Type '%@': expected an NSSt" userInfo:nil]; - } + return RKMIMETypeInSet(MIMEType, [NSSet setWithObject:self.MIMETypeStringOrRegularExpression]); } @end @@ -150,6 +142,11 @@ } } ++ (NSSet *)registeredMIMETypes +{ + return [NSSet setWithArray:[[self sharedSerialization].registrations valueForKey:@"MIMETypeStringOrRegularExpression"]]; +} + + (id)objectFromData:(NSData *)data MIMEType:(NSString *)MIMEType error:(NSError **)error { Class serializationClass = [self serializationClassForMIMEType:MIMEType]; diff --git a/Code/Support/RKMIMETypes.h b/Code/Support/RKMIMETypes.h index 79a22886..6119d5da 100644 --- a/Code/Support/RKMIMETypes.h +++ b/Code/Support/RKMIMETypes.h @@ -33,3 +33,12 @@ extern NSString * const RKMIMETypeXML; /// MIME Type text/xml extern NSString * const RKMIMETypeTextXML; + +/** + Returns `YES` if the given MIME Type matches any MIME Type identifiers in the given set. + + @param MIMEType The MIME Type to evaluate the match for. + @param MIMETypes An `NSSet` object who entries are `NSString` or `NSRegularExpression` objects specifying MIME Types. + @return `YES` if the given MIME Type matches any identifier in the set, else `NO`. + */ +BOOL RKMIMETypeInSet(NSString *MIMEType, NSSet *MIMETypes); diff --git a/Code/Support/RKMIMETypes.m b/Code/Support/RKMIMETypes.m index 7d67a486..f2315992 100644 --- a/Code/Support/RKMIMETypes.m +++ b/Code/Support/RKMIMETypes.m @@ -24,3 +24,21 @@ NSString * const RKMIMETypeJSON = @"application/json"; NSString * const RKMIMETypeFormURLEncoded = @"application/x-www-form-urlencoded"; NSString * const RKMIMETypeXML = @"application/xml"; NSString * const RKMIMETypeTextXML = @"text/xml"; + +BOOL RKMIMETypeInSet(NSString *MIMEType, NSSet *MIMETypes) +{ + for (id MIMETypeStringOrRegularExpression in MIMETypes) { + if ([MIMETypeStringOrRegularExpression isKindOfClass:[NSString class]]) { + return [[MIMEType lowercaseString] isEqualToString:[MIMEType lowercaseString]]; + } else if ([MIMETypeStringOrRegularExpression isKindOfClass:[NSRegularExpression class]]) { + NSRegularExpression *regex = (NSRegularExpression *) MIMETypeStringOrRegularExpression; + NSUInteger numberOfMatches = [regex numberOfMatchesInString:[MIMEType lowercaseString] options:0 range:NSMakeRange(0, [MIMEType length])]; + return numberOfMatches > 0; + } else { + NSString *reason = [NSString stringWithFormat:@"Unable to evaluate match for MIME Type '%@': expected an `NSString` or `NSRegularExpression`, got a `%@`", MIMEType, NSStringFromClass([MIMEType class])]; + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:reason userInfo:nil]; + } + } + + return NO; +}