AFNetworking 2.0 多部分请求正文空白
Posted
技术标签:
【中文标题】AFNetworking 2.0 多部分请求正文空白【英文标题】:AFNetworking 2.0 multipart request body blank 【发布时间】:2014-01-06 18:09:12 【问题描述】:类似于this issue。
使用 AFNetworking 2.0.3 并尝试使用 AFHTTPSessionManager 的 POST +constructingBodyWithBlock 上传图像。由于未知原因,当向服务器发出请求时,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:尝试在block本身中构建表单数据:
- (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 中发布了一个错误。
【问题讨论】:
我使用AFHTTPRequestOperationManager
创建了多部分请求,它运行良好,但是当我使用几乎相同的AFHTTPSessionManager
时,它失败了。 AFHTTPSessionManager
内部似乎有问题。
这成功了@Rob,现在将问题悬而未决,但这绝对闻起来像一个错误。
你试过查看task.response
吗?我的返回 statusCode
为 406(但来自 AFHTTPRequestOperationManager
的相同请求很好)。
我的返回 401 Unauthorized
,因为 access_token
没有随请求一起发送。 406 error 听起来很奇怪。显然,您的服务器担心它对未处理客户端的响应。您是否在请求上设置了任何特殊的 HTTP 标头?
没有。我的客户端代码与上面的第一个示例完全相同。 (我正在测试您的代码。)我添加的唯一内容是明确指定请求和响应序列化程序分别为AFHTTPRequestSerializer
和AFHTTPResponseSerializer
。同样的请求(以及序列化程序的使用)与AFHTTPRequestOperationManager
一起工作得很好。我正在调试两个 AFNetworking 管理器类,看看是否能看到两者之间差异的根源。
【参考方案1】:
Rob 是绝对正确的,您看到的问题与(现已关闭)issue 1398 有关。但是,我想提供一个快速的 tl;dr 以防其他人正在寻找。
首先,这是gberginc on github 提供的代码 sn-p,您可以在此之后对文件上传进行建模:
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 认为 content-length 标头在其控制之下,并且当为
NSURLRequest
设置 HTTP 主体流时,Apple 的库会将编码设置为 Chunked,然后放弃该标头(从而清除任何 content-length价值 AFNetworking 集)
上传命中的服务器不支持Transfer-Encoding: Chunked
(例如S3)
但事实证明,如果您从文件上传请求(因为提前知道总请求大小),Apple 的库将正确设置 content-length 标头。疯了吧?
【讨论】:
【参考方案2】:进一步深入研究,似乎当您将NSURLSession
与setHTTPBodyStream
结合使用时,即使请求设置Content-Length
(AFURLRequestSerialization
在requestByFinalizingMultipartFormData
中所做的),该标头也不会被发送.您可以通过比较任务的originalRequest
和currentRequest
的allHTTPHeaderFields
来确认这一点。我也向查尔斯证实了这一点。
有趣的是Transfer-Encoding
被设置为chunked
(通常在长度未知时是正确的)。
底线,这似乎是 AFNetworking 选择使用 setHTTPBodyStream
而不是 setHTTPBody
(不会受到这种行为的影响)的一种表现,当与 NSURLSession
结合使用时,会导致这种格式错误的行为请求。
我认为这与 AFNetworking issue 1398 有关。
【讨论】:
【参考方案3】:我自己也遇到了这个问题,正在尝试这两种方法和这里建议的方法......
事实证明,只需将附加数据的“名称”键更改为“文件”而不是文件名变量即可。
确保您的数据键匹配,否则您将看到空数据集的相同症状。
【讨论】:
以上是关于AFNetworking 2.0 多部分请求正文空白的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 AFNetworking-2.0 执行 JSON 编码的 GET 请求?
AFNetworking 2.0:多部分 POST 时出现错误 503
AFNetworking 2.0 多部分/表单数据上传到 mySQL
AFNetworking 2.0 中的 AFHTTPRequestOperationManager 如何传递 HTTPBody 标签