13

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"
)
Brittany
  • 1,359
  • 4
  • 24
  • 63
  • Possible duplicate of [How to send json data in the Http request using NSURLRequest](https://stackoverflow.com/questions/4456966/how-to-send-json-data-in-the-http-request-using-nsurlrequest) – tomfriwel Jun 29 '17 at 01:22
  • This is answerable, but a few clarifications: to make this code run *after* the user auth, it has to be triggered by login's completion block. Are you sure that's happening? I don't see any user auth being explicitly setup in the OP code, is that done through the cookie? Finally, have you checked to see that `postDeviceID` contains a reasonable looking CSRF token? `postDeviceID` is an unintuitive variable name for it, or maybe I'm misunderstanding that. – danh Jul 02 '17 at 21:42
  • @Brittany I getting NSURL connection error msg : `Could not connect to the server` Please make sure you sever is sending the data back to client. – Subramanian P Jul 03 '17 at 05:30
  • I agree with danh: Is it really called AFTER the authentification? Could you show us screenshot of what you do in Firefox POSTER? Could you show also the rest of the delegates methods? – Larme Jul 03 '17 at 05:49
  • i have created block method with block for your which will simplify your code , if you are using multiple times API , i would Suggest your to make a separate class for Posting data to server , and call this method from anywhere in the project . @Brittany , I hope this Will help you , check my answer below – Dhiru Jul 05 '17 at 12:21
  • Did you find any answers suitable? – Khokim Mamarasulov Jul 07 '17 at 12:18
  • None have worked so far @KhokimMamarasulov :/ Do I need to put some reference to the CSRF token in my POST method? I do in FireFox Poster so perhaps that's what I'm missing in my code? That said, how might I go about adding this? I've tried including it and still, no dice. Perhaps I'm writing that line wrong? – Brittany Jul 07 '17 at 17:01
  • @Brittany yes, I think that might be helpful. And one more question, do you send your csrf token in post data or appending it as query? – Khokim Mamarasulov Jul 07 '17 at 20:14
  • @KhokimMamarasulov The CSRF Token is created when my user logs in - that said, I should be sending it inside of my POST data, as that's how I do it in Firefox Poster; I'm just not sure how my code should look doing this in obj-c? – Brittany Jul 07 '17 at 23:17
  • You add a parameters if you want to send crfs token to the server in `NSMutableRequest` ..... Generally we send token in header only ...... See my answer below and put your token In `NSMutableRequest` @Brittany – Dhiru Jul 10 '17 at 01:20
  • Now see my updated Answer , see in the post method.... – Dhiru Jul 10 '17 at 01:29
  • NSURLConnection is deprecated, is there a reason you use this? – HAS Jul 10 '17 at 05:38

4 Answers4

9

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.

Gero
  • 4,394
  • 20
  • 36
  • Gave this a try - I get "Yay, done! Check for errors in response!" So I assume it connects, however I don't see any errors in the response, and my data still doesn't POST to the drupal endpoint. I thought this might be because a CSRF token was necessary, however that's already provided if my user is logged in. Thoughts? – Brittany Jul 07 '17 at 16:56
  • See code edit and NSLog above - posted error log. It seems that the CSRF validation failed? Why might this be? – Brittany Jul 18 '17 at 00:34
  • Nevermind, I FINALLY got it working by adding the following line to your code: [request addValue:csrfToken forHTTPHeaderField:@"X-CSRF-Token"]; THANK YOU for your help!!! – Brittany Jul 18 '17 at 01:09
  • Glad you finally got it. Yes, it's not uncommon you have to provide a token like this (the name might be different in for services). Unfortunately I kind of overlooked your comment from the 7th and didn't know you were working with a login mechanism, sorry. Btw, I just remembered/saw you typed a non-ssl URL in your question. If your actual server is also not using HTTPS, you might want to read up here, too: https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW33 – Gero Jul 18 '17 at 06:49
6

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

Dhiru
  • 3,040
  • 3
  • 25
  • 69
1
//Generate your postData
NSDictionary *postDict = @{@"token":postDeviceID, @"type":@"ios"};

//convert object to data
NSError *error;
NSData *postData = [NSJSONSerialization dataWithJSONObject:postDict options:kNilOptions error:&error];

NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setHTTPMethod:@"POST"];
[request setURL:[NSURL URLWithString:@"http://myurl.com/endpoint01/push_notifications"]];
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
[request setHTTPBody:postData];

 NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];

 if(conn) {
     NSLog(@"Connection Successful");
 } else {
     NSLog(@"Connection could not be made");
 }
tomfriwel
  • 2,575
  • 3
  • 20
  • 47
  • Sorry - are these edits I'm supposed to make to my existing code? – Brittany Jun 29 '17 at 02:09
  • Tried your edit, and the data still doesn't seem to POST. I look through the drupal back logs and it says the data is being passed, but it doesn't seem to be storing :/ – Brittany Jun 29 '17 at 18:02
  • It appears to be correct? When I check the drupal report logs, it sees the data being passed, but it doesn't seem to be "sticking"/posting... – Brittany Jul 02 '17 at 20:37
  • FYI: I get "connection successful" returned, the data just doesn't seem to be actually posting O_O – Brittany Jul 02 '17 at 20:56
  • Sorry, I mistake. – tomfriwel Jul 02 '17 at 22:19
  • You will always get that log message, since your if statement doesn't actually check for succeeding connections but rather just for the successful creation of the object. See my answer below for more details. – Gero Jul 05 '17 at 10:10
1

Is your JSON data must be in the form of String or Dictionary as said by tom

NSString *post = [NSString stringWithFormat:@"token=%@&type=%@",postDeviceID,@"ios"]; - Is it correct?

If YES.

Can you check with Asynchronous Request using NSURLConnection instead of delegate methods.

NSString *post = [NSString stringWithFormat:@"token=%@&type=%@",postDeviceID,@"ios"];
     NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
     NSString *postLength = [NSString stringWithFormat:@"%lu",(unsigned long)[postData length]];

     NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
     [request setHTTPMethod:@"POST"];

     [request setValue:postLength forHTTPHeaderField:@"Content-Length"];
     [request setURL:[NSURL URLWithString:@"http://myurl.com/endpoint01/push_notifications"]];
     [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
    [request setHTTPBody:postData];

[NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
     {
         if(data!=nil)
         {
             id res =[NSJSONSerialization
                      JSONObjectWithData:data
                      options:kNilOptions
                      error:nil];
 }
     }];

Hope it helps.

Vidhyanand
  • 5,369
  • 4
  • 26
  • 59
  • How do I know if it's supposed to post as a string or dictionary? I would assume dictionary, as multiple values are required? But I've tried both string and dictionary and no dice. I'll give the above a shot and report back :) – Brittany Jul 03 '17 at 18:20