AFNetworking upload progress sending SOAP NSData - objective-c

I am using AFNetworking and would like to upload an NSData object to my server. I am trying to upload to our SOAP server, so I have XML that I have converted to an NSData object. For some reason, the follow does NOT work:
// Back to NSData
NSData *convertedFile = [xml dataUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:siteConfiguration.soapURL];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
NSMutableURLRequest *request = [httpClient multipartFormRequestWithMethod:#"POST" path:#"" parameters:nil constructingBodyWithBlock: ^(id <AFMultipartFormData>formData) {
[formData appendPartWithFormData:convertedFile name:assetCreation.name];
}];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
// Progress
float totalProgress = (float)totalBytesWritten/(float)totalBytesExpectedToWrite;
progress(totalProgress, totalBytesWritten, totalBytesExpectedToWrite);
if (totalBytesExpectedToWrite == totalBytesWritten) {
completion();
}
}];
[operation start];
But when not using the multipartFormRequestWithMethod block, it DOES work and the file is correctly uploaded, but there is NO progress shown, as the callback is only called once:
// Back to NSData
NSData *convertedFile = [xml dataUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:siteConfiguration.soapURL];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setHTTPMethod: #"POST"];
[request setHTTPBody:convertedFile];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
// Progress
float totalProgress = (float)totalBytesWritten/(float)totalBytesExpectedToWrite;
progress(totalProgress, totalBytesWritten, totalBytesExpectedToWrite);
if (totalBytesExpectedToWrite == totalBytesWritten) {
MatrixAsset *asset = [[MatrixAsset alloc] init];
completion(asset);
}
}];
[operation start];
It seems that my server really wants to be sent the HTTPBody with my NSData object. Is it possible to do the same with the AFNetworking progress callback?

Ok, I got it working. Looks like I was going about it wrong:
// Setup the connection
NSString *wsdlURL = [NSString stringWithFormat:#"%#?WSDL", siteConfiguration.soapURL];
NSURL *serviceUrl = [NSURL URLWithString:wsdlURL];
NSMutableURLRequest *serviceRequest = [NSMutableURLRequest requestWithURL:serviceUrl];
[serviceRequest setValue:#"text/xml" forHTTPHeaderField:#"Content-type"];
[serviceRequest setHTTPMethod:#"POST"];
[serviceRequest setHTTPBody:[xml dataUsingEncoding:NSUTF8StringEncoding]];
// Operation
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:serviceRequest];
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
// Progress
float totalProgress = (float)totalBytesWritten/(float)totalBytesExpectedToWrite;
progress(totalProgress, totalBytesWritten, totalBytesExpectedToWrite);
}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Success");
MatrixAsset *asset = [[MatrixAsset alloc] init];
completion(asset);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"error: %#", error.localizedDescription);
NSLog(#"error: %#", error.localizedFailureReason);
}];
[operation start];

You need SOAPAction to continue:
[serviceRequest setValue:#"SOAP_ACTION_URL" forHTTPHeaderField:#"SOAPAction"];

Related

AFNetworking 1.x to 3.x migration - get download progress?

I need to download image with progress bar. This is what I've got for in AFNetworking 1.x which is not working for AFNetworking 3.x.
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:self.mediaItem.media_item_hd_url]];
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
op.responseSerializer = [AFImageResponseSerializer serializer];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
UIImage *image = responseObject;
_image = image;
[self.delegate browserItem:self loaded:1.0 finished:YES];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) { }];
[op setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
CGFloat progress = ((CGFloat)totalBytesRead)/((CGFloat)totalBytesExpectedToRead);
[self.delegate browserItem:self loaded:progress finished:NO];
}];
[[NSOperationQueue mainQueue] addOperation:op];
Finally this is what I could come up with, which is not complete. I have no idea how to use progress in code block.
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:self.mediaItem.media_item_hd_url]];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(#"File downloaded to: %#", filePath);
// UIImage *image = response;
// _image = image;
[self.delegate browserItem:self loaded:1.0 finished:YES];
}];
[downloadTask resume];
You can get best framework RSNetworkKit which handles all the network call with an ease. It has internally implemented AFNetworking 3.0. It'll gives you download progress in single method either you upload, download.
It has also an enhanced method to show progress on UIImageView.
You can find it here. https://github.com/rushisangani/RSNetworkKit

expectFutureValue fails

