与this issue类似。
使用AFNetworking 2.0.3并尝试使用AFHTTPSessionManager的POST + constructBodyWithBlock上传图像。由于原因未知,当向服务器发出请求时,似乎HTTP帖子正文总是空白。
我在下面将AFHTTPSessionManager子类化(因此使用了[self POST ...]
。
我尝试过两种方式构建请求。
方法1 :我只是尝试传递参数,然后只添加图像数据。
- (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<AFMultipartFormData> 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<AFMultipartFormData> 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's github repo发布了一个错误。
答案 0 :(得分:9)
Rob是绝对正确的,你看到的问题与(现已关闭)issue 1398有关。但是,我想提供一个快速的tl; dr,以防其他人在寻找。
首先,这是gberginc on 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];
}];
其次,要总结问题(以及为什么必须使用临时文件作为解决方法),它实际上是双重的。
NSURLRequest
设置HTTP正文流时,Apple会将编码设置为Chunked,然后放弃该标头(从而清除任何内容) - 长度值AFNetworking设置)Transfer-Encoding: Chunked
(例如S3)但事实证明,如果您从文件上传请求(因为提前知道总请求大小),Apple的库将正确设置内容长度标头。疯了吧?
答案 1 :(得分:5)
进一步深入研究,当您将NSURLSession
与setHTTPBodyStream
结合使用时,即使请求设置Content-Length
(AFURLRequestSerialization
在{{1} }}),该标头未被发送。您可以通过比较任务的requestByFinalizingMultipartFormData
和allHTTPHeaderFields
的{{1}}来确认这一点。我也向查尔斯证实了这一点。
有趣的是originalRequest
被设置为currentRequest
(当长度未知时,这是正确的)。
最重要的是,这似乎是AFNetworking选择使用Transfer-Encoding
而非chunked
(不受此行为影响)的表现形式,当与setHTTPBodyStream
结合使用时导致这种格式错误的请求行为。
我认为这与AFNetworking issue 1398有关。
答案 2 :(得分:0)
我自己遇到了这个问题,并且正在尝试这两种方法和建议的方法......
事实证明,就像将附加数据“name”键更改为“file”而不是文件名变量一样简单。
确保您的数据密钥匹配,否则您将看到与空数据集相同的症状。