Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Obj-C - POST JSON data to server?

Sorry for the newb question, but I am tearing my hair out over this one. I can successfully POST data to my endpoint URL via a program like FireFox's POSTER. However, I'm trying to post that same JSON data from my app to my endpoint URL (Drupal services push_notifications end point), and for some reason it will not POST successfully. Here is the code that I'm using:

ViewController.m

NSString *urlString1 = @"http://app.com/endpoint001/push_notifications";
 NSDictionary *jsonBodyDict = @{@"token":postDeviceID, @"type":@"ios"};
 NSData *jsonBodyData = [NSJSONSerialization dataWithJSONObject:jsonBodyDict options:kNilOptions error:nil];
 // watch out: error is nil here, but you never do that in production code. Do proper checks!

 NSString *urlString2 = [NSString stringWithFormat:@"http://app.com/endpoint001/push_notifications?token=%@&type=%@",
                         postDeviceID,@"ios"];

 NSMutableURLRequest *request = [NSMutableURLRequest new];
 request.HTTPMethod = @"POST";

 // for alternative 1:
 [request setURL:[NSURL URLWithString:urlString1]];
 [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
 [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
 [request setHTTPBody:jsonBodyData];
 [request addValue:csrfToken forHTTPHeaderField:@"X-CSRF-Token"];

 // for alternative 2:
 [request setURL:[NSURL URLWithString:urlString2]];
 // no body needed, though that ultimately depends on your server. Also, I didn't test this for lack of a backend :)

 NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
 NSURLSession *session = [NSURLSession sessionWithConfiguration:config
                                                       delegate:nil
                                                  delegateQueue:[NSOperationQueue mainQueue]];
 NSURLSessionDataTask *task = [session dataTaskWithRequest:request
                                         completionHandler:^(NSData * _Nullable data,
                                                             NSURLResponse * _Nullable response,
                                                             NSError * _Nullable error) {
                                             NSLog(@"Yay, done! Check for errors in response!");

                                             NSHTTPURLResponse *asHTTPResponse = (NSHTTPURLResponse *) response;
                                             NSLog(@"The response is: %@", asHTTPResponse);
                                             // set a breakpoint on the last NSLog and investigate the response in the debugger

                                             // if you get data, you can inspect that, too. If it's JSON, do one of these:
                                             NSDictionary *forJSONObject = [NSJSONSerialization JSONObjectWithData:data
                                                                                                           options:kNilOptions
                                                                                                             error:nil];
                                             // or
                                             NSArray *forJSONArray = [NSJSONSerialization JSONObjectWithData:data
                                                                                                     options:kNilOptions
                                                                                                       error:nil];

                                             NSLog(@"One of these might exist - object: %@ \n array: %@", forJSONObject, forJSONArray);

                                         }];
 [task resume];

Note: I've put this code in AFTER my user already successfully logs in, so I'm not sure starting a whole new connection is necessary? How can I POST my data to the server if a session and CSRF Token already exists? What should my code look like? Whoever answers this question is going on my Christmas list... O_O

NSLog Response:

2017-07-17 17:31:11.421281-0700 app[977:206852] Yay, done! Check for errors in response!
2017-07-17 17:31:11.422198-0700 app[977:206852] The response is: <NSHTTPURLResponse: 0x170239b40> { URL: http://app.com/endpoint001/push_notifications?token=9526687d594944513b0wabf704eae3223f0de9bf69136a0aae3ab046863474b1&type=ios } { status code: 401, headers {
    "Cache-Control" = "no-cache, must-revalidate";
    Connection = "keep-alive";
    "Content-Type" = "application/json";
    Date = "Tue, 18 Jul 2017 00:14:35 GMT";
    Expires = "Sun, 19 Nov 1978 05:00:00 GMT";
    Server = Apache;
    "Transfer-Encoding" = Identity;
    Vary = Accept;
    "X-Content-Type-Options" = nosniff;
} }
2017-07-17 17:31:27.172085-0700 app[977:207024] XPC connection interrupted
2017-07-17 17:31:27.172311-0700 app[977:206852] One of these might exist - object: (
    "CSRF validation failed"
) 
 array: (
    "CSRF validation failed"
)
like image 691
Brittany Avatar asked Jun 29 '17 00:06

Brittany


2 Answers

tom's answer gives the right direction, but I see the lack of description what the code actually does is confusing to you. Also, you're using deprecated methods (of NSURLConnection), so I've made a quick (untested) example using NSURLSession and its related classes. Don't worry, it is basically the same.

That being said, the way you try this in your original code makes me wonder whether you're actually really sending a json body (i.e. your backend expects that) or rather the system relies on boring parameters to the URL. So I added both ways in my code:

NSString *postDeviceID = @"something";

NSString *urlString1 = @"http://myurl.com/endpoint01/push_notifications";
NSDictionary *jsonBodyDict = @{@"token":postDeviceID, @"type":@"ios"};
NSData *jsonBodyData = [NSJSONSerialization dataWithJSONObject:jsonBodyDict options:kNilOptions error:nil];
// watch out: error is nil here, but you never do that in production code. Do proper checks!

NSString *urlString2 = [NSString stringWithFormat:@"http://myurl.com/endpoint01/push_notifications?token=%@&type=%@",
                        postDeviceID,@"ios"];

NSMutableURLRequest *request = [NSMutableURLRequest new];
request.HTTPMethod = @"POST";

// for alternative 1:
[request setURL:[NSURL URLWithString:urlString1]];
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
[request setHTTPBody:jsonBodyData];

// for alternative 2:
[request setURL:[NSURL URLWithString:urlString2]];
// no body needed, though that ultimately depends on your server. Also, I didn't test this for lack of a backend :)

NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config
                                                      delegate:nil
                                                 delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
                                        completionHandler:^(NSData * _Nullable data,
                                                            NSURLResponse * _Nullable response,
                                                            NSError * _Nullable error) {
                                            NSLog(@"Yay, done! Check for errors in response!");

                                            NSHTTPURLResponse *asHTTPResponse = (NSHTTPURLResponse *) response;
                                            NSLog(@"The response is: %@", asHTTPResponse);
                                            // set a breakpoint on the last NSLog and investigate the response in the debugger

                                            // if you get data, you can inspect that, too. If it's JSON, do one of these:
                                            NSDictionary *forJSONObject = [NSJSONSerialization JSONObjectWithData:data
                                                                                                          options:kNilOptions
                                                                                                            error:nil];
                                            // or
                                            NSArray *forJSONArray = [NSJSONSerialization JSONObjectWithData:data
                                                                                                    options:kNilOptions
                                                                                                      error:nil];

                                            NSLog(@"One of these might exist - object: %@ \n array: %@", forJSONObject, forJSONArray);

                                        }];
