iOS开源库解析之ASIHTTPRequest
2015-11-26 13:49
627 查看
坚持 成长 每日一篇
对于ASIHTTPRequest大家并不陌生,类似于iOS系统自带的request,这里主要介绍ASIFormDataRequest原理
ASIFormDataRequest特有属性
postData:用来保存非文件的数据
fileData:用来保存文件的数据
debugBodyString:只是用于纪录组件HttpPostBody过程错误日志信息,没有其他作用,设置Body时候并没有用他
postFormat:这是一个枚举属性,决定post是以form-data格式还是url encode格式上传数据
stringEncoding:对于DebugBodyString转data时候采用的是何种编码。
ASIFormDataRequest.m实现文件里面的主要方法解释
三种设置PostData情况
普通文本类型
本地文件路径,最终添加是数据到fileData
最后一种就是用于如果你手中已经有了文件数据,可以通过下列方法组织到fileData里面,包括上面介绍的file方法最终也是屌用此方法来
小结:上面介绍的三种方法都是收集一些组建PostData时候用的数据,并没有真正二进制数据存在内存,只有我们掉了ASIFormDataRequest.m的- (void)buildPostBody方法时候才真正开始组织数据
buildURLEncodedPostBody和buildMultipartFormDataPostBody方法:
buildURLEncodedPostBody源码如下:
buildMultipartFormDataPostBody源码如下:
小结:对于以上两个方法源码需要注意的是appendPostString和addToDebugBody两个方法。
addToDebugBody
这两个方法可以看出当我们掉用一次appendPostData时候其实是给BodyData加入一行数据而已,并纪录到DebugBody,所以DebugBody不参与任何转换,只是用于纪录与appendPostString类似的方法还有
等等,这里就不多解释了!注意所有的append动作都会在DebugString里面有纪录噢!
下面重点看几个方法
-(void)main(详细看源码),由于HTTPRequest是对NSOperation的实例,所以main方法是发起网络请求的入口。组建网络请求参数从这里开始真正组建。
这里有两个坑爹的设计
setupPostBody方法必须通过下面的amped方法才会叼到,如果没有调此方法postData是为空的,append方法是必须通过子类才回调得到,也就是说ASIHTPPRequest这个类是不能自己调动这两个方法,我们发起post请求只有直接设置postBody这个属性了。也许这么设计是为了延伸更多的Request类型,但是看到这里时候也是很疑惑了蛮久的。
差不多了解ASIHTTPRequest大致的逻辑,开始重构项目代码了!项目由于是用ASIFormDataRequest居多,所以从ASIFormDataRequest入手了解框架其他部分由于项目也没怎么使用到先不做了解,赶项目要紧。。
前言
ASIHTTPRequest框架作者已经停更,今日公司ASIHTTPRequest从项目移除需要重构使用了ASIHTTPRequest的代码,要想重构ASIHTTPRequest最好对ASIHTTPRequest有所了解。今日花了2天时间看了ASIHTTPRequest源码,以此博客作为一些理解纪录内容
ASIHTTPRequest和ASIFormDataRequest的关系
FormDataRequest是继承了httpRequest,他主要是针HTTP协议四种常见的POST方式的multipart/form-data请求和application/x-www-form-urlencoded请求。四种常见的请求详情http://www.aikaiyuan.com/6324.html对于ASIHTTPRequest大家并不陌生,类似于iOS系统自带的request,这里主要介绍ASIFormDataRequest原理
ASIFormDataRequest特有属性
postData:用来保存非文件的数据
fileData:用来保存文件的数据
debugBodyString:只是用于纪录组件HttpPostBody过程错误日志信息,没有其他作用,设置Body时候并没有用他
postFormat:这是一个枚举属性,决定post是以form-data格式还是url encode格式上传数据
stringEncoding:对于DebugBodyString转data时候采用的是何种编码。
ASIFormDataRequest.m实现文件里面的主要方法解释
三种设置PostData情况
普通文本类型
//普通文本类型最终会在这里加入所有的数据 - (void)addPostValue:(id <NSObject>)value forKey:(NSString *)key { if (!key) { return; } if (![self postData]) { [self setPostData:[NSMutableArray array]]; } NSMutableDictionary *keyValuePair = [NSMutableDictionary dictionaryWithCapacity:2]; [keyValuePair setValue:key forKey:@"key"]; [keyValuePair setValue:[value description] forKey:@"value"]; [[self postData] addObject:keyValuePair]; } //此方法最终调用上面的addPostValue:forKey:来给postData添加字典,postData在构建PostBody时候会被用到 - (void)setPostValue:(id <NSObject>)value forKey:(NSString *)key { // Remove any existing value NSUInteger i; for (i=0; i<[[self postData] count]; i++) { NSDictionary *val = [[self postData] objectAtIndex:i]; if ([[val objectForKey:@"key"] isEqualToString:key]) { [[self postData] removeObjectAtIndex:i]; i--; } } [self addPostValue:value forKey:key]; }
本地文件路径,最终添加是数据到fileData
//无论你是addFileValue还是setFileValue最终都会调用这个方法来组织文件数据 - (void)addFile:(NSString *)filePath withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key { BOOL isDirectory = NO; BOOL fileExists = [[[[NSFileManager alloc] init] autorelease] fileExistsAtPath:filePath isDirectory:&isDirectory]; if (!fileExists || isDirectory) { [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIInternalErrorWhileBuildingRequestType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"No file exists at %@",filePath],NSLocalizedDescriptionKey,nil]]]; } // If the caller didn't specify a custom file name, we'll use the file name of the file we were passed if (!fileName) { fileName = [filePath lastPathComponent]; } // If we were given the path to a file, and the user didn't specify a mime type, we can detect it from the file extension if (!contentType) { contentType = [ASIHTTPRequest mimeTypeForFileAtPath:filePath]; } [self addData:filePath withFileName:fileName andContentType:contentType forKey:key]; }
最后一种就是用于如果你手中已经有了文件数据,可以通过下列方法组织到fileData里面,包括上面介绍的file方法最终也是屌用此方法来
- (void)addData:(id)data withFileName:(NSString *)fileName andContentType:(NSString *)contentType forKey:(NSString *)key { if (![self fileData]) { [self setFileData:[NSMutableArray array]]; } if (!contentType) { contentType = @"application/octet-stream"; } NSMutableDictionary *fileInfo = [NSMutableDictionary dictionaryWithCapacity:4]; [fileInfo setValue:key forKey:@"key"]; [fileInfo setValue:fileName forKey:@"fileName"]; [fileInfo setValue:contentType forKey:@"contentType"]; [fileInfo setValue:data forKey:@"data"]; [[self fileData] addObject:fileInfo]; }
小结:上面介绍的三种方法都是收集一些组建PostData时候用的数据,并没有真正二进制数据存在内存,只有我们掉了ASIFormDataRequest.m的- (void)buildPostBody方法时候才真正开始组织数据
//这是一个重写了父类及ASIHTTPRequest类的buildPostBody - (void)buildPostBody { if ([self haveBuiltPostBody]) { return; } #if DEBUG_FORM_DATA_REQUEST [self setDebugBodyString:@""]; #endif //如果用户都没有设置postData和fileData则调用父类的buildPostBody //注意父类的build方法会根据是否含有postBodydata来设置http的Method(请求方式是post还是get) if (![self postData] && ![self fileData]) { [super buildPostBody]; return; } //如果要上传的数据存在文件数据,则设置“需要从磁盘读取数据的标志”为YES if ([[self fileData] count] > 0) { [self setShouldStreamPostDataFromDisk:YES]; } //判断以何种post方式组织bodyData,只有multipart和Application两种的一种,默认采用的是Application if ([self postFormat] == ASIURLEncodedPostFormat) { //下面会细看buildURLEncodedPostBody和buildMultipartFormDataPostBody [self buildURLEncodedPostBody]; } else { [self buildMultipartFormDataPostBody]; } //调用父类及ASIHTTPRequest类的buildPostBody [super buildPostBody]; #if DEBUG_FORM_DATA_REQUEST ASI_DEBUG_LOG(@"%@",[self debugBodyString]); [self setDebugBodyString:nil]; #endif }
buildURLEncodedPostBody和buildMultipartFormDataPostBody方法:
buildURLEncodedPostBody源码如下:
- (void)buildURLEncodedPostBody { // We can't post binary data using application/x-www-form-urlencoded /*这里如果存在fileData会强制转换为multipart的post格式*/ if ([[self fileData] count] > 0) { [self setPostFormat:ASIMultipartFormDataPostFormat]; [self buildMultipartFormDataPostBody]; return; } #if DEBUG_FORM_DATA_REQUEST [self addToDebugBody:@"\r\n==== Building an application/x-www-form-urlencoded body ====\r\n"]; #endif NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding([self stringEncoding])); [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@",charset]]; NSUInteger i=0; NSUInteger count = [[self postData] count]-1; for (NSDictionary *val in [self postData]) { NSString *data = [NSString stringWithFormat:@"%@=%@%@", [self encodeURL:[val objectForKey:@"key"]], [self encodeURL:[val objectForKey:@"value"]],(i<count ? @"&" : @"")]; [self appendPostString:data]; i++; } #if DEBUG_FORM_DATA_REQUEST [self addToDebugBody:@"\r\n==== End of application/x-www-form-urlencoded body ====\r\n"]; #endif }
buildMultipartFormDataPostBody源码如下:
- (void)buildMultipartFormDataPostBody { #if DEBUG_FORM_DATA_REQUEST [self addToDebugBody:@"\r\n==== Building a multipart/form-data body ====\r\n"]; #endif NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding([self stringEncoding])); // We don't bother to check if post data contains the boundary, since it's pretty unlikely that it does. CFUUIDRef uuid = CFUUIDCreate(nil); NSString *uuidString = [(NSString*)CFUUIDCreateString(nil, uuid) autorelease]; CFRelease(uuid); NSString *stringBoundary = [NSString stringWithFormat:@"0xKhTmLbOuNdArY-%@",uuidString]; [self addRequestHeader:@"Content-Type" value:[NSString stringWithFormat:@"multipart/form-data; charset=%@; boundary=%@", charset, stringBoundary]]; [self appendPostString:[NSString stringWithFormat:@"--%@\r\n",stringBoundary]]; // Adds post data NSString *endItemBoundary = [NSString stringWithFormat:@"\r\n--%@\r\n",stringBoundary]; NSUInteger i=0; for (NSDictionary *val in [self postData]) { [self appendPostString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",[val objectForKey:@"key"]]]; [self appendPostString:[val objectForKey:@"value"]]; i++; if (i != [[self postData] count] || [[self fileData] count] > 0) { //Only add the boundary if this is not the last item in the post body [self appendPostString:endItemBoundary]; } } // Adds files to upload i=0; for (NSDictionary *val in [self fileData]) { [self appendPostString:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", [val objectForKey:@"key"], [val objectForKey:@"fileName"]]]; [self appendPostString:[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", [val objectForKey:@"contentType"]]]; id data = [val objectForKey:@"data"]; if ([data isKindOfClass:[NSString class]]) { [self appendPostDataFromFile:data]; } else { [self appendPostData:data]; } i++; // Only add the boundary if this is not the last item in the post body if (i != [[self fileData] count]) { [self appendPostString:endItemBoundary]; } } [self appendPostString:[NSString stringWithFormat:@"\r\n--%@--\r\n",stringBoundary]]; #if DEBUG_FORM_DATA_REQUEST [self addToDebugBody:@"==== End of multipart/form-data body ====\r\n"]; #endif }
小结:对于以上两个方法源码需要注意的是appendPostString和addToDebugBody两个方法。
addToDebugBody
- (void)addToDebugBody:(NSString *)string { if (string) { [self setDebugBodyString:[[self debugBodyString] stringByAppendingString:string]]; } }
- (void)appendPostString:(NSString *)string { #if DEBUG_FORM_DATA_REQUEST [self addToDebugBody:string]; #endif [super appendPostData:[string dataUsingEncoding:[self stringEncoding]]]; }
这两个方法可以看出当我们掉用一次appendPostData时候其实是给BodyData加入一行数据而已,并纪录到DebugBody,所以DebugBody不参与任何转换,只是用于纪录与appendPostString类似的方法还有
- (void)appendPostData:(NSData *)data { [self addToDebugBody:[NSString stringWithFormat:@"[%lu bytes of data]",(unsigned long)[data length]]]; [super appendPostData:data]; }
- (void)appendPostDataFromFile:(NSString *)file { NSError *err = nil; unsigned long long fileSize = [[[[[[NSFileManager alloc] init] autorelease] attributesOfItemAtPath:file error:&err] objectForKey:NSFileSize] unsignedLongLongValue]; if (err) { [self addToDebugBody:[NSString stringWithFormat:@"[Error: Failed to obtain the size of the file at '%@']",file]]; } else { [self addToDebugBody:[NSString stringWithFormat:@"[%llu bytes of data from file '%@']",fileSize,file]]; } [super appendPostDataFromFile:file]; }
等等,这里就不多解释了!注意所有的append动作都会在DebugString里面有纪录噢!
父类的方法
看完了ASIFormDataRequest.m的实现文件我们发现子类回调父类的方法有append和build等方法下面重点看几个方法
-(void)main(详细看源码),由于HTTPRequest是对NSOperation的实例,所以main方法是发起网络请求的入口。组建网络请求参数从这里开始真正组建。
//父类的build方法在结束时候会把haveBuiltPostBody设置为YES - (void)buildPostBody { //如果haveBuiltPostBody为YES返回,这样就保证了一个Request只构建一次BodyData if ([self haveBuiltPostBody]) { return; } // Are we submitting the request body from a file on disk if ([self postBodyFilePath]) { // If we were writing to the post body via appendPostData or appendPostDataFromFile, close the write stream if ([self postBodyWriteStream]) { [[self postBodyWriteStream] close]; [self setPostBodyWriteStream:nil]; } NSString *path; if ([self shouldCompressRequestBody]) { if (![self compressedPostBodyFilePath]) { [self setCompressedPostBodyFilePath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]]; NSError *err = nil; if (![ASIDataCompressor compressDataFromFile:[self postBodyFilePath] toFile:[self compressedPostBodyFilePath] error:&err]) { [self failWithError:err]; return; } } path = [self compressedPostBodyFilePath]; } else { path = [self postBodyFilePath]; } NSError *err = nil; [self setPostLength:[[[[[NSFileManager alloc] init] autorelease] attributesOfItemAtPath:path error:&err] fileSize]]; if (err) { [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to get attributes for file at path '%@'",path],NSLocalizedDescriptionKey,error,NSUnderlyingErrorKey,nil]]]; return; } // Otherwise, we have an in-memory request body } else { //涉及到是否对要上传的数据进行压缩 if ([self shouldCompressRequestBody]) { NSError *err = nil; NSData *compressedBody = [ASIDataCompressor compressData:[self postBody] error:&err]; if (err) { [self failWithError:err]; return; } [self setCompressedPostBody:compressedBody]; [self setPostLength:[[self compressedPostBody] length]]; } else { [self setPostLength:[[self postBody] length]]; } } //如果有Post数据我们强制HttpMethod为POST模式 if ([self postLength] > 0) { if ([requestMethod isEqualToString:@"GET"] || [requestMethod isEqualToString:@"DELETE"] || [requestMethod isEqualToString:@"HEAD"]) { [self setRequestMethod:@"POST"]; } [self addRequestHeader:@"Content-Length" value:[NSString stringWithFormat:@"%llu",[self postLength]]]; } [self setHaveBuiltPostBody:YES]; }
这里有两个坑爹的设计
setupPostBody方法必须通过下面的amped方法才会叼到,如果没有调此方法postData是为空的,append方法是必须通过子类才回调得到,也就是说ASIHTPPRequest这个类是不能自己调动这两个方法,我们发起post请求只有直接设置postBody这个属性了。也许这么设计是为了延伸更多的Request类型,但是看到这里时候也是很疑惑了蛮久的。
- (void)setupPostBody { if ([self shouldStreamPostDataFromDisk]) { if (![self postBodyFilePath]) { [self setPostBodyFilePath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]]; [self setDidCreateTemporaryPostDataFile:YES]; } if (![self postBodyWriteStream]) { [self setPostBodyWriteStream:[[[NSOutputStream alloc] initToFileAtPath:[self postBodyFilePath] append:NO] autorelease]]; [[self postBodyWriteStream] open]; } } else { if (![self postBody]) { [self setPostBody:[[[NSMutableData alloc] init] autorelease]]; } } }
- (void)appendPostData:(NSData *)data { [self setupPostBody]; if ([data length] == 0) { return; } if ([self shouldStreamPostDataFromDisk]) { [[self postBodyWriteStream] write:[data bytes] maxLength:[data length]]; } else { [[self postBody] appendData:data]; } }
- (void)appendPostDataFromFile:(NSString *)file { [self setupPostBody]; NSInputStream *stream = [[[NSInputStream alloc] initWithFileAtPath:file] autorelease]; [stream open]; NSUInteger bytesRead; while ([stream hasBytesAvailable]) { unsigned char buffer[1024*256]; bytesRead = [stream read:buffer maxLength:sizeof(buffer)]; if (bytesRead == 0) { break; } if ([self shouldStreamPostDataFromDisk]) { [[self postBodyWriteStream] write:buffer maxLength:bytesRead]; } else { [[self postBody] appendData:[NSData dataWithBytes:buffer length:bytesRead]]; } } [stream close]; }
差不多了解ASIHTTPRequest大致的逻辑,开始重构项目代码了!项目由于是用ASIFormDataRequest居多,所以从ASIFormDataRequest入手了解框架其他部分由于项目也没怎么使用到先不做了解,赶项目要紧。。
相关文章推荐
- 《神经网络和深度学习》系列文章十六:反向传播算法代码
- 《神经网络和深度学习》系列文章十五:反向传播算法
- iOS开发网络篇—监测网络状态
- 用Java实现基于SOAP的XML文档网络传输及远程过程调用(RPC)
- kvm配置网络
- 【读书笔记】:哈工大软件学院计算机网络期末复习概要
- 未能从程序集“System.ServiceModel”中加载类型“System.ServiceModel.Activation.HttpModule”。
- 未能从程序集“System.ServiceModel”中加载类型“System.ServiceModel.Activation.HttpModule”。
- 浅谈单片机以太网接入方案
- [IIS] 不能加载类型System.ServiceModel.Activation.HttpModule
- WebService SOAP、Restful和HTTP(post/get)请求区别
- Android连接服务器请求架包之 Async-http-client
- 《读书笔记》系列2:TCP/IP详解
- Http通讯
- HttpClient调用第三方接口 底层代码的封装 方便以后使用
- ios 机型检测和网络检测
- 网络舆情分析技术 读书笔记2
- kali攻防第4章 内网称霸之HTTP信息截取
- C# 之httpwatch 缩减HttpWatch成可以进行二次开发的代码
- java基础之网络(UDP-Socket)