与此问题类似.
使用AFNetworking 2.0.3并尝试使用AFHTTPSessionManager的POST + constructBodyWithBlock上传图像.由于原因未知,似乎HTTP邮件正文在向服务器发出请求时始终为空.
我下面是AFHTTPSessionManager的子类(因此使用[self POST ...]
.
我试过两种方式构建请求.
方法1:我只是尝试传递params,然后只添加图像数据(如果存在).
- (void) createNewAccount:(NSString *)nickname accountType:(NSInteger)accountType primaryPhoto:(UIImage *)primaryPhoto
{
NSString *accessToken = self.accessToken;
// Ensure none of the params are nil, otherwise it'll mess up our dictionary
if (!nickname) nickname = @"";
if (!accessToken) accessToken = @"";
NSDictionary *params = @{@"nickname": nickname,
@"type": [[NSNumber alloc] initWithInteger:accountType],
@"access_token": accessToken};
NSLog(@"Creating new account %@", params);
[self POST:@"accounts" parameters:params constructingBodyWithBlock:^(id formData) {
if (primaryPhoto) {
[formData appendPartWithFileData:UIImageJPEGRepresentation(primaryPhoto, 1.0)
name:@"primary_photo"
fileName:@"image.jpg"
mimeType:@"image/jpeg"];
}
} success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(@"Created new account successfully");
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"Error: couldn't create new account: %@", error);
}];
}
方法2:尝试在块本身中构建表单数据:
- (void) createNewAccount:(NSString *)nickname accountType:(NSInteger)accountType primaryPhoto:(UIImage *)primaryPhoto
{
// Ensure none of the params are nil, otherwise it'll mess up our dictionary
if (!nickname) nickname = @"";
NSLog(@"Creating new account %@", params);
[self POST:@"accounts" parameters:nil constructingBodyWithBlock:^(id formData) {
[formData appendPartWithFormData:[nickname dataUsingEncoding:NSUTF8StringEncoding] name:@"nickname"];
[formData appendPartWithFormData:[NSData dataWithBytes:&accountType length:sizeof(accountType)] name:@"type"];
if (self.accessToken)
[formData appendPartWithFormData:[self.accessToken dataUsingEncoding:NSUTF8StringEncoding] name:@"access_token"];
if (primaryPhoto) {
[formData appendPartWithFileData:UIImageJPEGRepresentation(primaryPhoto, 1.0)
name:@"primary_photo"
fileName:@"image.jpg"
mimeType:@"image/jpeg"];
}
} success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(@"Created new account successfully");
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"Error: couldn't create new account: %@", error);
}];
}
使用任一方法,当HTTP请求到达服务器时,没有POST数据或查询字符串参数,只有HTTP标头.
Transfer-Encoding: Chunked Content-Length: User-Agent: MyApp/1.0 (iPhone Simulator; iOS 7.0.3; Scale/2.00) Connection: keep-alive Host: 127.0.0.1:5000 Accept: */* Accept-Language: en;q=1, fr;q=0.9, de;q=0.8, zh-Hans;q=0.7, zh-Hant;q=0.6, ja;q=0.5 Content-Type: multipart/form-data; boundary=Boundary+0xAbCdEfGbOuNdArY Accept-Encoding: gzip, deflate
有什么想法吗?还在AFNetworking的github回购中发布了一个错误.
罗布是绝对正确的,你看到的问题与(现已结束)问题1398有关.但是,我想提供一个快速的tl; dr,以防其他人在寻找.
首先,这是githgin在github上提供的代码片段,您可以在以下情况后对文件上传进行建模:
NSString* apiUrl = @"http://example.com/upload"; // Prepare a temporary file to store the multipart request prior to sending it to the server due to an alleged // bug in NSURLSessionTask. NSString* tmpFilename = [NSString stringWithFormat:@"%f", [NSDate timeIntervalSinceReferenceDate]]; NSURL* tmpFileUrl = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:tmpFilename]]; // Create a multipart form request. NSMutableURLRequest *multipartRequest = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:apiUrl parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) { [formData appendPartWithFileURL:[NSURL fileURLWithPath:filePath] name:@"file" fileName:fileName mimeType:@"image/jpeg" error:nil]; } error:nil]; // Dump multipart request into the temporary file. [[AFHTTPRequestSerializer serializer] requestWithMultipartFormRequest:multipartRequest writingStreamContentsToFile:tmpFileUrl completionHandler:^(NSError *error) { // Once the multipart form is serialized into a temporary file, we can initialize // the actual HTTP request using session manager. // Create default session manager. AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; // Show progress. NSProgress *progress = nil; // Here note that we are submitting the initial multipart request. We are, however, // forcing the body stream to be read from the temporary file. NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithRequest:multipartRequest fromFile:tmpFileUrl progress:&progress completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) { // Cleanup: remove temporary file. [[NSFileManager defaultManager] removeItemAtURL:tmpFileUrl error:nil]; // Do something with the result. if (error) { NSLog(@"Error: %@", error); } else { NSLog(@"Success: %@", responseObject); } }]; // Add the observer monitoring the upload progress. [progress addObserver:self forKeyPath:@"fractionCompleted" options:NSKeyValueObservingOptionNew context:NULL]; // Start the file upload. [uploadTask resume]; }];
其次,要总结问题(以及为什么必须使用临时文件作为解决方法),它实际上是双重的.
Apple认为内容长度标头在其控制之下,并且当为NSURLRequest
Apple的库设置HTTP正文流时,将编码设置为Chunked然后放弃该标头(从而清除AFNetworking设置的任何内容长度值)
上传的服务器不支持Transfer-Encoding: Chunked
(例如S3)
但事实证明,如果您从文件上传请求(因为提前知道总请求大小),Apple的库将正确设置内容长度标头.疯了吧?
挖掘到这个进一步,看来,当您使用NSURLSession
与配合setHTTPBodyStream
,即使请求集Content-Length
(这AFURLRequestSerialization
确实在requestByFinalizingMultipartFormData
),这个头是没有得到发送.您可以通过比较allHTTPHeaderFields
任务originalRequest
和来确认这一点currentRequest
.我也向查尔斯证实了这一点.
有趣的是Transfer-Encoding
被设置为chunked
(当长度未知时,这是正确的).
最重要的是,这似乎是AFNetworking选择使用setHTTPBodyStream
而不是setHTTPBody
(不受此行为影响)的一种表现形式,当与NSURLSession
这种不正确的请求行为相结合时.
我认为这与AFNetworking 问题1398有关.