Download a file with AFHTTPSessionManager and with authentication - ios

I'm working on an iOS app using OAuth2 authentication.
I'm authenticated, I have no problem requesting my API, but I'm facing a problem when trying to download some files from protected URLS.
I'm using downloadTaskWithRequest from AFURLSessionManager (AFNetworking). It returns always an error 401 (not authorized) as if I was not logged in.
I can access this URL with no problem if I use the GET method, but I would like to use downloadTaskWithRequest because it permits me to have a progress indicator.
Here's my code (which isn't working):
NSURLSessionDownloadTask *downloadTask = [apiC downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *dstPath = [NSURL fileURLWithPath:dstFilePath];
return dstPath;
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
// HERE I HAVE A 401
}];
Here's my code that does work:
[[OEOApiClient sharedClient] GET:url parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) {
// SUCCEDD !!
} failure:^(NSURLSessionDataTask *task, NSError *error) {
}];
Does downloadTaskWithRequest not support authentication? Am I doing something wrong?

OK, I was creating my request the wrong way :
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
this was the good way :
NSURLRequest *request = [mySessionManager.requestSerializer requestWithMethod:#"GET" URLString:url parameters:nil error:&serializationError];

The request you're creating and passing to downloadTaskWithRequest:… is different from the one generated by your request serializer in GET:parameters:….
Look at the implementation of GET:parameters:… to learn how AFNetworking generates requests using request serializers. Then use the same code to make an authenticated request object to pass to downloadTaskWithRequest:….

Related

Issue while uploading Image

Step 1: here I am creating the Request
NSMutableURLRequest *request1 = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:#"POST"
URLString:[NSString stringWithFormat:#"%#%#", API_MAIN_URL, IMAGE_UPLOAD]
parameters:param constructingBodyWithBlock:^(id formData) {
[formData appendPartWithFileURL:[NSURL fileURLWithPath:strImagePath]
name:#"sendimage"
fileName:[strImagePath lastPathComponent]
mimeType:#"image/png"
error:nil];
} error:nil];
[request1 setValue:authToken forHTTPHeaderField:#"Authorization"];
Step 2: here I am creating the Stream at given Location
[[AFHTTPRequestSerializer serializer] requestWithMultipartFormRequest:request1
writingStreamContentsToFile:[NSURL fileURLWithPath:[strImagePath stringByDeletingPathExtension]]
completionHandler:^(NSError *error){
Step 3: here I am creating uploading task.
///here is file
NSProgress *progress = nil;
NSURLSessionUploadTask *uploadTask = [self.manager uploadTaskWithRequest:request1
fromFile:[NSURL fileURLWithPath:strImagePath]
progress:&progress
completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
NSLog(#"response : %#\n\n responseObject : %#\n\n error : %#", response, responseObject, error);
}];
[uploadTask resume];
}
}];
}
My problem is before application going to background mode I want to write all the request HTTPBody(HTTPStream) at given location with using Step:1 and Step:2 and save all the request into the NSArray after writing all the HTTPStream in File(in application Foreground) mean while I'll show Image prepare to upload.
then I'll start creating background task with the help of Step3: with passing request into this which request I have stored into the NSArray.
with this approach I am not able to upload images.
But If I call all the steps together one by one then it will upload the image on the server, but with this approach my application should be in Foreground for create request because we need to write the HTTPBody at give location.
Please help me to come out this problem I am stuck on this from last 2 week.
My application needed more then 500 images uploading over the server.
try like this
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:#"POST" URLString:#"http://example.com/upload" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileURL:[NSURL fileURLWithPath:#"file://path/to/image.jpg"] name:#"file" fileName:#"filename.jpg" mimeType:#"image/jpeg" error:nil];
} error:nil];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSProgress *progress = nil;
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithStreamedRequest:request progress:&progress completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(#"Error: %#", error);
} else {
NSLog(#"%# %#", response, responseObject);
}
}];
[uploadTask resume];
I have done with this task, create all the upload task without resume them and save every task within the NSArray after creation.
Once all the task are created then call a method in which we resume all the tasks.

Does AFNetworking V 2 support non streaming multi part background upload tasks?