[task resume];

It is also possible your backend supports both ways, but learning how to construct a proper json body is always helpful. Note that the reason why tom's code makes you think it posted is that you seem to misunderstand how NSURLConnection works (unless you excluded code you rely on): The object is always constructed the way you're using it, so if (conn) is always in the YES branch. That doesn't mean the connection, i.e. the actual loading succeeds. The initializer returns nil if it can't create an instance, not if a valid instance simply relies on data that is refused on connecting to its target. You don't get an error for that in this way (you need to rely on delegation for this).

The session approach I showed gives you a completion block in which you can investigate what the server responds to track down further what went wrong. You can just set a breakpoint there and look at the error (which is nil if it succeeds), the status in the response etc.

I obviously didn't run this exact code, so excuse any typos, but I have used this approach a lot with loading from and sending to a variety of backends for my usual work. In general it should work (and, more importantly, allow you to figure out what the heck your backend expects, assuming you don't have detailed docs about it).


Edit:

Okay, that hint in the code comment was maybe misleading: I meant to check the response for errors. The response object (also see the documentation for the dataTask... method in general). I added code helping a bit with that, but please be aware that the conversion of the data object into JSON depends on what and if your server actually passes any data at all. Or it might have a weird format or something, you need to figure this out. For simple stuff that gets POSTed, it might not provide any data, you simply get an according information about whether it worked or not in the response. Note especially NSHTTPURLResponse's statusCode property. Unless your server does something weird, that's the HTTP status code defined by the standard and should help you out. If, for example, the way in which you constructed your body in the request (the jsonBodyDict in my example code) was wrong (wrong element names or the like) you would get a 400.

