Similar to this issue.
Using AFNetworking 2.0.3 and trying to upload an image using AFHTTPSessionManager\'s POST + constructingBodyWithBlock. For reasons unknown, it se
Digging into this further, it appears that when you use NSURLSession
in conjunction with setHTTPBodyStream
, even if the request sets Content-Length
(which AFURLRequestSerialization
does in requestByFinalizingMultipartFormData
), that header is not getting sent. You can confirm this by comparing the allHTTPHeaderFields
of the task's originalRequest
and currentRequest
. I also confirmed this with Charles.
What's interesting is that Transfer-Encoding
is getting set as chunked
(which is correct in general when the length is unknown).
Bottom line, this seems to be a manifestation of AFNetworking's choice to use setHTTPBodyStream
rather than setHTTPBody
(which doesn't suffer from this behavior), which, when combined with NSURLSession
results in this behavior of malformed requests.
I think this is related to AFNetworking issue 1398.
Rob is absolutely right, the problem you're seeing is related to the (now closed) issue 1398. However, I wanted to provide a quick tl;dr in case anyone else was looking.
First, here's a code snippet provided by gberginc on github that you can model your file uploads after:
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];
}];
And secondly, to summarize the problem (and why you have to use a temporary file as a work around), it really is two fold.
NSURLRequest
Apple's libraries will set the encoding to Chunked and then abandon that header (and thereby clearing any content-length value AFNetworking sets)Transfer-Encoding: Chunked
(eg. S3)But it turns out, if you're uploading a request from a file (because the total request size is known ahead of time), Apple's libraries will properly set the content-length header. Crazy right?
I was running into this problem myself, and was trying both methods and the suggested method here...
Turns out, it was as simple as changing the appended data "name" key to "file" instead of the filename variable.
Be sure your data key matches, or you will see the same symptom of an empty data set.