Implemented full support for generation of cache keys on RKParams. fixes #272

* Builds on work started by @OpenFibers.
* Should eliminate all cache warnings.
* Added FileMD5Hash library for efficiently computing MD5 for files
* Extended RKParams to return composite MD5 for all attachments
* Implemented MD5 method on each RKParamsAttachment instance
* Updated RKRequest to utilize new MD5 sums and enabled cache keys for RKParams
This commit is contained in:
OpenThread
2011-09-25 12:52:38 +08:00
committed by Blake Watters
parent e86d9375bd
commit f894226908
15 changed files with 663 additions and 101 deletions

View File

@@ -27,9 +27,8 @@
* objects.
*/
@interface RKParams : NSInputStream <RKRequestSerializable> {
NSMutableArray* _attachments;
@private
@private
NSMutableArray *_attachments;
NSStreamStatus _streamStatus;
NSData* _footer;
NSUInteger _bytesDelivered;
@@ -39,58 +38,68 @@
}
/**
* Returns an empty params object ready for population
Array of all attachments
*/
+ (RKParams*)params;
@property (nonatomic, readonly) NSMutableArray *attachments;
/**
* Initialize a params object from a dictionary of key/value pairs
Returns an empty params object ready for population
*/
+ (RKParams*)paramsWithDictionary:(NSDictionary*)dictionary;
+ (RKParams *)params;
/**
* Initalize a params object from a dictionary of key/value pairs
Initialize a params object from a dictionary of key/value pairs
*/
- (RKParams*)initWithDictionary:(NSDictionary*)dictionary;
+ (RKParams *)paramsWithDictionary:(NSDictionary *)dictionary;
/**
* Sets the value for a named parameter
Initalize a params object from a dictionary of key/value pairs
*/
- (RKParamsAttachment*)setValue:(id <NSObject>)value forParam:(NSString*)param;
- (RKParams *)initWithDictionary:(NSDictionary *)dictionary;
/**
* Sets the value for a named parameter to the data contained in a file at the given path
Sets the value for a named parameter
*/
- (RKParamsAttachment*)setFile:(NSString*)filePath forParam:(NSString*)param;
- (RKParamsAttachment *)setValue:(id <NSObject>)value forParam:(NSString *)param;
/**
* Sets the value to the data object for a named parameter. A default MIME type of
* application/octet-stream will be used.
Sets the value for a named parameter to the data contained in a file at the given path
*/
- (RKParamsAttachment*)setData:(NSData*)data forParam:(NSString*)param;
- (RKParamsAttachment *)setFile:(NSString *)filePath forParam:(NSString *)param;
/**
* Sets the value for a named parameter to an NSData object with a specific MIME type
Sets the value to the data object for a named parameter. A default MIME type of
application/octet-stream will be used.
*/
- (RKParamsAttachment*)setData:(NSData*)data MIMEType:(NSString*)MIMEType forParam:(NSString*)param;
- (RKParamsAttachment *)setData:(NSData*)data forParam:(NSString *)param;
/**
* Sets the value for a named parameter to a data object with the specified MIME Type and attachment file name
*
* @deprecated Set the MIMEType and fileName on the returned RKParamsAttachment instead
Sets the value for a named parameter to an NSData object with a specific MIME type
*/
- (RKParamsAttachment*)setData:(NSData*)data MIMEType:(NSString*)MIMEType fileName:(NSString*)fileName forParam:(NSString*)param DEPRECATED_ATTRIBUTE;
- (RKParamsAttachment *)setData:(NSData*)data MIMEType:(NSString *)MIMEType forParam:(NSString *)param;
/**
* Sets the value for a named parameter to the data contained in a file at the given path with the specified MIME Type and attachment file name
*
* @deprecated Set the MIMEType and fileName on the returned RKParamsAttachment instead
Sets the value for a named parameter to a data object with the specified MIME Type and attachment file name
@deprecated Set the MIMEType and fileName on the returned RKParamsAttachment instead
*/
- (RKParamsAttachment*)setFile:(NSString*)filePath MIMEType:(NSString*)MIMEType fileName:(NSString*)fileName forParam:(NSString*)param DEPRECATED_ATTRIBUTE;
- (RKParamsAttachment *)setData:(NSData*)data MIMEType:(NSString *)MIMEType fileName:(NSString *)fileName forParam:(NSString *)param DEPRECATED_ATTRIBUTE;
/**
* Resets the state of the RKParams stream
Sets the value for a named parameter to the data contained in a file at the given path with the specified MIME Type and attachment file name
@deprecated Set the MIMEType and fileName on the returned RKParamsAttachment instead
*/
- (RKParamsAttachment *)setFile:(NSString*)filePath MIMEType:(NSString *)MIMEType fileName:(NSString *)fileName forParam:(NSString *)param DEPRECATED_ATTRIBUTE;
/**
Resets the state of the RKParams stream
*/
- (void)reset;
/**
Return a composite MD5 checksum value for all attachments
*/
- (NSString *)MD5;
@end

