今天有这么一个需求,实现APP意见反馈,可以提交标题、内容、联系方式、以及0-4张图片,从接口角度出发,这个属于混合表单提交,AFN3.0刚好支持这种提交方式。
AFN3.0支持文件上传,并且提供下面一个API:
/**
@param method 不能是 `GET` or `HEAD`, or `nil`.
@param URLString 服务器文件路径.
@param parameters 表单数据,最终是存储在HTTP body中.
@param block 此block有一个遵循 `AFMultipartFormData`协议的对象,用来往HTTP body添加内容.
@param error 创建Request过程中产生异常会保存在这个对象中.
@return 一个 `NSMutableURLRequest` 对象
*/
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable NSDictionary <NSString *, id> *)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
error:(NSError * _Nullable __autoreleasing *)error;
假如我们需要上传多个键值对以及多个文件,那么代码如下:
NSMutableURLRequest *request = [self.httpRequestSerializer multipartFormRequestWithMethod:@"POST" URLString:urlString parameters:requestParams constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
for (NSDictionary *dict in files) {
[formData appendPartWithFileData:dict[kUploadData] name:dict[kUploadTargetName] fileName:dict[kUploadFileName] mimeType:dict[kUploadMimeType]];
}
} error:NULL];
拿到了Request之后我们可以通过AFHTTPSessionManager中的API发起网络请求:
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler;
举个例子:
- (NSNumber *)callApiWithRequest:(NSURLRequest *)request success:(BPCallBack)success fail:(BPCallBack)fail
{
BPLog(@"\n==================================\n\nRequest Start: \n\n %@\n\n==================================", request.URL);
// 跑到这里的block的时候,就已经是主线程了。
__block NSURLSessionDataTask *dataTask = nil;
dataTask = [self.sessionManager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
NSNumber *requestID = @([dataTask taskIdentifier]);
[self.dispatchTable removeObjectForKey:requestID];
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSData *responseData = responseObject;
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
if (error) {
BPURLResponse *BPResponse = [[BPURLResponse alloc] initWithResponseString:responseString requestId:requestID request:request responseData:responseData error:error];
fail?fail(BPResponse):nil;
} else {
BPURLResponse *BPResponse = [[BPURLResponse alloc] initWithResponseString:responseString requestId:requestID request:request responseData:responseData status:BPURLResponseStatusSuccess];
success?success(BPResponse):nil;
}
}];
NSNumber *requestId = @([dataTask taskIdentifier]);
self.dispatchTable[requestId] = dataTask;
[dataTask resume];
return requestId;
}
其实现原理大致如下:
- 声明编码类型为multipart/form-data,指表单数据有多部分构成:既有文本数据,又有文件等二进制数据的意思。
- 生成一个boundary字符串用以分隔数据。
- 将requestParams字典对象中的键值对枚举出来,通过指定的编码格式(默认是UTF-8)编码后存储追加进HTTP body中。
以Apache服务器为例子,服务器端截获的request请求如下图1-1所示:
图1-1由图的Request Payload下可以清晰看到每个数据都是以boundary分割的,正是因为有了boundary的存在,所以我们可以实现多文件、多类型的数据上传。
服务器组件支持多文件读取,比如fileupload组件,举个例子:
try {
List<FileItem> items = sfu.parseRequest(request);
// 区分表单域
for (int i = 0; i < items.size(); i++) {
FileItem item = items.get(i);
// isFormField为true,表示这不是文件上传表单域
if (!item.isFormField()) {
//此item为文件
}else{
//item为表单数据,通过item.getFieldName()获取字段名,通过item.getString("utf-8")获取字段对应的值
}
}
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
out.println(e);
out.flush();
out.close();
return;
}
关于FileItem的具体使用和参考可以点击这里
网友评论