Issue with NSURLSession and Content-Type - ios

I am trying to upload a photo to a webservice, and I am using the following code:
NSData *imageData = UIImageJPEGRepresentation([UIImage imageNamed:imageToUpload], 0.5);
// 1
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.HTTPMaximumConnectionsPerHost = 1;
[config setHTTPAdditionalHeaders:#{#"Content-Type":#"multipart/form-data"}];
// 2
NSURLSession *upLoadSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
// for now just create a random file name, dropbox will handle it if we overwrite a file and create a new name..
NSString *urlString =[NSString stringWithFormat:#"https://ABC.co.uk/photos?api_key=MYAPIKEY" ];
NSURL *webUrl = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:webUrl];
[request setHTTPMethod:#"POST"];
// 3
NSURLSessionUploadTask *uploadTask = [upLoadSession uploadTaskWithRequest:request fromData:imageData];
// 4
// uploadView.hidden = NO;
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
// 5
[uploadTask resume];
I receive an error 500, and I'm not sure what to do.
I have tried implementing AFNetworking, body data appending like This, This, and This. Yet, no luck.
The code above is from Ray Wenderlich's website: Here.
All I need to provide the server is this:
file (the image)
API Key (I tried using the NSMutableURLRequest's addValue:forHTTPHeaderField: but it didn't work, so I'm using it in the URL. I know it's not the best practice in terms of safety, but I'm getting so desperate as I've been working on this for over 10 hours! One step at the time!)
The Content-Type to be multipart/form-data
That's all I need to provide, and yet I'm not getting anywhere!
Can anyone please help?
Thank you in advance
UPDATE 1: So I made some alterations to the URL structure, and added the API key as a dictionary in HTTPBody, and now I am getting error 403.
It seems really weird that apple has not made any effort to serialise this part of connection. Anyway. I really appreciate anyone helping out!

Try setting the Content-Type in the request as well:
[request addValue:#"multipart/form-data" forHTTPHeaderField:#"Content-Type"];

Related

Network calls take more time on iOS than on Android

I have a very strange problem, in Android, the API calls take 300-500 ms while in iOS it takes 1.5-2.5 sec. I have removed dependencies like my server, device specific issue, internet connectivity etc. I have a very simple sample code hitting a sample URL and for me, it takes about 2 sec, even on the simulator.
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(#"start");
// 1
NSURL *url = [NSURL URLWithString:#"https://httpbin.org/get"];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
// 2
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setValue:#"" forHTTPHeaderField:#"Accept-Encoding"];
request.HTTPMethod = #"GET";
[[session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// Handle response here
NSLog(#"end - %ld", (long)[(NSHTTPURLResponse*)response statusCode]);
}] resume];
});
I also have tried using AFNetworking and ASSIHTTP libraries, but there is no difference. I also have checked the headers and they are the same in both Android and iOS. What am I doing something wrong here?
I think your problem is not in the network but in this line:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
Can you remove it and check again?
Data task is async so you don't need to wrap it in another async block.
Also you don't need to create instance of NSURLSession for every request
You can log out the timestamp (NSLog already did) when the request generate, send, callback to analyse it.
Does the server side code do any processing based on 'user-agent'?
Is there a time difference if you open the url in iOS safari and within the app ?
You can try calling the api from postman (or another REST API test tool like firefox RESTClient) and override the user-agent to use iOS values (http://www.enterpriseios.com/wiki/UserAgent). If the time difference is still the same, theres nothing you can do in your mobile code to fix this lag.
P.S. :
1. Overriding user-agent in postman needs some tweaking : https://github.com/postmanlabs/postman-app-support/wiki/Postman-Proxy

Using NSURLSessionUploadTask fromFile, how to link the HTML input name with the file being uploaded?

I am wanting to upload a file to a server from an iOS device. I want to use the NSURLSessionUploadTask and I want the upload to get the content of the file being uploaded from a file.
The server is expecting to receive a file called "sightings.zip".
The HTML form used for the upload contains an input tag with name "fileBean" as per:
<input name="fileBean" type="file" />
I think I need to set up the request so that it contains the right "Content-disposition" information:
Content-Disposition: form-data; name="fileBean"; filename="sightings.zip"
but I have not idea how to do that based on the examples I can find, the questions on so and the Apple documentation.
My relevant code is given below.
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.allowsCellularAccess = YES;
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[operationManager backgroundQueue]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[GGPConstants urlFor:GGP_SEND_SIGHTINGS_PATH]];
[request setHTTPMethod:#"POST"];
[request setValue:#"identity" forHTTPHeaderField:#"Accept-Encoding"];
[request setValue:#"Content-Type" forHTTPHeaderField:#"application/zip"];
[request addValue:#"sightings.zip" forHTTPHeaderField:#"fileName"];
// How to get the value for the name and filename into the content disposition as per the line below?
// Content-Disposition: form-data; name="fileBean"; filename="sightings.zip"
NSURLSessionUploadTask *uploadTask = [session
uploadTaskWithRequest:request
fromFile:myFileURL
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(#"Error: %#", error);
} else {
// Celebrate the successful upload...
}
}];
In the past I have used AFNetworking's AFHTTPRequestSerializer, which supplies the input name when you are building the form data but that is not working for me for other reasons.
Any help would be appreciated.
I don't recommend the form-data type, because it is easy to get wrong. Better to use application/x-www-form-urlencoded, which is really trivial to construct. In that format, the body data basically looks like a GET request, but without the question mark, e.g.
name=foo.jpg&data=[url-encoded data blob here]
where the url-encoded data blob can be generated as described in Apple's docs with a couple of (__bridge_transfer NSString *) and (__bridge CFStringRef) bits added. :-)
With that said, there's a decent example of multipart form data here on Stack Overflow already. Be careful to note that you add two extra hyphens to the front of the boundary when you actually use it, so if you specify "foo" as your boundary, then you'll have "--foo" between each part.

Set video metadata after uploading to vimeo

I've successfully uploaded video to vimeo by using API V3. Now, i want to set few metadata likes name, description, privacy.view etc to uploaded video.
When i tried with vimeo playground, it get success: https://developer.vimeo.com/api/endpoints/videos#/{video_id}
But in code, i tried following two way but didn't get success.
NSString *strURL = [NSString stringWithFormat:#"%#videos/%#?access_token=%#&name=%#&description=%#&privacy.view=%#", VIMEO_API_CALL_URL, strVideoID, VIMEO_ACCESS_TOKEN_TEMP, strName, strDescription, strPrivacyView];
NSURL *URL = [NSURL URLWithString:[strURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL];
[request setHTTPMethod:#"PATCH"];
and following also,
NSString *strURL = [NSString stringWithFormat:#"%#videos/%#?access_token=%#", VIMEO_API_CALL_URL, strVideoID, VIMEO_ACCESS_TOKEN_TEMP];
NSURL *URL = [NSURL URLWithString:[strURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL];
[request setHTTPMethod:#"PATCH"];
[request setValue:#"iOS_QuickTime" forHTTPHeaderField:#"name"];
[request setValue:#"This video is uploaded via iOS applcation and to perform last step by adding all required metadata." forHTTPHeaderField:#"description"];
[request setValue:#"nobody" forHTTPHeaderField:#"privacy.view"];
Here, VIMEO_API_CALL_URL is "https://api.vimeo.com/";
I'm wandering for solution. Till if any one can help me here.
Your access token should be included in a proper authentication header. Something like this:
Authorization: Bearer xxxxxxxxxtokenxxxxxxx
The values you are trying to set (title, description, privacy) should be query params, not header key / values. So your first approach is correct.
You could also consider using the Vimeo iOS SDK (I'm one of the authors) or just browse the codebase to help you get from point A to point B:
https://github.com/vimeo/VIMNetworking
Feel free to file issues on that repo too.

Using NSURLSession to POST, what is the correct way to post the variables?

I am following this tutorial: http://www.raywenderlich.com/2965/how-to-write-an-ios-app-that-uses-a-web-service. Trying to set up a basic web service. Seems like the tutorial is old material and ASIHTTPRequest is no longer continued. I have been trying to use NSURLRequest instead. First question, is NSURLRequest a pretty standard way to be doing this? I just want something for basic GET, POST etc, should I be doing it a different way?
My code is:
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
NSLog(#"We want to unlock for the code %#",self.textField.text);
//Get a device ID, (actually can't do this aymore)
NSString *uniqueIdentifier = #"My iPhone";
NSString *code = self.textField.text;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://www.madasd.co/promos/"]];
request.HTTPMethod=#"POST";
//Set the header fields
[request setValue:#"application/xml; charset=utf-8" forHTTPHeaderField:#"Content-Type"];
NSString *myString = [NSString stringWithFormat:#"rw_app_id=1&code=%#&device_id=%#",code,uniqueIdentifier];
NSLog(#"%#",myString);
NSData *requestBodyData = [myString dataUsingEncoding:NSUTF8StringEncoding];
request.HTTPBody=requestBodyData;
//Create url and fire request
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[conn start];
return TRUE;
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#",string);
}
Second question, I have tested the backend using curl so I know it works fine, however the response I get is "Invalid Request", I think this is because the string I am sending is not correct. Am I doing this correct using the var names and & operators? Any pointers on this would be great! thanks. (Running a LAMP server on Linode!)
EDIT:
Also tried sending as JSON:
[request addValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request addValue:#"application/json" forHTTPHeaderField:#"Accept"];
NSDictionary *mapData = [[NSDictionary alloc]initWithObjectsAndKeys:#"1",#"rw_app_id",code,#"code",uniqueIdentifier,#"device_id", nil];
NSError *error = nil;
NSData *requestBodyData = [NSJSONSerialization dataWithJSONObject:mapData options:0 error:&error];
request.HTTPBody=requestBodyData;
Still getting the same error.
A couple of thoughts:
Don't use NSURLConnection. It is deprecated as of iOS 9. Use NSURLSession. See Using NSURLSession in the URL Loading System Programming Guide.
Decide what type of request you need to prepare. You specified application/xml in your header, but are creating a application/x-www-form-urlencoded request. Your Content-Type header must match how you're building the HTTPBody.
What type of request does your server require? x-www-form-urlencoded? XML? JSON?
Also, what type of response does your server provide?
If building a application/x-www-form-urlencoded request (as suggested by the body of your request), you are not properly percent escaping the values (see https://stackoverflow.com/a/20398755/1271826).
If you use delegate based NSURLConnection or NSURLSession, you should not just grab the results in didReceiveData. What you need to do is
Instantiate a NSMutableData before starting the request;
Have didReceiveData merely append to that NSMutableData;
Only when connectionDidFinishLoading: (in NSURLConnection) or URLSession:task:didCompleteWithError: (in NSURLSession) is called, should you then use the NSMutableData.
Alternatively, if using the block-based NSURLSession, this concern is completely eliminated (since you're not implementing any delegate methods). Using completionHandler-based methods of NSURLSession is much easier.
If all of this is too complicated, you might consider using AFNetworking's AFHTTPSessionManager (but not AFHTTPRequestOperationManager) to build your requests. It gets you out of the weeds of properly building requests, implementing delegate methods, etc.
You might need to wrap the strings into a dictionary and get the NSData object from a call to NSJSONSerialization. Though it depends on the form expected by the server.

Persistance of data in NSURLSession

I have a bunch of nested NSURLSessionDataTasks and the data downloaded persists on calls of the method. I think it might have something to do with how NSURLSession handles cache??
Is there some way I can flush the cache so I get the most recent data when I call the method (thats supposed to refresh the data)
Here's the code I'm working with if it helps at all...
--EDIT--
And a more readable excerpt of just one of the requests that I want to flush the cache from after use:
NSMutableURLRequest *homeRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"https://mistar.oakland.k12.mi.us/novi/StudentPortal/Home/Login"]]];
[homeRequest setCachePolicy:NSURLRequestReloadIgnoringCacheData];
[homeRequest setHTTPMethod:#"POST"];
NSString *postString = [NSString stringWithFormat:#"Pin=%#&Password=%#",
[self percentEscapeString:pin],
[self percentEscapeString:password]];
NSData * postBody = [postString dataUsingEncoding:NSUTF8StringEncoding];
[homeRequest setHTTPBody:postBody];
NSURLSessionDataTask *homeTask = [defaultSession dataTaskWithRequest:homeRequest
homeTask is the NSURLDataTask that I add to the defaultSession. I want to remove the cache it saves so that when I call the method that contains these lines again, it fetches from the server instead of the local cache.
Is defautSession a local variable pointing to [NSURLSession sharedSession]? I would try using an ephemeral session instead. It is almost the same except that it doesn't cache data to disk.
NSURLSessionConfiguration *config =
[NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:config];
If that still doesn't fix the problem, then invalidate it before posting again. Use
[defaultSession invalidateAndCancel]
after your task has completed and then create a new session before the next time you need to post.

Resources