Why I can't pass test? Any blocks of AFHTTPRequestOperation not called. Seems that expectFutureValue don't wait results and returns immediately. I trying on XCode 6.1 and Kiwi 2.3.1
Thanks!
context(#"AFNetworking", ^{
it(#"stubs a request with an error", ^{
NSError *error = [NSError errorWithDomain:#"com.luisobo.nocilla" code:123 userInfo:#{NSLocalizedDescriptionKey:#"Failing, failing... 1, 2, 3..."}];
stubRequest(#"POST", #"https://example.com/say-hello").
withHeaders(#{ #"X-MY-AWESOME-HEADER": #"sisisi", #"Content-Type": #"text/plain" }).
withBody(#"Adios!").
andFailWithError(error);
NSURL *url = [NSURL URLWithString:#"https://example.com/say-hello"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
[request setValue:#"text/plain" forHTTPHeaderField:#"Content-Type"];
[request setValue:#"sisisi" forHTTPHeaderField:#"X-MY-AWESOME-HEADER"];
[request setHTTPBody:[#"Adios!" dataUsingEncoding:NSASCIIStringEncoding]];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
__block BOOL succeed = NO;
__block BOOL failed = NO;
__block NSError *capturedError = nil;
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
succeed = YES;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
capturedError = error;
failed = YES;
}];
[operation start];
[[expectFutureValue(theValue(failed)) shouldEventually] beYes];
});
});

AFNetworking background file upload

I want to upload files to my server from my app. The code below is working great when app is active. If I press home button or open another app uploading stops.
I activated background fetch but still not working.
Afnetworking has background support but I cant figure out how I implement this feature to my code.
NSString *str=[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"Archive.zip"];
NSDictionary *parameters = #{#"foo": #"bar"};
NSURL *filePath = [NSURL fileURLWithPath:str];
AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
NSData *imageData=[NSData dataWithContentsOfURL:filePath];
NSMutableURLRequest *request =
[serializer multipartFormRequestWithMethod:#"POST" URLString:#"http://url"
parameters:parameters
constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileData:imageData
name:#"image"
fileName:#"Archive.zip"
mimeType:#"application/zip"];
}];
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
AFHTTPRequestOperation *operation =
[manager HTTPRequestOperationWithRequest:request
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Success %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failure %#", error.description);
}];
[operation setUploadProgressBlock:^(NSUInteger __unused bytesWritten,
long long totalBytesWritten,
long long totalBytesExpectedToWrite) {
NSLog(#"Wrote %lld/%lld", totalBytesWritten, totalBytesExpectedToWrite);
}];
[operation start];
Change this line
[operation start];
to this
[operation setShouldExecuteAsBackgroundTaskWithExpirationHandler:^{
// Handle iOS shutting you down (possibly make a note of where you
// stopped so you can resume later)
}];
[manager.operationQueue addOperation:operation];
You can look at these links
AFHTTPRequestOperation doesn't work after stand by mode
and Alternative for enqueueHTTPRequestOperation in AFNetworking 2.0

Upload PowerPoint via AFNetworking gets corrupt

I receive a successfully response when uploading a PowerPoint via the below code. It does get upload but the file is corrupt. When opening the corrupt file on the server via PowerPoint I get this message:
"PowerPoint found a problem with content in filename.pptx. PowerPoint can attempt to repair the presentation."
- (void)updateDocument:(NSString *) path parameters:(FileUploadParameters*)para success:(void (^)(void))success failure:(void (^)(NSError *error))failure
{
_postData = nil;
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:[ConfigurationUtil objectForKey:#"baseURL"]]];
_postData = [NSMutableData dataWithContentsOfFile:[path stringByStandardizingPath]];
_postData = [NSMutableData dataWithContentsOfFile:[path stringByStandardizingPath] options:NSDataReadingMapped error:nil];
NSMutableURLRequest *request = [httpClient multipartFormRequestWithMethod:HTTP_METHOD_POST
path:[self getQueryString:path parameter:para]
parameters:nil
constructingBodyWithBlock: ^(id <AFMultipartFormData>formData)
{
[formData appendPartWithFileData:_postData
name:[[path lastPathComponent] stringByDeletingPathExtension]
fileName:[[path lastPathComponent] stringByDeletingPathExtension] mimeType:#"application/powerpoint"];
}];
[request addValue:[NSString stringWithFormat:#"WRAP access_token=%#",[Tenant loadSharedTenantInstance].authToken] forHTTPHeaderField:#"Authorization"];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
if (success)
success();
}
failure:^(AFHTTPRequestOperation* operation, NSError* error)
{
if (failure)
failure(error);
}
];
[operation start];
}
Your AFNetworking code looks fine. I don't think that's the culprit.
You can use the cmp command line tool to compare the two files (the original and the upload) byte-for-byte and see if there's a difference. I think there won't be.
More likely: it's a misleading error message, and it's actually a permissions issue on the computer with Powerpoint installed, as outlined in this Microsoft Knowledge Base article.
The below code corrects the problem. It appears that appendPartWithFileData was causing the issue. It changes the filesize and that seemed to register the file as corrupt.
_postData = nil;
_postData = [NSMutableData dataWithContentsOfFile:[path stringByStandardizingPath]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]init];
NSString *queryString =[self getQueryString:path parameter:nil];
[request setURL:[NSURL URLWithString:queryString]];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:_postData];
[request addValue:[NSString stringWithFormat:#"WRAP access_token=%#",[Tenant loadSharedTenantInstance].authToken] forHTTPHeaderField:#"Authorization"];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
if (success)
success();
}
failure:^(AFHTTPRequestOperation* operation, NSError* error)
{
if (failure)
failure(error);
}
];
[operation start];

AFNetworking - 6 operations at a time consume 80% of CPU

Can anybody answer to me, how come below code eats up whole core of a C2D 2.6GHz CPU?
It just downloads files in 10MB chunks, there can be like 600 of them, but the NSOperationQueue has a limit of 6 concurrent tasks.
How come the same app on Windows (written in C# eats only 2%, not 80%!), it is just a simple HTTP request!
for (DownloadFile *downloadFile in [download filesInTheDownload])
{
for (DownloadChunk *downloadChunk in [downloadFile chunksInTheFile])
{
NSString *downloadPath = [[NSString stringWithFormat:#"%#/%#", [download downloadFolder], [download escapedTitle]] stringByExpandingTildeInPath];
NSString *chunkPath = [downloadPath stringByAppendingFormat:#"/%#.%i", [downloadFile fileName], [downloadChunk chunkId]];
NSError *attributesError = nil;
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:chunkPath error:&attributesError];
NSNumber *fileSizeNumber = [fileAttributes objectForKey:NSFileSize];
uint64_t fileSize = [fileSizeNumber longLongValue];
NSLog(#"Chunk file size: %lli", fileSize);
uint64_t expectedSize = ([downloadChunk endingByte] - [downloadChunk startingByte]) + 1;
NSLog(#"Chunk expected size: %lli", expectedSize);
uint64_t newStartingByte = [downloadChunk startingByte] + fileSize;
if (fileSize == expectedSize)
{
NSLog(#"Chunk complete: %#.%i", [downloadFile fileName], [downloadChunk chunkId]);
}
else
{
NSURL *fileURL = [[NSURL alloc] initWithString:[downloadFile filePath]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:fileURL];
NSLog(#"Normal range: %lli-%lli", [downloadChunk startingByte], [downloadChunk endingByte]);
NSString *range = [NSString stringWithFormat:#"bytes=%lli-%lli", newStartingByte, [downloadChunk endingByte]];
[request setValue:range forHTTPHeaderField:#"Range"];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:chunkPath append:YES];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"%#", [NSString stringWithFormat:#"Chunk complete: %#.%i", [downloadFile fileName], [downloadChunk chunkId]]);
if (download.downloadedBytes == download.size)
[[NSNotificationCenter defaultCenter] postNotificationName:#"downloadFinished" object:download];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[operation setDownloadProgressBlock:^(NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
download.downloadedBytes += bytesRead;
}];
[queue addOperation:operation];
}
}
}
Here are some time profiler screenshots, seems everything is because RunLoops if I read it correctly.
Seems like downgrading most of the properties to nonatomic seems to lower the usage from 80% to 20-30%. Now only downloadedBytes are atomic. What can I do also to make improvements?
In assuming the last progress block completes the download, you're sort of shoving a round peg down a square hole. I'm not sure if that's causing your high CPU usage or not, but I'd certainly fix that. AFNetworking has a function just for this, [AFHTTPRequestOperation setCompletionBlockWithSuccess:failure:], documented here. The correct block will be called based on the HTTP status code returned.
In other words, instead of this:
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:chunkPath append:YES];
[operation setDownloadProgressBlock:^(NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
download.downloadedBytes += bytesRead;
if (download.downloadedBytes == download.size)
[[NSNotificationCenter defaultCenter] postNotificationName:#"downloadFinished" object:download];
}];
[queue addOperation:operation];
chunkId++;
Use this:
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:chunkPath append:YES];
[output setCompletionBlockWithSuccess: ^(AFHTTPRequestOperation *operation, id responseObject) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"downloadFinished" object:download];
}
failure: ^(AFHTTPRequestOperation *operation, NSError *error) {
// you should probably do something here
}];
After that, I doubt you'd even need downloadedBytes. That removes your last atomic property, which should improve things.