iOS POST request using NSURLSession - ios

I'm trying to send a simple POST request in iOS to test a server I've written. My code is as follows:
NSURL *url = [NSURL URLWithString:#"http://localhost:8888/createUser"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
NSString *bodyData = #"username=johndoe";
request.HTTPMethod = #"POST";
request.HTTPBody = [bodyData dataUsingEncoding:NSUTF8StringEncoding];
request.timeoutInterval = 5;
[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"resp: %#, err: %#", response, error);
}];
I've been trying to debug this and am very confused. The code inside the completionHandler is never run, the request never times out, and the server never sees anything. Nothing seems to happen at all. I was able to do a request using the old NSURLConnection but would like to avoid that since it's deprecated. What's the issue here?

In addition to setting up your code to do a GET when you say you want to do a POST (as josemando points out in his comment), you're not starting your task.
You need to change your last line like this:
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
dataTaskWithRequest: request
completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error)
{
NSLog(#"resp: %#, err: %#", response, error);
}];
[task resume];
(dataTaskWithRequest creates and returns a data task object which you then have to submit for execution with the resume method.)

Related

Getting data back from an NSURLSession via POST with JSON

Since NSURLConnection is depreated I need to move to an NSURLSession. I have a URL and some data I need to input as JSON. Then the result should be JSON coming back. I see something like so:
NSError *error;
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
NSURL *url = [NSURL URLWithString:#"[JSON SERVER"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
[request addValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request addValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setHTTPMethod:#"POST"];
NSDictionary *mapData = [[NSDictionary alloc] initWithObjectsAndKeys: #"TEST IOS", #"name",
#"IOS TYPE", #"typemap",
nil];
NSData *postData = [NSJSONSerialization dataWithJSONObject:mapData options:0 error:&error];
[request setHTTPBody:postData];
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
}];
[postDataTask resume];
I this the correct approach?
My requirements are:
1. Turn my key value pairs into JSON.
2. Pass in the URL and JSON to a reusable function.
3. Get the JSON data returned.
4. Parse the JSON data returned.
Have the callers to your method to provide a completion handler which processes the data returned and update the UI to indicate completion.
You can copy the pattern found in the SDK, as follows:
- (void)makeRequest:(NSString *)param completion:(void (^)(NSDictionary *, NSError *))completion;
Implement it like this:
// in the same scope
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
- (void)makeRequest:(NSString *)param
completion:(void (^)(NSDictionary *, NSError *))completion {
// your OP code goes here, e.g.
NSError *error;
NSMutableURLRequest *request = // maybe the param is the url for this request
// use the already initialized session
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// call the completion handler in EVERY code path, so the caller is never left waiting
if (!error) {
// convert the NSData response to a dictionary
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (error) {
// there was a parse error...maybe log it here, too
completion(nil, error);
} else {
// success!
completion(dictionary, nil);
}
} else {
// error from the session...maybe log it here, too
completion(nil, error);
}
}];
[postDataTask resume];
}
Code that calls this method will look like this:
// update the UI here to say "I'm busy making a request"
// call your function, which you've given a completion handler
[self makeRequest:#"https://..." completion:^(NSDictionary *someResult, NSError *error) {
// here, update the UI to say "Not busy anymore"
if (!error) {
// update the model, which should cause views that depend on the model to update
// e.g. [self.someUITableView reloadData];
} else {
// handle the error
}
}];
Notice a couple things: (1) the return type is void, the caller expects nothing to be returned from this method, and makes no assignment when calling it. The data "returned" is provided as parameters to the completion handler, which is called later, after the asnych request is complete, (2) the signature of the completion handler matches exactly what the caller declared in the completion block ^(NSDictionary *, NSError *), this is just a suggestion, typical for network requests.
Instantiate the NSURLSession and NSMutableURLRequest object:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setHTTPMethod:#"POST"];
[request addValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request addValue:#"application/json" forHTTPHeaderField:#"Accept"];
Turn your key value pairs into JSON:
// choose the right type for your value.
NSDictionary *postDict = #{#"key1": value1, #"key2": value2};
NSData *postData = [NSJSONSerialization dataWithJSONObject:postDict options:0 error:nil];
Make your POST with with the URL and JSON:
[request setURL:[NSURL URLWithString:#"JSON SERVER"];
[request setHTTPBody:postData];
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
}];
[postDataTask resume];
Parse the JSON data returned within the completionHandler above:
if (!error) {
NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
} else {
// error code here
}
responseDict is the parsed data. For example, if the server returns
{
"message":"Your messsage",
"data1":value1,
"data2":value2
}
You can easily get the value for data1 by using
[responseDict objectForKey:#"data1"];
If your want to make another POST with different URL or JSON, just repeat the flow of step 2-4.
Hope my answer helps.

How to make a PUT request in NSURLSession?

I want to make a PUT request to a URL, but when the output shows status code as 405, which means the request to the URL is something other than put.
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
NSURL * url = [NSURL URLWithString:#"http://httpbin.org/put"];
NSMutableURLRequest *request =[[NSMutableURLRequest alloc]initWithURL:url];
NSData *postbody = [#"name=testname&suggestion=testing123" dataUsingEncoding:NSUTF8StringEncoding];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
request.HTTPMethod = #"PUT";
[request setHTTPBody:postbody];
NSURLSessionDataTask * dataTask = [defaultSession dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(error == nil)
{
[request setHTTPBody:data];
NSLog(#"Response = %#",response);
}
}];
[dataTask resume];
Can someone point out where i am going wrong, i have been reading a lot about this issue since the last couple of hours, but i am not able to figure it out. Kindly do not mark this as duplicate since the previous op did not add body which is not the case with my code. Also the URL mentioned accepts any data as body, so i guess what i set the data to is irrelevant.
EDIT (ANSWER):
After banging my head from yesterday, one of my senior helped me solve the issue, hope this will help someone. The data task needs to be supplied with the request object and not with the URL, this was the reason it always showed 'GET' in Charles web debugging tool. The code should be as follows:
NSURLSessionDataTask * dataTask = [defaultSession dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// code
}];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
request.HTTPMethod = #"PUT";

How to wait for completion of NSURLSession?

I have a method that downloads a binary from a server and returns it.
But before the NSURLSession completes, my function is returning the value, so it's coming to be nil each time.
How can I wait till the download is complete and then return the binary?
Try this -
NSURLSession *delegateFreeSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
[[delegateFreeSession dataTaskWithURL: [NSURL URLWithString: #"http://www.example.com/"]
completionHandler:^(NSData *data, NSURLResponse *response,
NSError *error) {
NSLog(#"Got response %# with error %#.\n", response, error);
NSLog(#"DATA:\n%#\nEND DATA\n",
[[NSString alloc] initWithData: data
encoding: NSUTF8StringEncoding]);
[self loadDataToView:data]; // << your custom method inside the MyViewControllerClass
}] resume];
Your method should take a callback as an argument. Once the NSURLSession completion handler gets the object you need, you call that callback with the data (or an NSError object if you got an error back from the server)
You can't 'wait' for execution to continue after you get the data. By definition, such network operations are handled asynchronously, hence the need for a callback.
Update: sample code below
- (void)getFeed:(NSDictionary*)parameters fromURL:(NSString*)url withCallback:(void (^)(NSData *data, NSURLResponse *response, NSError *error))callback {
NSURL*newsfeedUrl = [NSURL URLWithString:url];
NSMutableURLRequest *newsfeedRequest = [NSMutableURLRequest requestWithURL:newsfeedUrl];
[newsfeedRequest addValue:#"XMLHTTPRequest" forHTTPHeaderField:#"X-Requested-With"];
newsfeedRequest.HTTPMethod = #"GET";
NSURLSessionDataTask *downloadNeewsfeedDataTask = [instance.session dataTaskWithRequest:newsfeedRequest completionHandler:^(NSData*data, NSURLResponse*response, NSError*error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*) response;
NSLog(#"statusCode %ld", (long)httpResponse.statusCode);
//Do something here in your completion handler and call your callback function when ready (either with the data you expected, or with an error object. Alternatively, you can outright refrain from adding a completion handler block and simply put your callback argument as the completion handler (they must have the same signature).
callback(data, response, error);
}];
[downloadNeewsfeedDataTask resume];
}
Here is the example of completion handler for NSURLSession.
-(void) httpPostWithCustomDelegate{
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
NSURL * url = [NSURL URLWithString:#"http://hayageek.com/examples/jquery/ajax-post/ajax-post.php"];
NSMutableURLRequest * urlRequest = [NSMutableURLRequest requestWithURL:url];
NSString * params =#"name=Ravi&loc=India&age=31&submit=true";
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask * dataTask =[defaultSession dataTaskWithRequest:urlRequest
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(#"Response:%# %#\n", response, error);
if(error == nil)
{
NSString * text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"Data = %#",text);
}
}];
[dataTask resume];
}
Here is good tutorial for NSURLSession.

How to return NSData from NSURLSessionDataTask completion handler

I am trying to make a simple class that I can use to call a post web service.
Everything is working perfectly except that I am not able to return the NSData.
This is my code:
+ (NSData *)postCall:(NSDictionary *)parameters fromURL:(NSString *)url{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSMutableArray *pairs = [[NSMutableArray alloc]init];
for(NSString *key in parameters){
[pairs addObject:[NSString stringWithFormat:#"%#=%#", key, parameters[key]]];
}
NSString *requestParameters = [pairs componentsJoinedByString:#"$"];
NSURL *nsurl = [NSURL URLWithString:url];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:nsurl];
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setHTTPBody:[requestParameters dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//return data;
}];
[dataTask resume];
return nil;
}
Please notice that I have //return data but it gives me this error
Incompatible block pointer types sending 'NSData *(^)(NSData *__strong, NSURLResponse *__strong, NSError *__strong)' to parameter of type 'void (^)(NSData *__strong, NSURLResponse *__strong, NSError *__strong)'
My question is:
Is my way good or it will cause me problems in the future? I don't have image to download and I don't have anything to upload, I just have to send simple string data and receive simpe string data. Or it will be better to but that code in each function independently?
How can I return the data please?
You cannot just return the data (because the NSURLSessionDataTask runs asynchronously). You probably want to employ your own completion block pattern, similar to the completionHandler of the dataTaskWithRequest method.
So, you would add your own block parameter to your method, that you'll invoke from inside the dataTaskWithRequest method's completionHandler:
+ (NSURLSessionDataTask *)postCall:(NSDictionary *)parameters fromURL:(NSString *)url completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler {
// create your request here ...
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (completionHandler)
completionHandler(data, response, error);
}];
[dataTask resume];
return dataTask;
}
Or, because this dataTaskWithRequest runs on a background thread, it’s sometimes useful to make sure to dispatch the completion handler back to the main queue, e.g.
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (completionHandler)
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(data, response, error);
});
}];
Note, as an aside, I think it's good to return the NSURLSessionDataTask reference, like above, so (a) the caller can make sure the data task was successfully created; and (b) you have the NSURLSessionTask reference that you can use to cancel the task in case, at some future date, you want to be able to cancel the request for some reason (e.g. the user dismisses the view controller from which the request was issued).
Anyway, you'd then invoke this with:
NSURLSessionTask *task = [MyClass postCall:parameters fromURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
// put whatever code you want to perform when the asynchronous data task completes
}];
if (!task) {
// handle failure to create task any way you want
}
You ask:
Is my way good or it will cause me problems in the future? I don't have [an] image to download and I don't have anything to upload, I just have to send [some] simple string data and receive [simple] string data. Or it will be better to but that code in each function independently?
If you're receiving simple string data back, I'd suggest composing your response in JSON format, and then having the completion block in postCall use NSJSONSerialization to extract the response. Using JSON makes it easier for the app to differentiate between successful response and a variety of server related problems that might also return string responses.
So, let's say you modified your server code to return a response like so:
{"response":"some text"}
Then you could modify postCall to parse that response like so:
+ (NSURLSessionDataTask *)postCall:(NSDictionary *)parameters fromURL:(NSString *)url completionHandler:(void (^)(NSString *responseString, NSError *error))completionHandler {
// create your request here ...
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (completionHandler) {
if (error) {
completionHandler(nil, error);
} else {
NSError *parseError = nil;
NSDictionary *responseDictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&parseError];
completionHandler(responseDictionary[#"response"], parseError);
}
}
}];
[dataTask resume];
return dataTask;
}
In terms of your underlying question, whether a method like postCall makes sense, yes, I think it makes perfect sense to put the details of creating the request in a single method. My minor reservation in your implementation was your decision to make it a class method rather than an instance method. You're currently creating a new NSURLSession for each request. I'd suggest making postCall an instance method (of a singleton if you want) and then saving the session as a class property, which you set once and then re-use on subsequent queries.
You should use a block method.
First define a block
typedef void (^OnComplete) (NSData *data);
Use the following method
+ (void)postCall:(NSDictionary *)parameters fromURL:(NSString *)url withBlock:(OnComplete)block; {
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSMutableArray *pairs = [[NSMutableArray alloc]init];
for(NSString *key in parameters){
[pairs addObject:[NSString stringWithFormat:#"%#=%#", key, parameters[key]]];
}
NSString *requestParameters = [pairs componentsJoinedByString:#"&"];
NSURL *myURL = [NSURL URLWithString:url];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:myURL];
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setHTTPBody:[requestParameters dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
block(data);
}];
[dataTask resume];
}

Do we need to handle the connection timeout of a process using NSURLSessionDataTask and NSMutableURLRequest

I am using NSURLSessionDataTask and NSMutableURLRequest. I noticed that the NSMutableURLRequest has a timeout (240 seconds I believe, which is a long time). I also read that NSURLSession has a timeout also but I am unsure of exactly what it is. My question is, will the app crash if I do not handle a timeout if it occurs? Is it necessary to handle timeouts or does the OS handle it and prevents the app from crashing, and just kills the request. If we must handle it then it would be great to get some feedback in regards to my code example;
NSURLSession * session = [NSURLSession sharedSession];
NSURL * url = [[NSURL alloc] initWithString:self.url];
NSMutableURLRequest * request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setHTTPMethod:#"POST"];
[request addValue:#"application/x-www-form-urlencoded; charset=utf-8" forHTTPHeaderField:#"Content-Type"];
NSString * params =[NSString stringWithFormat:#"email=%#",some email];
[request setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask * task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse * httpResp = (NSHTTPURLResponse *)response;
NSDictionary * dataDictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
//some code missing
}
[task resume];
dataTaskWithRequest:completionHandler: has an error parameter. If a timeout occurs, you will get a non-nil error object passed in to the completion block, and you should handle that however is appropriate for your app. You need to be handling that anyway for other types of errors that may occur. The documentation on this method is pretty sparse, but I presume that you will get a nil data object if the download fails (for any reason including a timeout), so you should check for that before you try to do anything with the data.
NSURLSessionDataTask * task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse * httpResp = (NSHTTPURLResponse *)response;
if (data) {
NSDictionary * dataDictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
}else{
NSLog (#"%#", error);
// do whatever to handle the error;
}

Resources