A project I am working on needs to upload a video. The post API call for uploading a video needs a multipart body. The code I tried to use is included at the bottom of the question.
The code works perfectly for [NSURLSessionConfiguration defaultSessionConfiguration].
If I change the configuration to [NSURLSessionConfiguration backgroundSessionConfiguration:#"Test Id"], the code crashes because background sessions only support file uploads. The error is:
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Upload tasks in background sessions must be from a file'
All of the multipart append methods that can be used in the following method use the AFMultipartBodyStream class. This class streams the upload so it can not be used in a background session since a stream is not a file.
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer]
multipartFormRequestWithMethod:#"POST"
parameters:params
constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
NSError *error = nil;
[formData appendPartWithFileURL:fileURL
name:#"uploadFile"
fileName:[fileURL lastPathComponent]
mimeType:#"video/quicktime"
error:&error];
} error:&error];
Is it possible to have a NSURLSessionUploadTask with a multi part body that can run in a AFHTTPSessionManager that is using a backgroundSessionConfiguration?
See sample code below:
NSURL *APIURL = [NSURL URLWithString:#"https://www.test.com/uploadVideo"];
NSError *error = nil;
NSDictionary *params = #{ #"token" : #"d5bcf6c2-99ec-4de7-bd87-71918b633b3a",
#"title" : #"Test_Video" };
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer]
multipartFormRequestWithMethod:#"POST"
parameters:params
constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
NSError *error = nil;
[formData appendPartWithFileURL:fileURL
name:#"uploadFile"
fileName:filePath
mimeType:#"video/quicktime"
error:&error];
} error:&error];
[request addValue:#"Basic 12345678901234567890" forHTTPHeaderField:#"Authorization"];
NSLog(#"%#", error);
__weak __typeof(self)weakSelf = self;
__block NSURLSessionUploadTask *task =
[super
uploadTaskWithStreamedRequest:request
progress:progress
completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
[weakSelf handleCompletionForTask:task
responseObject:responseObject
error:error
success:successBlock
failure:failureBlock];
}];
[task setTaskDescription:endpoint.name];
[task resume];
There are some strict limitations with background NSURLSessions, regardless of AFNetworking. Upload tasks must use [NSURLSession uploadTaskWithRequest:fromFile:] I used Charles to monitor outgoing HTTP requests from this method and noticed it's using application/octet-stream. There is no way to get around this, at least nothing I've come across tinkering with it the past few days.
I have made this work finally.This is possible by using AFHTTPRequestSerializer:requestWithMultipartFormRequest:writingStreamContentsToFile:completionHandler:.
Refer https://github.com/AFNetworking/AFNetworking/issues/1874 - Lansing's answer.

Using NSURLConnection to retrieve data

I've got this api (http://www.timeapi.org/utc/now) that just gives the time as a string and I want to use NSURLConnection to retrieve it, except I'm confused as to how NSURLConnection works.
Current code:
+(NSString *) fetchTime
{
NSString *timeString=#"not_set";
//Code for URL request here
NSURL *timeURL = [NSURL URLWithString:#"http://www.timeapi.org/utc/now"]
return timeString;
}
The method is called from the view controller that will then in turn display it on the screen as per MVC, all I need is a good example to get me in the right direction.
In order to make a request to that api you need something like this:
NSURL *timeURL = [NSURL URLWithString:#"http://www.timeapi.org/utc/now"]
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:120];
NSData *urlData;
NSURLResponse *response;
NSError *error;
urlData = [NSURLConnection sendSynchronousRequest:urlRequest
returningResponse:&response
error:&error];
NSString *string = [[NSString alloc] initWithData:urlData encoding:NSUTF8StringEncoding];
What you want to do is send an Asynchronous request to the server to fetch the Time. If you send out a synchronous request it will block your UI and for some reason if server took a minute to send the response back user wont be able to do anything for a minute. Example using the standard API :
Note that if you are using sync request you can expect a return value but in async calls you would need the help of a block to return the value. So
-(void) fetchTimeFromServerWithCompletionHandler:(void(^)(id)) onComplete {
NSURLRequest *timeRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.timeapi.org/utc/now"]];
[NSURLConnection sendAsynchronousRequest:timeRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *urlResponse, NSData *data, NSError *error) {
// Do something usefull with Data.
// If expected object is a String, alloc init a String with received Data
NSString *time = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
onComplete(time); // This will return back the time string.
}];
}
If you are using service API a lot in your app, you can check out AFNetworking as well.
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://example.com/resources.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"JSON: %#", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];

AFNetworking getting HTML from rails

I am using AFNetworking to get JSON data from the server, but I am only getting back HTML and an error that says the following:
Expected content type {(
"text/json",
"application/json",
"text/javascript"
)}, got text/html, AFNetworkingOperationFailingURLResponseErrorKey=<NSHTTPURLResponse: 0x7592470>}
The code is as follows:
NSURL *url = [NSURL URLWithString:#"http://127.0.0.1:3000/games"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation;
operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *req, NSHTTPURLResponse *response, id jsonObject){
NSLog(#"Response: %#", jsonObject);
}
failure:^(NSURLRequest *req, NSHTTPURLResponse *response, NSError *error, id jsonObject){
NSLog(#"Error: %#", error);
}];
[operation start];
I am using rails and the server sends back JSON when I access the page with curl. I want to force application/json to be requested, am I doing this wrong?
You probably need to tell the server what content type you want back.
Here's a common fix for this issue:
NSURL *url = [NSURL URLWithString:#"http://127.0.0.1:3000/games"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
If this doesn't work, you'll need to step through your server code to determine under what conditions it'll return JSON instead of HTML.

AFNetworking with AFHTTPClient with AFJSONRequestOperation // MIME-Type Issues

I've been trying to get a grip on AFHTTPClient in the specific instance of dispatching a request to a REST-based service that requires OAuth authentication. I have no problem with creating the OAuth authentication using GTMOAuth.
I can also successfully marshall parameters to dispatch the request and obtain a well-formed JSON response using a hand-cobbled NSMutableURLRequest and both AFJSONRequestOperation and an NSURLConnection. Those latter two mechanics were my sanity check that I was touching the service correctly.
I get a response using
[AFHTTPClient HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject)]
but no matter what — it's interpreted as text/plain. The returned object's class is __NCFData.
No bueno.
This bit of code doesn't want to return a response that's a dictionary of any sort.
- (IBAction) testFlickr {
// marshall parameters
NSString *urlStr = #"http://api.flickr.com/";
NSURL *url = [NSURL URLWithString:urlStr];
AFHTTPClient *client = [[AFHTTPClient alloc]initWithBaseURL:url];
[client registerHTTPOperationClass:[AFJSONRequestOperation class]];
[client setParameterEncoding:AFJSONParameterEncoding];
NSDictionary *params = [[NSDictionary alloc]initWithObjectsAndKeys:#"json", #"format", #"66854529#N00", #"user_id", #"1", #"jsoncallback", nil];
NSString *path = [[NSString alloc]initWithFormat:#"services/rest/?method=flickr.people.getPhotos"];
NSMutableURLRequest *af_request = [client requestWithMethod:#"GET" path:path parameters:params];
// flickrAuth instance variable is an instance of GTMOAuthAuthentication
[self.flickrAuth authorizeRequest:af_request];
[client setAuthorizationHeaderWithToken:[self.flickrAuth accessToken]];
[client setDefaultHeader:#"Accept" value:#"application/json"];
[client requestWithMethod:#"GET" path:path parameters:params];
LOG_FLICKR_VERBOSE(0, #"Can Authorize? %#", ([self.flickrAuth canAuthorize] ? #"YES":#"NO"));
LOG_FLICKR_VERBOSE(0, #"%#", client);
// first way of trying..
AFHTTPRequestOperation *af_operation = [client HTTPRequestOperationWithRequest:af_request success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSString *str = [[NSString alloc] initWithData:responseObject
encoding:NSUTF8StringEncoding];
LOG_FLICKR_VERBOSE(0, #"Weird af_operation semantics, but.. %#", str);
LOG_FLICKR_VERBOSE(0, #"Weird af_operation semantics returns %#", [responseObject class]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//
LOG_FLICKR_VERBOSE(0, #"Weird af_operation semantics, error.. %#", error);
}];
[af_operation start];
}
This request goes through okay. The response data itself is what I'd expect, but it is not any kind of dictionary class.
I'd rather keep to using methods of AFHTTPClient (as opposed to, for example, [AFJSONRequestOperation JSONRequestOperationWithRequest]) so I can use AFHTTPClient's Reachability methods and so forth.
Strangely (to me, at least) if I do the request like this:
NSMutableURLRequest *aj_request = [client requestWithMethod:#"GET" path:path parameters:params];
[self.flickrAuth authorizeRequest:aj_request];
AFJSONRequestOperation *aj_operation =
[AFJSONRequestOperation JSONRequestOperationWithRequest:af_request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
LOG_FLICKR_VERBOSE(0, #"AFJSONRequestOperation %#", JSON);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
LOG_FLICKR_VERBOSE(0, #"AFJSONREquestOperation Error %#", error);
}];
[aj_operation start];
It fails with a "401" because it was expecting application/json in the response header and instead thinks it's received text/plain
But, if I do the request like this:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[[NSURL alloc]initWithString:#"http://api.flickr.com/services/rest/?method=flickr.people.getPhotos&format=json&user_id=66854529#N00&nojsoncallback=1"]];
[self.flickrAuth authorizeRequest:request];
AFJSONRequestOperation *operation =
[AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
LOG_FLICKR_VERBOSE(0, #"Success Flickr =========\n%# %#", JSON, [JSON valueForKeyPath:#"photos.total"]);
/////handler(JSON, nil);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
LOG_FLICKR(0, #"URL Was %#", url);
LOG_FLICKR(0, #"Failed Flickr ==========\n%# %#", error, JSON);
/////handler(nil, error);
}];
[operation start];
It works fine, including nice JSON, dictionary-formed data.
In the first instance, I'm using AFHTTPClient to produce the NSMutableURLRequest. In the second instance, I'm creating the NSMutableURLRequest on my own. In both cases I'm using AFJSONRequestOperation to dispatch the request leaving the only culprit for the problem to (besides myself..) AFHTTPClient.
In the first example that I can get to work, it's not returning JSON-y data.
In the second example AFHTTPClient seems to create an NSMutableURLRequest that blatantly fails — but (AFAICT) the same URL succeeds when that URL is created "by hand" using [NSMutableURLRequest requestWithURL].
I wonder — what am I missing when using AFHTTPClient?
Help?
In your first code example, it looks like you're doing NSMutableURLRequest *af_request = [client requestWithMethod:#"GET" path:path parameters:params]; and then setting default headers afterwards. Default headers only get applied to requests created after they were specified. Maybe that's where things are going amiss.
Also, that 401 error may be complaining about its content type, but 401 is an error status code, meaning that you're unauthenticated.
I ended up removing all the header parameters to isolate the problem, but it made no difference. Examining the response quite closely gave me a clue. While Flickr does return "JSON" it is not Lint-free, it seems and requires a tweak to one of the parameters. I had been sending jsoncallback=1 but it should be nojsoncallback=1. Once I fixed that parameter AFJSONRequestOperation handles the response correctly and parses the JSON.
My final code looks like this (for others, n.b. the nojsoncallback=1 parameter)
- (IBAction)testFlickrAFJSON:(id)sender
{
// marshall parameters
NSString *urlStr = #"http://api.flickr.com/";
NSURL *url = [NSURL URLWithString:urlStr];
//NSDictionary *params = [[NSDictionary alloc]initWithObjectsAndKeys:#"json", #"format", #"66854529#N00", #"user_id", nil];
NSDictionary *params = [[NSDictionary alloc]initWithObjectsAndKeys:#"json", #"format", #"66854529#N00", #"user_id", #"1", #"nojsoncallback", nil];
NSString *path = [[NSString alloc]initWithFormat:#"services/rest/?method=flickr.people.getPhotos"];
AFHTTPClient *client = [[AFHTTPClient alloc]initWithBaseURL:url];
NSMutableURLRequest *af_request = [client requestWithMethod:#"GET" path:path parameters:params];
[self.flickrAuth authorizeRequest:af_request];
LOG_FLICKR_VERBOSE(0, #"Can Authorize? %#", ([self.flickrAuth canAuthorize] ? #"YES":#"NO"));
AFJSONRequestOperation *af_operation_2 = [AFJSONRequestOperation JSONRequestOperationWithRequest:af_request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
LOG_FLICKR_VERBOSE(0, #"AFJSONRequestOperation Alt %#", JSON);
LOG_FLICKR_VERBOSE(0,#"AFJSONRequestOperation Alt response MIMEType %#",[response MIMEType]);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
LOG_FLICKR_VERBOSE(0, #"AFJSONREquestOperation Alt Error %#", error);
NSHTTPURLResponse *resp = [[error userInfo] valueForKey:AFNetworkingOperationFailingURLResponseErrorKey];
LOG_FLICKR_VERBOSE(0,#"AFJSONRequestOperation Alt Error response MIMEType %#",[resp MIMEType]);
}];
[af_operation_2 start];
}

Resources