View File

@@ -20,6 +20,7 @@
#import "RKParams.h"
#import "../Support/RKLog.h"
#import "NSString+MD5.h"
// Set Logging Component
#undef RKLogComponent
@@ -60,10 +61,13 @@ NSString* const kRKStringBoundary = @"0xKhTmLbOuNdArY";
[super dealloc];
}
- (RKParams*)initWithDictionary:(NSDictionary*)dictionary {
- (RKParams *)initWithDictionary:(NSDictionary *)dictionary {
self = [self init];
if (self) {
for (NSString* key in dictionary) {
// NOTE: We sort the keys to try and ensure given identical dictionaries we'll wind up
// with matching MD5 checksums.
NSArray *sortedKeys = [[dictionary allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
for (NSString *key in sortedKeys) {
id value = [dictionary objectForKey:key];
[self setValue:value forParam:key];
}
@@ -72,32 +76,32 @@ NSString* const kRKStringBoundary = @"0xKhTmLbOuNdArY";
return self;
}
- (RKParamsAttachment*)setValue:(id <NSObject>)value forParam:(NSString*)param {
RKParamsAttachment* attachment = [[RKParamsAttachment alloc] initWithName:param value:value];
- (RKParamsAttachment *)setValue:(id <NSObject>)value forParam:(NSString *)param {
RKParamsAttachment *attachment = [[RKParamsAttachment alloc] initWithName:param value:value];
[_attachments addObject:attachment];
[attachment release];
return attachment;
}
- (RKParamsAttachment*)setFile:(NSString*)filePath forParam:(NSString*)param {
RKParamsAttachment* attachment = [[RKParamsAttachment alloc] initWithName:param file:filePath];
- (RKParamsAttachment *)setFile:(NSString *)filePath forParam:(NSString *)param {
RKParamsAttachment *attachment = [[RKParamsAttachment alloc] initWithName:param file:filePath];
[_attachments addObject:attachment];
[attachment release];
return attachment;
}
- (RKParamsAttachment*)setData:(NSData*)data forParam:(NSString*)param {
RKParamsAttachment* attachment = [[RKParamsAttachment alloc] initWithName:param data:data];
- (RKParamsAttachment *)setData:(NSData *)data forParam:(NSString *)param {
RKParamsAttachment *attachment = [[RKParamsAttachment alloc] initWithName:param data:data];
[_attachments addObject:attachment];
[attachment release];
return attachment;
}
- (RKParamsAttachment*)setData:(NSData*)data MIMEType:(NSString*)MIMEType forParam:(NSString*)param {
RKParamsAttachment* attachment = [self setData:data forParam:param];
- (RKParamsAttachment *)setData:(NSData *)data MIMEType:(NSString *)MIMEType forParam:(NSString *)param {
RKParamsAttachment *attachment = [self setData:data forParam:param];
if (MIMEType != nil) {
attachment.MIMEType = MIMEType;
}
@@ -105,8 +109,8 @@ NSString* const kRKStringBoundary = @"0xKhTmLbOuNdArY";
return attachment;
}
- (RKParamsAttachment*)setData:(NSData*)data MIMEType:(NSString*)MIMEType fileName:(NSString*)fileName forParam:(NSString*)param {
RKParamsAttachment* attachment = [self setData:data forParam:param];
- (RKParamsAttachment *)setData:(NSData *)data MIMEType:(NSString *)MIMEType fileName:(NSString *)fileName forParam:(NSString *)param {
RKParamsAttachment *attachment = [self setData:data forParam:param];
if (MIMEType != nil) {
attachment.MIMEType = MIMEType;
}
@@ -117,8 +121,8 @@ NSString* const kRKStringBoundary = @"0xKhTmLbOuNdArY";
return attachment;
}
- (RKParamsAttachment*)setFile:(NSString*)filePath MIMEType:(NSString*)MIMEType fileName:(NSString*)fileName forParam:(NSString*)param {
RKParamsAttachment* attachment = [self setFile:filePath forParam:param];
- (RKParamsAttachment *)setFile:(NSString *)filePath MIMEType:(NSString *)MIMEType fileName:(NSString *)fileName forParam:(NSString *)param {
RKParamsAttachment *attachment = [self setFile:filePath forParam:param];
if (MIMEType != nil) {
attachment.MIMEType = MIMEType;
}
@@ -131,7 +135,7 @@ NSString* const kRKStringBoundary = @"0xKhTmLbOuNdArY";
#pragma mark RKRequestSerializable methods
- (NSString*)HTTPHeaderValueForContentType {
- (NSString *)HTTPHeaderValueForContentType {
return [NSString stringWithFormat:@"multipart/form-data; boundary=%@", kRKStringBoundary];
}
@@ -151,7 +155,7 @@ NSString* const kRKStringBoundary = @"0xKhTmLbOuNdArY";
// Calculate the length of the stream
_length = _footerLength;
for (RKParamsAttachment* attachment in _attachments) {
for (RKParamsAttachment *attachment in _attachments) {
_length += [attachment length];
}
@@ -230,6 +234,19 @@ NSString* const kRKStringBoundary = @"0xKhTmLbOuNdArY";
return _streamStatus;
}
- (NSArray *)attachments {
return [NSArray arrayWithArray:_attachments];
}
- (NSString *)MD5 {
NSMutableString *attachmentsMD5 = [[NSMutableString new] autorelease];
for (RKParamsAttachment *attachment in self.attachments) {
[attachmentsMD5 appendString:[attachment MD5]];
}
return [attachmentsMD5 MD5];
}
#pragma mark Core Foundation stream methods
- (void)_scheduleInCFRunLoop:(NSRunLoop *)runLoop forMode:(id)mode {

View File

@@ -21,77 +21,91 @@
#import <Foundation/Foundation.h>
/**
* Models an individual part of a multi-part MIME document. These
* attachments are stacked together within the RKParams document to
* allow for uploading files via HTTP
Models an individual part of a multi-part MIME document. These
attachments are stacked together within the RKParams document to
allow for uploading files via HTTP
*/
@interface RKParamsAttachment : NSObject {
NSString* _name;
NSString* _fileName;
NSString* _MIMEType;
NSString *_name;
NSString *_fileName;
NSString *_MIMEType;
@private
NSData* _MIMEHeader;
NSUInteger _MIMEHeaderLength;
NSInputStream* _bodyStream;
NSString *_filePath;
NSData *_body;
NSInputStream *_bodyStream;
NSData *_MIMEHeader;
NSUInteger _MIMEHeaderLength;
NSUInteger _bodyLength;
NSUInteger _length;
NSUInteger _delivered;
}
/**
* The parameter name of this attachment in the multi-part document
The parameter name of this attachment in the multi-part document
*/
@property (nonatomic, retain) NSString* name;
@property (nonatomic, retain) NSString *name;
/**
* The name of the attached file in the MIME stream
* Defaults to the name of the file attached or nil if there is not one.
The MIME type of the attached file in the MIME stream. MIME Type will be
auto-detected from the file extension of the attached file.
Defaults to nil
*/
@property (nonatomic, retain) NSString* fileName;
@property (nonatomic, retain) NSString *MIMEType;
/**
* The MIME type of the attached file in the MIME stream. MIME Type will be
* auto-detected from the file extension of the attached file.
*
* Defaults to nil
The MIME boundary string
*/
@property (nonatomic, retain) NSString* MIMEType;
@property (nonatomic, readonly) NSString *MIMEBoundary;
/**
* The MIME boundary string
The complete path to the attached file on disk.
*/
@property (nonatomic, readonly) NSString* MIMEBoundary;
@property (nonatomic, readonly) NSString *filePath;
/**
* Initialize a new attachment with a given parameter name and a value
The name of the attached file in the MIME stream
Defaults to the name of the file attached or nil if there is not one.
*/
- (id)initWithName:(NSString*)name value:(id<NSObject>)value;
@property (nonatomic, retain) NSString *fileName;
/**
* Initialize a new attachment with a given parameter name and the data stored in an NSData object
Initialize a new attachment with a given parameter name and a value
*/
- (id)initWithName:(NSString*)name data:(NSData*)data;
- (id)initWithName:(NSString *)name value:(id<NSObject>)value;
/**
* Initialize a new attachment with a given parameter name and the data stored on disk at the given file path
Initialize a new attachment with a given parameter name and the data stored in an NSData object
*/
- (id)initWithName:(NSString*)name file:(NSString*)filePath;
- (id)initWithName:(NSString *)name data:(NSData *)data;
/**
* Open the attachment stream to begin reading. This will generate a MIME header and prepare the
* attachment for writing to an RKParams stream
Initialize a new attachment with a given parameter name and the data stored on disk at the given file path
*/
- (id)initWithName:(NSString *)name file:(NSString *)filePath;
/**
Open the attachment stream to begin reading. This will generate a MIME header and prepare the
attachment for writing to an RKParams stream
*/
- (void)open;
/**
* The length of the entire attachment (including the MIME Header and the body)
The length of the entire attachment (including the MIME Header and the body)
*/
- (NSUInteger)length;
/**
* Read the attachment body in a streaming fashion
Read the attachment body in a streaming fashion
*/
- (NSUInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len;
/**
Calculate and return an MD5 checksum for the body of this attachment. This works
for simple values, NSData structures in memory, or by efficiently streaming a file
and calculating an MD5.
*/
- (NSString *)MD5;
@end

View File

@@ -23,6 +23,8 @@
#endif
#import "RKParamsAttachment.h"
#import "RKLog.h"
#import "NSData+MD5.h"
#import "FileMD5Hash.h"
// Set Logging Component
#undef RKLogComponent
@@ -39,10 +41,14 @@ extern NSString* const kRKStringBoundary;
@implementation RKParamsAttachment
@synthesize fileName = _fileName, MIMEType = _MIMEType, name = _name;
@synthesize filePath = _filePath;
@synthesize fileName = _fileName;
@synthesize MIMEType = _MIMEType;
@synthesize name = _name;
- (id)initWithName:(NSString*)name {
if ((self = [self init])) {
- (id)initWithName:(NSString *)name {
self = [self init];
if (self) {
self.name = name;
self.fileName = name;
}
@@ -50,24 +56,25 @@ extern NSString* const kRKStringBoundary;
return self;
}
- (id)initWithName:(NSString*)name value:(id<NSObject>)value {
- (id)initWithName:(NSString *)name value:(id<NSObject>)value {
if ((self = [self initWithName:name])) {
NSMutableData* body = [NSMutableData data];
if ([value respondsToSelector:@selector(dataUsingEncoding:)]) {
[body appendData:[(NSString*)value dataUsingEncoding:NSUTF8StringEncoding]];
_body = [[(NSString*)value dataUsingEncoding:NSUTF8StringEncoding] retain];
} else {
[body appendData:[[NSString stringWithFormat:@"%@", value] dataUsingEncoding:NSUTF8StringEncoding]];
}
_bodyStream = [[NSInputStream alloc] initWithData:body];
_bodyLength = [body length];
_body = [[[NSString stringWithFormat:@"%@", value] dataUsingEncoding:NSUTF8StringEncoding] retain];
}
_bodyStream = [[NSInputStream alloc] initWithData:_body];
_bodyLength = [_body length];
}
return self;
}
- (id)initWithName:(NSString*)name data:(NSData*)data {
if ((self = [self initWithName:name])) {
self = [self initWithName:name];
if (self) {
_body = [data retain];
_bodyStream = [[NSInputStream alloc] initWithData:data];
_bodyLength = [data length];
}
@@ -76,11 +83,13 @@ extern NSString* const kRKStringBoundary;
}
- (id)initWithName:(NSString*)name file:(NSString*)filePath {
if ((self = [self initWithName:name])) {
self = [self initWithName:name];
if (self) {
NSAssert1([[NSFileManager defaultManager] fileExistsAtPath:filePath], @"Expected file to exist at path: %@", filePath);
_fileName = [[filePath lastPathComponent] retain];
_filePath = [filePath retain];
_fileName = [[filePath lastPathComponent] retain];
_MIMEType = [[self mimeTypeForExtension:[filePath pathExtension]] retain];
_bodyStream = [[NSInputStream alloc] initWithFileAtPath:filePath];
_bodyStream = [[NSInputStream alloc] initWithFileAtPath:filePath];
NSError* error;
NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error];
@@ -96,7 +105,9 @@ extern NSString* const kRKStringBoundary;
}
- (void)dealloc {
[_name release];
[_name release];
[_body release];
[_filePath release];
[_fileName release];
[_MIMEType release];
@@ -213,4 +224,18 @@ extern NSString* const kRKStringBoundary;
return sent;
}
// NOTE: Cannot handle MD5 for files. We don't want to read the contents into memory
- (NSString *)MD5 {
if (_body) {
return [_body MD5];
} else if (_filePath) {
CFStringRef fileAttachmentMD5 = FileMD5HashCreateWithPath((CFStringRef)_filePath,
FileHashDefaultChunkSizeForReadingData);
return [(NSString *)fileAttachmentMD5 autorelease];
} else {
RKLogWarning(@"Failed to generate MD5 for attachment: unknown data type.");
return nil;
}
}
@end

View File

@@ -641,28 +641,25 @@
return NO;
}
// Multi-part file uploads are not cacheable
if (_params && ![_params respondsToSelector:@selector(HTTPBody)]) {
return NO;
}
return YES;
}
- (NSString*)cacheKey {
if (! [self isCacheable]) {
RKLogDebug(@"Asked to return cacheKey for uncacheable request: %@", self);
return nil;
}
// Use [_params HTTPBody] because the URLRequest body may not have been set up yet.
NSString* compositeCacheKey = nil;
if (_params && [_params respondsToSelector:@selector(HTTPBody)]) {
compositeCacheKey = [NSString stringWithFormat:@"%@-%d-%@", self.URL, _method, [_params HTTPBody]];
if (_params) {
if ([_params respondsToSelector:@selector(HTTPBody)]) {
compositeCacheKey = [NSString stringWithFormat:@"%@-%d-%@", self.URL, _method, [_params HTTPBody]];
} else if ([_params isKindOfClass:[RKParams class]]) {
compositeCacheKey = [NSString stringWithFormat:@"%@-%d-%@", self.URL, _method, [(RKParams *)_params MD5]];
}
} else {
compositeCacheKey = [NSString stringWithFormat:@"%@-%d", self.URL, _method];
}
NSAssert(compositeCacheKey, @"Expected a cacheKey to be generated for request %@, but got nil", compositeCacheKey);
return [compositeCacheKey MD5];
}