In general you have to understand that the meaning of "error" is not that simple here. The methods consider not being able to connect at all as an error, so you get an error object in the completion block in these cases. This is typically the case when your server simply doesn't exist or the like. If, however, you simply fail to communicate your intended information to your server, that's not an error from the API's perspective and you don't get an error object. You get a valid response that says "this request was erroneous". You're doing good already in your case, as the connection seems to happen and work, you're just not yet sending the data your server expects. The response should give more info on that.

To further debug this would go beyond the scope of this question even more, because ultimately we're talking about how your specific backend behaves now. If you can't figure that out, you'd need to give me the credentials for your server so I can test it myself, everything else would be guessing on my part now. :) We can do that, but it's probably best to do so in chat and not in a question/answer that's already that long, hehe.

like image 177
Gero Avatar answered Sep 22 '22 13:09

Gero


All the Answers are good but i am posting a method , for i proper implementation of posting JSON data to POST API

POST Method

    -(void)postServiceAPI:(NSString*)url andParameters:(NSString *)parameterString andCompletionHandler:(void(^)(NSData *data,NSError *error,BOOL networkError))compilationHandler
        {

     // Checking the Network availability : You can skip this check 
             Reachability *netConnectionReach = [Reachability reachabilityWithHostname:@"www.google.com"];
            NetworkStatus internetStatus = [netConnectionReach currentReachabilityStatus];


            if ((internetStatus != ReachableViaWiFi) && (internetStatus != ReachableViaWWAN))
            {

                dispatch_async(dispatch_get_main_queue(), ^{

                    compilationHandler(nil,nil,TRUE);
                });

            }
            else
            {
                NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
                configuration.timeoutIntervalForRequest = 30.0;
                configuration.timeoutIntervalForResource = 60.0;
                configuration.requestCachePolicy =  NSURLRequestReloadIgnoringLocalAndRemoteCacheData;
                _session = [NSURLSession sessionWithConfiguration:configuration];

                NSURL *urlStr = [NSURL URLWithString:url];
                NSData *postData = [parameterString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
                NSString *postLength = [NSString stringWithFormat:@"%lu",(unsigned long)[postData length]];
                NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:urlStr
                                                                       cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:60.0];
                [request setHTTPMethod:@"POST"];
                [request setValue:postLength forHTTPHeaderField:@"Content-Length"];
                [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];


             // You can also set  " application/json"

  // This will solve your problem 
                [ request setValue:@"Your_Token" forHTTPHeaderField:@"your_key_for_token"];




                [request setHTTPBody:postData];

                [[_session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
                  {
                      if (error)
                      {
                          dispatch_async(dispatch_get_main_queue(), ^{

                              compilationHandler(nil,error,nil);
                          });
                      }
                      else
                      {
                          dispatch_async(dispatch_get_main_queue(), ^{

                              compilationHandler(data,nil,nil);
                          });
                      }

                  }]resume];


            }

        }

Uses:

   NSMutableDictionary *dict = [[NSMutableDictionary alloc]init];
  [dict setObject:postDeviceID forKey:@"token"];
  [dict setObject:@"iOS" forKey:@"type"];

NSData * JsonData =[NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil];

    NSString *urlStr = @"http://myurl.com/endpoint01/push_notifications"
    NSString *parameterJsonString= [[NSString alloc] initWithData:JsonData encoding:NSUTF8StringEncoding];

// If you Don't want to create Dictionary than just send your Parameter JSon what you have made , please make sure if json is correct or not 

 //parameterJsonString = [NSString stringWithFormat:@"token=%@&type=%@",postDeviceID,@"ios"];


    NSLog(@"URL:%@ Request :\n\n  %@",urlStr,parameterJsonString);


 [self postServiceAPI:urlStr andParameters:parameterJsonString andCompletionHandler:^(NSData *data, NSError *error, BOOL networkError) {

            // If Data Retrun By API is not nil

        if (data)
        {
            // Here is your Response
            NSDictionary *dicData=[NSJSONSerialization JSONObjectWithData:data options:0 error:nil];


        }
        else
        {

            // Show Alert When data is Not Recived from API
              if (networkError)
                {
                    // No Internet Connection
                }
                else
                {
                    // Network Error

                    NSLog(@"Network Error : %@",error.description);

                }

        }
 }];

i hope this will help you

like image 45
Dhiru Avatar answered Sep 18 '22 13:09

Dhiru