接手的项目是要将IOS上的SDK移植到Mac上使用,取到IOS版本的SDK代码阅读后发现仍然在使用ASIHttpReqest作为网络请求的模块,但是众所周知ASIHttpReqest已经停止更新已有4年之久,虽然ASIHttpReqest是一个非常优秀的网络库,但因为缺少社区维护,随着技术的发展,还是有一定的局限。
相比之下AFNetworking是替代ASIHTTPRequest最佳之选。所以决定先将SDK功能业务部分采用AFNetworking进行代码重构[AFNetworking和ASIHTTPRequest的比较]
原先IOS版本的SDK直接使用ASIFormDataRequest通过setPostValue将要发送的数据一个一个添加到postData中,开始采用AFNetworking之后直接使用AFHTTPSessionManager中
- (NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
发现,POST的数据在服务端被认为是缺少数据,同样是POST操作,为什么会有不同的结果呢?使用Charles抓包分析后,发现使用AFHTTPSessionManager发送的数据是MultiPart,而ASIHTTPRequest发送的是x-www-form-urlencoded类型,所以判断服务器只能接收x-www-form-urlencoded类型的数据,也就是&key=value样式的。
继续上网查资料,POST发送数据,但是会有多种数据提交格式,[四种常见的POST提交数据方式],查看了AFNetworking中POST代码后,发现使用到的Content-Type是multipart/form-data。至此找到服务器对我使用AFHTTPSessionManager发送的POST数据被认为不完整的原因。
于是直接使用NSURLConnection写了以下的方法实现POST发送form-data到服务端。
+(void)universalRequestWith:(NSDictionary *)parameters URLStr:(NSString *)URLStr method:(RequestMethodType)methodType andBlock:(void (^)(NSDictionary *response, NSError *error))block{
NSURL *url=[NSURL URLWithString:URLStr];
if (methodType == RequestMethodPostType) {
dispatch_queue_t myCustomQueue = dispatch_queue_create("com.myown.UniversalRequestQueue", NULL);
dispatch_async(myCustomQueue, ^(){
NSMutableData *postBody=[NSMutableData data];
NSUInteger i=0;
for (NSString *key in [parameters allKeys]) {
NSString *data = [NSString stringWithFormat:@"%@=%@%@", key, [parameters valueForKey:key],(i<[[parameters allKeys] count]-1 ? @"&" : @"")];
[postBody appendData:[data dataUsingEncoding:NSUTF8StringEncoding]];
i++;
}
NSMutableURLRequest *request=[[NSMutableURLRequest alloc]initWithURL:url
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:20.0f];
[request setHTTPMethod: @"POST"];
[request setValue: @"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:postBody];
NSError *error = nil;
NSDictionary *result = [[NSDictionary alloc]init];
NSHTTPURLResponse* urlResponse = nil;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request
returningResponse:&urlResponse error:&error];
if (error.code==0) {
result =[NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableContainers error:nil];
}
dispatch_async(dispatch_get_main_queue(), ^(){
if (block) {
block(result,error);
}
});
});
}
}
再对POST提交数据的方式进行仔细研究,发现其中application/x-www-form-urlencoded
编码其实是基于uri的percent-encoding
编码的,所以采用application/x-www-form-urlencoded
的POST数据和queryString只是形式不同,本质都是传递参数。哪是否意味着可以直接使用GET方式去提交参数呢?
于是使用了AFHTTPSessionManager的
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
最终发现,数据提交成功,返回了正确的结果。
接下来对AFNetworking再仔细研究。
但是又有疑虑,既然AFNetworking应该更全面,为什么会没有支持application/x-www-form-urlencoded的处理方式呢?在源码中搜索application/x-www-form-urlencoded,发现AFURLRequestSerialization的方法
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
中是有使用application/x-www-form-urlencoded作为 Content-Type来封装Http请求的,使用效果怎么样?简单写了一段测试,用AFHTTPSessionManager的 requestSerializer去调用AFNetworking提供的封装方法,也是可以得到正确的结果。
NSError *error;
NSHTTPURLResponse* urlResponse = nil;
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:URLStr]];
NSURLRequest *AFRequest = [manager.requestSerializer requestBySerializingRequest:[request copy] withParameters:parameters error:&error];
NSData *responseData = [NSURLConnection sendSynchronousRequest:AFRequest
returningResponse:&urlResponse error:&error];
NSDictionary *result =[NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableContainers error:nil];
综上,解决了form-data的提交,也对POST不同的数据提交方式有了初步的认识和了解,该类问题以后处理起来也将更游刃有余。