参考文章 iOS 使用AFNetworking的form表单请求
AFNetworking post请求方式有application/x-www-form-urlencoded、multipart/form-data、application/json
。其中application/json就是我们常见的post请求,使用AFN直接请求即可。
form表单请求,是一种特殊的post请求
下面开始正式的内容,如何使用AFNetworking
完成form表单请求
1、application/x-www-form-urlencoded
直接上代码:
+ (AFHTTPSessionManager *)getManagerWithReqType:(requestType)reqType{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
if(reqType == REQ_Form){
[manager.requestSerializer setValue:@"application/x-www-form-urlencoded;charset=utf-8" forHTTPHeaderField:@"Content-Type"];
}else{
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager.requestSerializer setValue:@"application/json;text/html;charset=utf-8" forHTTPHeaderField:@"Content-Type"];
}
return manager;
}
这种form表单请求方式和我们常见的post请求方式,只有这一个地方需要区别,特别注意以下两句不能出现在application/x-www-form-urlencoded
请求方式中
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
剩下的部分和常见的post请求一致,请求时只需要调用:
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
2、multipart/form-data
目前个人在项目中遇到的情况,主要用于上传二进制文件,使用时只需要在常见post请求的基础上,设置Content-Type
为:
[manager.requestSerializer setValue:@"multipart/form-data; boundary=----WebKitFormBoundaryHzyefUottpz7ltKf"forHTTPHeaderField:@"Content-Type"];
在调用方法上稍有不同,multipart/form-data
调用AFN的constructingBodyWithBlock方法:
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
以上传视频为例:
+(void)uploadVideoUseAFN:(NSURL *)filePath andUrl:(NSString *)urlString{
AFHTTPSessionManager *manager = [self getManager];
/// 要上传的二进制文件
NSData *videoData = [NSData dataWithContentsOfURL:filePath];
/// 二进制文件在服务器保存的路劲
NSDateFormatter *formatter = [[NSDateFormatter alloc]init];
formatter.dateFormat =@"yyyyMMddHHmmss";
NSString *str = [formatter stringFromDate:[NSDate date]];
NSString *fileName = [NSString stringWithFormat:@"%@.mp4", str];
NSURLSessionDataTask *task = [manager POST:urlString parameters:nil headers:nil constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
/// 拼接formdata
[formData appendPartWithFileData:videoData
name:@"myFile"
fileName:fileName
mimeType:@"video/mpeg"];
} progress:^(NSProgress * _Nonnull uploadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
}];
[task resume];
}
疑点1、关于mimeType的设置
使用AFNetworking
不需要设置额外的参数(如Content-Disposition
),因为在AFNetworking
完成form提交时,下面的方法会自动设置Content-Disposition
为@"form-data; name=\"%@\"; filename=\"%@\"
- (void)appendPartWithFileData:(NSData *)data
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType;
觉得不妥的也可以查看appendPartWithFileData
的具体实现,会发现如下内容:
[mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
[mutableHeaders setValue:mimeType forKey:@"Content-Type"];
这里有一个痛点,就是关于mimeType
的设置,图片和视频都很方便,可以直接设置为image/jpg
、image/png
、video/mpeg
等(最好和后台对一下,因为有些开发会指定文件类型,设置不一致会请求失败
)。
但对于文件上传,mimeType
设置什么就比较懵,这里可以设置万能类型application/octet-stream
,服务器会自动解析文件类型。关于如何获取文件的mimeType
,可以查看
疑点2、关于使用AFNetworking提交Form表单,请求成功了,但是后台接收到的数据为空
正常情况下我们使用form提交,都会在header中设置:
[manager.requestSerializer setValue:@"multipart/form-data; boundary=----WebKitFormBoundaryHzyefUottpz7ltKf"forHTTPHeaderField:@"Content-Type"];
后台没有接收到传过去的数据就是因为设置了multipart/form-data
和boundary
。这里最坑的就是设置了multipart/form-data
和boundary
,还能请求成功,让人一度人为自己写的代码没有半点毛病。
解决办法:
+ (void)uploadImageWIthimageData:(NSData *)imageData{
NSString *requestUrl = @"";
/// 设置filename
NSDateFormatter *formatter = [[NSDateFormatter alloc]init];
formatter.dateFormat =@"yyyyMMddHHmmss";
NSString *str = [formatter stringFromDate:[NSDate date]];
NSString *fileName = [NSString stringWithFormat:@"%@.jpg", str];
/// 请求配置
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
/// 注意:如果返回结果是json类型按如下设置,data类型manager.responseSerializer = [AFHTTPResponseSerializer serializer];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json",@"text/html",@"image/jpeg",@"image/png",@"application/octet-stream",@"text/json",nil];
/// 设置上传参数,经过测试这个可以不用传
NSDictionary *params = @{@"file":imageData};
/// 设置header
NSDictionary *headers = @{@"Authorization":@""};
NSURLSessionDataTask *task = [manager POST:requestUrl parameters:params headers:headers constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
[formData appendPartWithFileData:imageData
name:@"file"
fileName:fileName
mimeType:@"image/jpg"];
} progress:^(NSProgress * _Nonnull uploadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
}];
[task resume];
}
你也不要觉得这样就不是一个multipart/form-data
类型的请求了,因为AFNetworking
会自动补全这一部分