Thursday, June 12, 2014

Communicating with the Server

This article and tutorial continues from the article on data serialization. I have modified the code slightly for the purposes of this demonstration. In this particular example, we make use of a PHP server script. You will need to make modifications to the code if you are using a different server side script or if your script is located at a different URL than the one described in the code.

Synchronous Data Requests

To the code that we finished the previous tutorial with, add the following at the end of the applicationDidFinishLaunching method:
/**********
     send data to server
     **********/
    var urlReq = [CPURLRequest requestWithURL:@"http://localhost/DataTransfer/backend.php"];
    [urlReq setHTTPMethod:@"POST"];
  
    var jsonServerResponse = [self getServerResponse:urlReq useSerializedData:_JSONString],
        P280NServerResponse = [self getServerResponse:urlReq useSerializedData:_280String],
        XMLServerResponse = [self getServerResponse:urlReq useSerializedData:_XMLString];
    console.log( "JSON" );
    console.log( jsonServerResponse._rawString );
    console.log( "P280N" );
    console.log( P280NServerResponse._rawString );
    console.log( "XML" );
    console.log( XMLServerResponse._rawString );
Also, add the following method to AppController:
-(CPString)getServerResponse:(CPURLRequest)urlReq useSerializedData:(CPString)string
{
    [urlReq setHTTPBody:string];
    return [CPURLConnection sendSynchronousRequest:urlReq returningResponse:Nil];
}
If you are running PHP on your server, then in your web root, create a file called 'backend.php', and put into it the following code:
<?php
echo $HTTP_RAW_POST_DATA;
?>
Run the application, and you will see the following:

What is more important is the console's display.  Open the console and reload the page to see the following:
What we have done here is the following:

We created three serialized strings. We created a url request object, and set the method to POST, because POST methods do not contain the content within the url string and are considered more secure than GET methods. Then we added our string to the body of the url request and used a url connection object to send the data to the server at the url address encoded in the url request.  The server processed the string, but for those familiar with PHP, we note that the data was not parsed by the server side script in the usual manner (we will address this a little later). Rather than store the data, we simply put the received data in the response data so that our program has access to it once again. Doing this, we simulate both the 'sending' and the 'retrieving' actions which you will need to communicate with your server applications.

Asynchronous Data Requests

The class-scoped method used above sends a synchronous data request. We started with this, because it is simple and often all that is needed. Sometimes, we want to make the request and let the processing continue while we wait for the response. This is called an asynchronous request.

When we make asynchronous requests, the system gets just a little more complicated.  In a synchronous request, we dump the result directly into a variable and continue. With an asynchronous request, we are continuing before we have data to process, so we must use a delegate. With a delegate, the url connection will send updates as the server sends responses.  On the final response, the delegate is able to grab the data and put it in the intended container as well as start any processing methods to handle the data.

The delegate functions called by CPURLConnection are as follows:

  • -(void)connection:(CPURLConnection)connection didReceiveData:(CPString)data;
  • -(void)connectionDidFinishLoading:(CPURLConnection)connection;
  • -(void)connectionDidReceiveAuthenticationChallenge:(id)connection;

We implement these methods in any object we choose and set that object as the delegate, and we are then able to receive data, be notified when the URL has finished loading and be notified when the URL responds by requesting authentication from the user.

For now, we are just interested in receiving the data, so lets add the following implementations to the AppController.j file:
@implementation ASyncLogger : CPObject
{
    CPString indicatorText;
}
-(id)initWithString:(CPString)text
{
    if( self = [super init] ){
        indicatorText = text;
    }
    return self;
}
-(void)connection:(CPURLConnection)connection didReceiveData:(CPString)data
{
    console.log( "DATA RECEIVED FOR " + indicatorText );
    console.log( data );
}
@end
@implementation JSONLogger : ASyncLogger
{
}
+(id)init
{
    return [[JSONLogger alloc] initWithString:"JSON"];
}
@end
@implementation P280NLogger : ASyncLogger
{
}
+(id)init
{
    return [[P280NLogger alloc] initWithString:"P280N"];
}
@end
@implementation XMLLogger : ASyncLogger
{
}
+(id)init
{
    return [[XMLLogger alloc] initWithString:"XML"];
}
@end
Change the data communication section of applicationDidFinishLoading to the following:
    /**********
     send data to server
     **********/
    [self useSerializedData:_JSONString withDelegate:[JSONLogger init]];
    [self useSerializedData:_280String withDelegate:
        [P280NLogger init]];
    [self useSerializedData:_XMLString withDelegate:
        [XMLLogger init]];

We are not using the same url request for each call any more.  With asynchronous requests, it is best that we keep the elements separate so that we do not alter any ongoing actions.  Add the following method to the AppController class to create and send the url requests:
-(void)useSerializedData:(CPString)string withDelegate:(id)aDelegate
{
    var urlReq = [CPURLRequest requestWithURL:@"http://localhost/DataTransfer/backend.php"];
    [urlReq setHTTPMethod:@"POST"];
    [urlReq setHTTPBody:string];
    [[CPURLConnection alloc] initWithRequest:urlReq delegate:aDelegate startImmediately:YES];
}
When we run the code, we see the following in the console:

This is a success. We see that the data service calls are successful, because our new objects are listing data to the console along with the "DATA RECEIVED FOR XYZ" text that is unique to those new classes. We can prove that this call is asynchronous by adding the following lines to our PHP code:
    if( preg_match('/^[{]/', $HTTP_RAW_POST_DATA) ){
                   sleep(5);
    }
When we re-run the script, the output in the console changes to this:
 The JSON data is now listed after the other data, but why is this so important, and how did it happen?

The additional code that we added to the PHP script was a time waster.  It checked the post data for the JSON format by looking at the first character.  If the data passed is JSON, then the script goes to sleep for a short period of time and does nothing, but then it responds as normal.

We made our function calls in the same order as before. The JSON function call is made first, but because of the time wasting script, it takes much longer to processes. The other two server calls are made after the JSON call, and each is returned before the JSON call. The shows that the program was able to continue operating after the JSON call was made, and that it received the server calls independent of the order of the written code.

PHP "Anomalies" (Note:This information can be useful to other Server-side languages)

Earlier in the code, we saw an unexpected response from PHP. For those familiar with PHP, they might expect the PHP code to receive the data into the $_POST object. When the $_POST object is empty, you can always check the $HTTP_RAW_POST_DATA variable or dump the $GLOBALS variable to find your communicated data string.  According to the PHP manuals, data communicated in the form "key1=value1&key2=value2" in the raw data will be parsed into a key-valued array, but the parsing behavior only happens when we set the header field for Content-Type.

I have tried many values for Content-Type to try to cause the behavior.  So far, the only value that I have tried that is successful is: "application/x-www-form-urlencoded".  All others fail to cause the $_POST value to populate.

For the following example, I have changed the php code in the backend.php file to the following:
<?php
    var_dump( file_get_contents( "php://input" ) );
    echo "\n\n";
    var_dump( $GLOBALS );
?>

This script will dump the raw input followed by the full array of global variables available for use in the script.  In the first screenshot, we see the results of the Content-Type being set to "application/x-www-form-urlencoded".  In the second screen shot, we have changed it to one of several other settings.  An easily accessible list is available on Wikipedia's Mime Type page.

Content-Type: application/x-www-form-urlencoded
Content-Type: application/json
It is important to note that when the data is parsed into $_POST that it is no longer available in $HTTP_RAW_POST_DATA. If you are using server-side languages other than PHP, you should note that the data might go through similar processes. Take some time to familiarize yourself with your server-side language, or at the very least, learn how to access the raw, unprocessed data.  In PHP, accessing raw unprocessed data is as simple as reading from the file-stream "php://input".

No comments:

Post a Comment