USING A STANDARD WEB SERVER FOR DATA ACCESS WITH HMG AND JMG APPS (PART V/V)

THE END IS NEAR¬† ūüôā

For a real-world situation, you should consider, some other things:

Security: You should add an users table and ask for username and password to allow a client app to do anything.

Parse Parameters: The parameters must be parsed: Meaning that (ie) the spaces must be encoded as %20 or ampersands, as %26.  This is an easy thing, using StrTran() Harbour function. This is required only in HMG sample (JMG does not required that, since jQuery takes care).

More info here: http://www.w3schools.com/tags/ref_urlencode.asp

Note: Maybe HBTIP includes some function/method to handle this without using StrTran(), if so, please, let me know, Thanks!

Development Machine Setup: As mentioned in the first post, you should install XAMPP bundle (Apache, PHP, MySql and a lot of other things) and JMG in your development machine.

These are the links:

  1. XAMPP: https://www.apachefriends.org/index.html
  2. JMG: https://sourceforge.net/projects/jmg/

SQL Tables Creation: The tables required for the samples, could be created using the definitions contained in ‘data’ sub-folder of JMG distribution. You only must start ‘PhpMyAdmin’ this way: 127.0.0.1/phpmyadmin select the ‘test’ database, then import and the ‘people.sql’ file in the JMG ‘data’ folder. There is more info in JMG docs.

JMG Demo Sources: Are in JMG distribution. The code related with this tutorial is in AJAX demo section. You should add the ‘echo “ok”;’ at the end of each PHP procedure to match the HMG demo behavior shown in the tutorial.

HMG Demo Sources:

Download “hmg_web_data_demo.7z” ¬†From here

Android App!: You can easily convert any JMG app (ie: the main demo) into an Android app, using this utility: https://sourceforge.net/projects/website2apk/ (Thanks Claudio!).

Errata: In the first post, I’ve suggested that the data handling scheme shown in this tutorial, could be good for new apps, since you are not dependent on dbf files, but, in fact, you could convert your dbf files (in the case you are using it) to SQL easily, so, you could upgrade your legacy apps, to this scheme easily. You could look at HMG MySql samples for more about this.

You can discuss about this post on the HMG SourceForge forum Here

Advertisements
Posted in Uncategorized | 5 Comments

USING A STANDARD WEB SERVER FOR DATA ACCESS WITH HMG AND JMG APPS (PART IV/V)

PUTTING IT ALL TOGETHER

The last data-handling procedures of this experiment, are about modifying records.

In fact, there is little new here, but, a lot of that we already done.

  1. We need to identify the record to modify (as in delete procedures).
  2. We need to handle parameters to hold the content of ‘first’ and ‘last’ fields.

The full new concept, will be the UPDATE, SQL command, that we will use (as you can imagine) to change the content of an existing record.

Now… our ‘update.php’ server procedure:

 <?php

 $id = $_REQUEST["id"];
 $first = $_REQUEST["first"];
 $last = $_REQUEST["last"];
 
 $db = mysqli_connect( '127.0.0.1', 'root', '');

 if( ! $db ) :
 die( mysql_error() ) ;
 endif;

 if( ! mysqli_select_db($db, 'test')):
 die( mysql_error() );
 endif;

 $sql = "UPDATE people SET first='".$first."', last='".$last."' WHERE id=".$id;

 $query = mysqli_query( $db, $sql );

 if( !$query ) :
 die("error SQL");
 endif; 

 mysqli_close($db);

  echo "Ok";
?>

HMG (WINDOWS DESKTOP) CLIENT CODE:

Function main_modify_record_action

// Get Selected Grid Row

I := Main.Grid_1.Value
 
IF I == 0
    RETURN
ENDIF
 
// Get Id of record to be modified
 
cId := Alltrim(Main.Grid_1.Cell(I,1))
 
// Lets assume the server are local
// and the server procedures are in the 'php' subfolder.

cServer := '127.0.0.1'
cGet := 'http://' + cServer + '/php/update.php'

// Add parameters ('Id' of record to modify, 'first' and 'last')
// Please, consider that for real-world use, the
// parameters content, must be parsed (discussed in part V)
cGet += '?'

cGet += 'id=' + cId

cGet += '&' 
 
cGet += 'first=' + alltrim ( InputBox('Enter Your First Name','Please...', Alltrim(Main.Grid_1.Cell(I,2)) ) )

cGet += '&'

cGet += 'last=' + alltrim ( InputBox('Enter Your Last Name','Thanks!', Alltrim(Main.Grid_1.Cell(I,3))) )
 
// Call remote server procedure with required parameters

oHttp:= TIpClientHttp():new( cGet )

// Check for error
 
IF .NOT. oHttp:open()
    MsgStop ( "Connection not available!" )
    oHttp:close()
    Return
EndIf

// Get returned data

cData := oHttp:readAll()

oHttp:close() 

MsgInfo(cData)
 
// Update grid.
main_get_data_action()

Return Nil


JMG (WEB/MOBILE APP) CODE

function ajaxEdit()
{ 

// On our JMG sample, the table holding our records, is
// called 'table2'.
// We want only one row selected for editing.

if ( get('table2','selectedRowCount') == 0 )
{
    alert('No Row Selected!');
    return;
}

if ( get('table2','selectedRowCount') > 1 )
{
    alert('You must select only one row!');
    return;
}

// Getting current values from table.

var selectedRows = get('table2','selectedRows');

var selRowNumber = selectedRows[0];

var edit_id = get('table2','cell',selRowNumber,1) ;

// Prompting users for new values.

var edit_first = prompt('Please Enter First:',get('table2','cell',selRowNumber,2));

var edit_last = prompt('Please Enter Last:',get('table2','cell',selRowNumber,3));

// Calling the update.php server procedure with the required parameters.
// We will assume our server local for this tutorial

var server = '127.0.0.1';

var request = $.ajax( { url: "http://" + server + "/php/update.php" , data: { id: edit_id , first: edit_first , last: edit_last } } );

// Finally, we define the callbacks.

request.done(function()
{
    alert('Row Edit Complete!');
    ajaxRead();
});

request.fail(function(jqXHR, textStatus)
{
    alert( "Request failed: " + textStatus );
});

}


BUT… ALL THIS IS REALLY CONFUSING… ¬†ūüôā

Well… I have bad news and good news… which ¬†do you want to know first?

The bad?… I agree! ūüôā

The bad: This is a tutorial, created to keep things, as simple as possible… in a real-world situation, you must consider, a lot of other things!

The Good:¬†In the next (and final) post, I’ll publish the full source code of the example apps, and give you some useful tips!

TO BE CONTINUED…

You can discuss about this post on the HMG SourceForge forum Here

Posted in Uncategorized | 1 Comment

USING A STANDARD WEB SERVER FOR DATA ACCESS WITH HMG AND JMG APPS (PART III/V)

COMPLICATING ALL THIS A LITTLE ūüôā

Well.. we added records and retrieved them… now will delete some of them.

I’ve avoided to mention some things to keep this as simple as possible, but I can’t keep hiding certain fundamentals, so…

Our SQL server is doing some kind of magic, that consist of automatically storing an unique id on each record, in a special column that I’ve created for that.

Such column (as you can imagine) is called ‘id’.

This column (field) has the ‘AUTO_INCREMENT’ attribute and besides that, it is a primary key (we need that at least one of our columns be a primary key, because a lot of reasons).

So, each time that we add a record (on our sample, using the INSERT SQL command) the ‘id’ field is automatically filled with an unique new value that serves the purpose of identify a record (row)¬†univocally.

This column (id) is retrieved and stored in the first grid column of our HMG example (or the first column of our table in the JMG example).

We will use this ‘id’ value to identify the record to be deleted.

THE SERVER PROCEDURE

This procedure requires only one parameter (the id of the record to be deleted):

 <?php
 
 $id = $_REQUEST["id"];

 $db = mysqli_connect( '127.0.0.1', 'root', '');

 if( ! $db ) :
 die( mysql_error() );
 endif;

 if( ! mysqli_select_db($db, 'test')):
 die( mysql_error() );
 endif;

 $sql = "DELETE FROM people WHERE Id=".$id;
 
 $query = mysqli_query( $db, $sql );

 if( !$query ) :
 die("error SQL");
 endif; 

 mysqli_close($db);

  echo "Ok";

?>

THE HMG (WINDOWS DESKTOP) CLIENT CODE Simply, we get the record id, stored in the first column of the selected grid record and… delete it!

Function main_delete_record_action

 // Get Selected Grid Row

 I := Main.Grid_1.Value
 
 IF I == 0
 RETURN
 ENDIF
 
 // Get Id of record to be deleted
 
 cId := Alltrim(Main.Grid_1.Cell(I,1))

 // Lets assume the server are local
 // and the server procedures are in the 'php' subfolder.

 cServer := '127.0.0.1'
 cGet := 'http://' + cServer + '/php/delete.php'

 // Add parameters (Id of record to delete)

 cGet += '?'

 cGet += 'id=' + cId

 // Call remote server procedure with required parameters

 oHttp:= TIpClientHttp():new( cGet )

 // Check for error
 
 IF .NOT. oHttp:open()
 MsgStop ( "Connection not available!" )
 oHttp:close()
 Return
 EndIf

 // Get returned data

 cData := oHttp:readAll()

 oHttp:close() 

 MsgInfo(cData)
 
 // Update grid.
 
 main_get_data_action()
 
Return Nil

THE JMG CLIENT CODE (IF YOU WANT A MOBILE APP TOO): 

function ajaxDelete()
{ 

// In our JMG sample, the retrieved server data
// is on 'table2' widget

 // If no rows selected the action will be canceled.

 if ( get('table2','selectedRowCount') == 0 )
 {
 alert('No Row Selected!');
 return;
 }

 // We want that only one row be selected for deletion at a time!

 if ( get('table2','selectedRowCount') > 1 )
 {
 alert('You must select only one row!');
 return;
 }

 // Getting the 'id' (column 1) of the selected row.

 var selectedRows = get('table2','selectedRows');

 var selRowNumber = selectedRows[0];

 var delete_id = get('table2','cell',selRowNumber,1) ;

 // With the 'id' we call the delete.php server procedure.

// we assume that server is local
 var server = '127.0.0.1'

 var request = $.ajax( { url: "http://" + server + "/php/delete.php" , data: { id: delete_id } } );

 request.done(function( data )
 {
 ajaxRead();
 alert('Selected Row Deleted!');
 });

 request.fail(function(jqXHR, textStatus)
 {
 alert( "Request failed: " + textStatus );
 });

}

TO BE CONTINUED (update records and FULL WORKING CODE!)

You can discuss about this post on the HMG SourceForge forum Here

Posted in Uncategorized | 1 Comment

USING A STANDARD WEB SERVER FOR DATA ACCESS WITH HMG AND JMG APPS (PART II/V)

THE FUN BEGINS¬† ūüôā

In the previous post, we added records… now we will retrieve it.

For simplicity, we will retrieve all the records from our test table.

THE SERVER PROCEDURE TO GET DATA

It will be called ‘select.php’ and will be located at the ‘php’ subfolder on our server.

For simplicity, we will retrieve the all the rows on our test table, so, no parameters are required.

 <?php
 
 $db = mysqli_connect( '127.0.0.1', 'root', '');

 if( ! $db ) :
 die( mysql_error() ) ;
 endif;

 if( ! mysqli_select_db($db, 'test')):
 die( mysql_error() );
 endif;
 
 $query = mysqli_query( $db, "select * from people" );

 if( !$query ) :
 die("error SQL");
 endif;
 
 $table = array();

 while( $row = mysqli_fetch_array( $query, MYSQLI_NUM ) ):
 $table[] = $row;
 endwhile;

 $string = json_encode( $table );
 
 mysqli_free_result( $query );
 mysqli_close($db);

 echo $string;

?>

RETRIEVING RECORDS FROM OUR HMG CLIENT APP. This will be easier than adding records, since the server procedure will not require parameters. Since our server procedure send the data to us in JSON format, we will convert it, to an array to fill a Grid.

Function main_get_data_action

// Lets assume the server are local
// and the server procedures are in the 'php' subfolder.

cServer := '127.0.0.1'
cGet := cServer + '/php/select.php'

// Call remote server procedure (no parameters required)

oHttp := TIpClientHttp():new( "http://127.0.0.1/php/select.php" )

// Check for error
 
IF .NOT. oHttp:open()
    MsgStop ( "Connection not available!" )
    oHttp:close()
    Return
EndIf

// Get returned data

cData := oHttp:readAll()

// Create an array to store our recordset

aRecordSet := {}

// Decode our JSON string retrieved from server and store
// in the array

hb_jsondecode( cData , @aRecordSet )

oHttp:close()

// Fill a grid with our recordset

Main.Grid_1.DeleteAllItems

FOR I := 1 TO LEN( aRecordSet )
    Main.Grid_1.AddItem ( { aRecordSet[I][1] , aRecordSet[I][2] , aRecordSet[I][3] } )
NEXT I

MSGINFO('Done! ( ' + ALLTRIM(STR(LEN( aRecordSet ))) + ' records returned).' )

Return Nil


RETRIEVING RECORDS FROM OUR JMG CLIENT APP

We fill a table widget with our server data.

Table widget ‘load’ method will automatically decode JSON server data, making the things easier.

function ajaxRead()
{ 

// First, we call the select.php server procedure 
// to get our recordset. 
// The process is ASYNCHRONOUS, meaning that our 
// client program still runing
// WITHOUT WAIT for request completion.

// For this example, the server will be in 
// the local machine:
// The server procedure is in the 'php' sub-folder

var server = '127.0.0.1';
var request = $.ajax( { url: "http://" + server + "/php/select.php" } );

// Then we define the required callback functions 
// for the events 'done' and 'fail'.
// One of this will be automatically executed, 
// when our request be successfully finished or when fail.

request.done(function( jsonData )
{
    doMethod('table2','load',jsonData);
});

request.fail(function(jqXHR, textStatus)
{
    alert( "Request failed: " + textStatus );
});

}

TO BE CONTINUED…¬†(delete, update and THE FULL WORKING CODE!)

You can discuss about this post on the HMG SourceForge forum Here

Posted in Uncategorized | 3 Comments

USING A STANDARD WEB SERVER FOR DATA ACCESS WITH HMG AND JMG APPS (PART I/V)

INTRO:

Lets imagine a new project. Since it is new, you will not be ‘tied’ to dbf files,
then, you can think on ‘big things’ ūüôā

The ‘big thing’ could be use a standard web server, I mean an ‘AMP’ server.

AMP stands for Apache-Mysql-Php. This is the standard technology for web servers arround the world.

If your development machine is a Windows one, you could install any AMP server there for testing.

My favorite is XAMPP: https://www.apachefriends.org/index.html

Or you could use one of the many free web services available without the need of installing anything on your machine.

THE MAIN IDEA:

This will be a sort of ‘for dummies’ explanation… here we go…

1. Our HMG (or JMG) apps will call our PHP server procedures (that resides on server).

2. The remote PHP procedures are executed doing required database work.

3. The procedures returns data (or execution result) to client app calling it.

Simple as that.

SOME SERVER CODE:

Here is a simple PHP server procedure to add a record.
We will call it ‘insert.php’:

 <?php

 $first = $_REQUEST["first"];
 $last = $_REQUEST["last"];
 
 $db = mysqli_connect( '127.0.0.1', 'root', '');

 if( ! $db ) :
 die( mysql_error() );
 endif;

 if( ! mysqli_select_db($db, 'test')):
 die( mysql_error() );
 endif;

 $sql = "INSERT INTO people (first,last) VALUES ( '".$first."','".$last."')";

 $query = mysqli_query( $db, $sql );

 if( !$query ) :
 die("error SQL");
 endif; 
 
 mysqli_close($db);

 echo 'Ok';

?>

SOME CLIENT CODE FOR WINDOWS:

Well… we will work now on our Windows client (HMG) now.

Thanks to the HBTIP Harbour contribution library, we can call a PHP server
procedure, being the server on our LAN or the Internet.

This is the trick:

// Lets assume the server are local
// and the server procedures are in the 'php' subfolder.

cServer := '127.0.0.1'
cGet := 'http://' + cServer + '/php/insert.php'


// Add parameters

cGet += '?'

cGet += 'first=' + alltrim ( InputBox('Enter Your First Name','Please...') )

cGet += '&'

cGet += 'last=' + alltrim ( InputBox('Enter Your Last Name','Thanks!') )


// Call remote server procedure with required parameters

oHttp:= TIpClientHttp():new( cGet )


// Check for error
 
IF .NOT. oHttp:open()
    MsgStop ( "Connection not available!" )
    oHttp:close()
    Return
EndIf


// Get returned data

cData := oHttp:readAll()

MSGINFO('Data returned from server: ' + cData)

oHttp:close()

SOME CLIENT CODE FOR JMG (MOBILE DEVICES):

Calling a remote procedure with JMG is very easy:

To code JMG we must use JavaScript (the programming language of our web browsers).

JavaScript is very similar to PHP, so, very simple:

function ajaxNew()
{

// We prompt user for the content of the fields for our
// new record

var new_first = prompt('Please Enter First:','');
var new_last = prompt('Please Enter Last:','');


// For simplicity, by now, we will assume that we are 
// testing on a local server 
// The PHP scripts will be stored on a sub-folder 
// called 'PHP'
// We call the insert.php procedure with the two 
// required parameters

var server = '127.0.0.1'; 
var request = $.ajax( { url: "http://" + server + "/php/insert.php" , data: { first: new_first , last: new_last } } );


// The process is ASYNCHRONOUS, meaning that our client 
// program still running
// WITHOUT WAIT for request completion.
// Then we define the required callback functions for the 
// events 'done' and 'fail'.
// One of this will be automatically executed, when our 
// request be successfully finished
// or when fail.

request.done(function()
{
    alert('New Row Added!');
});

request.fail(function(jqXHR, textStatus)
{
    alert( "Request failed: " + textStatus );
});

}

TO BE CONTINUED… (retrieve, delete, update and THE FULL WORKING CODE!)

HMG: http://www.hmgforum.com/app.php/page/download

JMG: http://www.sourceforge.net/projects/jmg

You can discuss about this post on the HMG SourceForge forum Here

Posted in Uncategorized | 5 Comments

A HARBOUR ANDROID NETIO CLIENT DEMO WITH HDROIDGUI

I. INTRO

This demo is based on hddbf HDROIDGUI demo by Alexander Kresin.

Since it was created modifying ‘hddbf’ demo, such name will appear when you install (I feel a little lazy today ūüôā ).

This Android client is designed to work with the server created in the previous post.

II. INSTRUCTIONS

To test this demo on a LAN, you must do the following:

  • Run the NETIO server described and downloadable from the previous post
  • Find the local IP address of the machine running it¬†(you can use ipconfig command to get it)
  • Download the .apk linked to this post and install in an Android device.
  • Run the app, enter the address of the NETIO server machine and tap¬†the ‘Retrieve Records’ button. Don’t forget to connect your Android device to the same LAN running the server.
  • To make your server available on the Internet, please, take a look¬†at the previous post. Once configured, you must use the server public Internet address on your Android client, to get your data anywhere!

III. THE CODE

* HDROIDGUI NETIO Client Demo (Roberto Lopez)
* Based on hddbfdemo by Alexander Kresin

#include "hdroidgui.ch"

* All HDROIDGUI apps starts with HDroidMain function...

FUNCTION HDroidMain

LOCAL oWnd, oLayV, oBtn1 , oEdit1 

* We create the main window

INIT WINDOW oWnd TITLE "NETIO Client Demo" 

	* In a vertical layout the widgets are arranged from top 
	* to bottom vertically
	
        BEGIN LAYOUT oLayV SIZE MATCH_PARENT,MATCH_PARENT

	* We will add an editbox to specify the server address 
	* and a button to invoke GetRecordSet() function
	
		EDITBOX oEdit1;
			TEXT '' ;
			HINT 'NETIO Server Address' 

		BUTTON oBtn1 ;
			TEXT "Retrieve Records" ;
			SIZE MATCH_PARENT,WRAP_CONTENT ;
			ON CLICK {||GetRecordSet(oEdit1)}

	END LAYOUT oLayV

ACTIVATE WINDOW oWnd

RETURN NIL

STATIC FUNCTION GetRecordSet(oEdit1)

LOCAL oWnd, oLayV, oBrw, oBtn1, aRecordSet := {} 

*...............................................................*
* Connect
*...............................................................*

cNetServer		:= oEdit1:GetText() // Server Address
nNetPort		:= 50000            // Server Port
cNetPass		:= 'secret'         // Server Password
nCompressionLevel	:= 9                // Compression Level

lConnect := netio_connect( cNetServer , nNetPort ,, ;
            cNetPass, nCompressionLevel )

IF .Not. lConnect
	hd_MsgInfo ("Can't Connect To Server!")
	RETURN NIL
ENDIF

*...............................................................*
* Retrieve Records (An empty query string retrieves all records)
*...............................................................*

aRecordset := netio_funcexec( "query_001" , "" )

*...............................................................*
* Disconnect
*...............................................................*

netio_disconnect( cNetServer , nNetPort )   

*...............................................................*
* Create Window
*...............................................................*

INIT WINDOW oWnd ;
        TITLE 'Showing: '  + ALLTRIM(STR(LEN(aRecordset))) + ;
        ' records.'

	BEGIN LAYOUT oLayV SIZE MATCH_PARENT,MATCH_PARENT
  
		BROWSE oBrw ARRAY aRecordSet ;
                SIZE MATCH_PARENT, 0 HSCROLL

		* Some cosmetics for browse...

		oBrw:nRowHeight := 40
		oBrw:nHeadHeight := 24
		oBrw:HeadBColor := "#0000CD"

		* Add columns:

		* We must specify how the columns will be filled.
			
		* o:data contains the browse array (in our case, 
                  aRecordSet).
			
		* o:nCurrent is the current row
			
		* so, we specify o:data[o:nCurrent][1] for the 
                  column 1

		* o:data[o:nCurrent][2] for the column 2 

		* and so on...

		oBrw:AddColumn( HDColumn():New( {|o|o:data[o:nCurrent][1]},140,"Id",,ALIGN_CENTER+ALIGN_VCENTER ) )
		oBrw:AddColumn( HDColumn():New( {|o|o:data[o:nCurrent][2]},140,"Last",,ALIGN_CENTER+ALIGN_VCENTER ) )
		oBrw:AddColumn( HDColumn():New( {|o|o:data[o:nCurrent][3]},140,"First",,ALIGN_CENTER+ALIGN_VCENTER ) )
		oBrw:AddColumn( HDColumn():New( {|o|o:data[o:nCurrent][4]},140,"Street",,ALIGN_CENTER+ALIGN_VCENTER ) )
		oBrw:AddColumn( HDColumn():New( {|o|o:data[o:nCurrent][5]},140,"City",,ALIGN_CENTER+ALIGN_VCENTER ) )
		oBrw:AddColumn( HDColumn():New( {|o|o:data[o:nCurrent][6]},140,"State",,ALIGN_CENTER+ALIGN_VCENTER ) )
		oBrw:AddColumn( HDColumn():New( {|o|o:data[o:nCurrent][7]},140,"Zip",,ALIGN_CENTER+ALIGN_VCENTER ) )
		oBrw:AddColumn( HDColumn():New( {|o|o:data[o:nCurrent][8]},140,"Hire Date",,ALIGN_CENTER+ALIGN_VCENTER ) )
		oBrw:AddColumn( HDColumn():New( {|o|o:data[o:nCurrent][9]},140,"Married",,ALIGN_CENTER+ALIGN_VCENTER ) )
		oBrw:AddColumn( HDColumn():New( {|o|o:data[o:nCurrent][10]},140,"Age",,ALIGN_CENTER+ALIGN_VCENTER ) )
		oBrw:AddColumn( HDColumn():New( {|o|o:data[o:nCurrent][11]},140,"Salary",,ALIGN_CENTER+ALIGN_VCENTER ) )

	END LAYOUT oLayV

ACTIVATE WINDOW oWnd

RETURN NIL

IV. DOWNLOAD

Download “hadroidgui_demo.apk” ¬†From Here

V. LINKS

Alexander Kresin (HDROIDGUI author) Web Site: Click Here

Alexander Kresin on Github: Click Here

VI. BYE!

Enjoy!

Roberto.

You can discuss about this post on the HMG SourceForge forum Here

Posted in Uncategorized | 18 Comments

FULL HMG, INTERNET ENABLED APPS: A SIMPLE STORY

I. ONCE UPON A TIME

Some years ago, I’ve started a tiny application for one of my customers. The app, managed some¬†simple transactions and was intended to be used by a single employee on her computer only. So,¬†I’ve used a simple approach: Traditional dbf scheme with exclusive access.

Some time later, the requirement expanded and then, I’ve added a couple of new modules to meet it.

Then… Good News!… another employee could work on the app too, so, more¬†changes were required, then, I’ve put it all in a shared folder, added the required locks and¬†all gone well.

II. THE PRICE OF THE SUCCESS

The app requirements continued growing and growing and a new problem arises: The app should¬†now manage some sensitive information and (remember) it was living in a shared folder on a LAN,¬†so, I’ve been on a big trouble ūüôā

My first solution was to left the things exactly the same, except that I’ve started to encrypt¬†the data. Then… I’ve been convinced that I’ve solved my problem with relative small effort…¬†but not.

III. SUPER EASY… OF COURSE!

There is a lot of things that appears to be ‘easy’ and ‘simple’ for the customers… so…¬†one nice day I’ve received the bad news:

“Hey… we need now that the app can be used from two offices outside the city, sharing¬†the same data with us (of course). We have Internet connections everywhere… so…¬†no problem right?”

My answer was “Yes… No problem… let me think on the best implementation…”

IV. THE REMEDY IS WORSE THAN THE DISEASE?

My first thinking was remote desktop, but, to make it work I should have installed¬†a Windows server with a terminal server… but it appeared to me, to be too expensive,too complicated and (most important) a remote desktop is not as good as the real thing¬†(the app running directly on remote users computers).

My second thinking was to create VPN connections with remote clients, so my beloved¬†‘shared folder’ scheme could survive. This idea lasted very few in my head: It could¬†be very slow and I would have had to deal with the ‘remote’ IT managers, potentially¬†‘unfriendly’ to let me to mess with their networks ūüôā

So, MySql “popped” on my mind and it appeared as a big idea (at least, better than the others)

Then, I’ve finally decided to start the migration towards MySql.

V. YES!

Yes indeed: The remedy was worse than the disease, but not for technical reasons, but, personal ones.

I’ve successfully ported one module of the app to MySql, using SQLMIX RDD.

It worked perfect (technically speaking, at least) but something bothered me…

In fact… two things…

  • I love the standalone/portable EXEs:¬†it is one of the sexiest features on Harbour,¬†but MySql requires a dll.
  • SQL itself:¬†SQL is great… I know… You can do everything¬†with it. The problem is that I don’t feel comfortable with it. My brain, my heart and the rest of my body started to tell me: ‘Hey… SQL is¬†not as easy, intuitive and enjoyable as xBase is… please… STOP THIS!

Then… I’ve done it… I’ve stopped the migration process and started to¬†think again.

VI. A FUNNY THING HAPPENED IN THE WAY TO… CLIENT/SERVER

And I’ve remembered a new Harbour toy called NETIO.

It, basically, could allow you to use your actual code, designed for local access, on a LAN or even through the Internet with minor changes.

The starting tests were very disappointing, because two reasons: at first, my code was written not for efficiency, but, for reliability, simplicity and easy maintenance. Secondly, the remote clients connections were very slow.

It was a difficult moment for me… I’ve stopped all the work and started to¬†reconsider some of my previous ideas…

VII. RPC? WHAT THE F*** IS THAT?

After some days of lurking around remote database access techniques, I’ve¬†remembered something that I’ve read about many years ago: Remote Procedure Call (RPC).

Lets Wikipedia talk:

“In computer science, a remote procedure call (RPC) is an inter-process communication¬†that allows a computer program to cause a subroutine or procedure to execute in another¬†address space (commonly on another computer on a shared network) without the programmer¬†explicitly coding the details for this remote interaction. That is, the programmer¬†writes essentially the same code whether the subroutine is local to the executing program,¬†or remote.”

The following thing happened, was me trying to figure out, how to do that with NETIO.

VIII. A HAPPY END

I’ve re-started NETIO tests, but this time, I’ve created a server application and¬†moved the data handling routines there. These ‘remote’ routines were then called¬†by the ‘client’ app.

The results were amazing. Inside the LAN, NETIO is considerable faster than classic shared-folder scheme and it solves the security risk that this scheme brings.

What about the Internet?

Nearly as fast as the LAN… yes… believe or not… it works nearly the same!

If you think a little, it is very reasonable, since all the data processing is done in the server, so, all dbfs are local from the server app POV.

The data sent from the client to the server are usually some bytes only, being the only possible problem, a result recordset sent from server to client as a response to a query, but since NETIO compresses the data before sending, that is not a problem either.

IX. ARE YOU STILL READING THIS?

Well… thanks and get ready because now the best part comes…

X. THE SERVER

Our demo server will work on port 50000 (you can choose another), it will allow
RCP execution. The data will be compressed and encrypted.

LOCAL nPort		:= 50000
LOCAL cIfAddr		:= '0.0.0.0'
LOCAL cRootDir		:= '.'
LOCAL lRPC		:= .T.
LOCAL cPasswd		:= 'secret'
LOCAL nCompressionLevel	:= 9
LOCAL nStrategy		:= NIL
LOCAL pSockSrv		:= NIL

	pSockSrv := NETIO_MTSERVER( nPort , cIfAddr , cRootDir , lRPC , cPasswd , nCompressionLevel , nStrategy )
	if empty( pSockSrv )
		MSGSTOP("Can't Start Server!")
		RETURN
	endif


Now… we will add to our server, a function that will receive a query string¬†as a parameter and will return a result recordset.

*----------------------------------------------------------------------------*
FUNCTION Query_001(cQueryString)
*----------------------------------------------------------------------------*
LOCAL aRecordSet := {} , cIndex

	cQueryString := ALLTRIM(UPPER(cQueryString))

	cIndex :=  alltrim( STRTRAN ( STR ( HB_RANDOM() , 20 , 17 ) , '.' , '' )    ) + '.ntx'

	USE TEST SHARED

		INDEX ON LAST TO (cIndex) FOR ALLTRIM(UPPER(LAST)) = cQueryString .AND. ( .NOT. DELETED() )

		GO TOP

		DO WHILE .NOT. EOF()

			AADD( aRecordSet , { STR(RECNO()) , LAST , FIRST , STREET , CITY , STATE , ZIP , DTOC(HIREDATE) , IF(MARRIED,'.T.','.F.') , STR(AGE) , STR(SALARY) } )

			SKIP

		ENDDO

	USE

	DELETE FILE (cIndex)

RETURN aRecordSet

XI. THE CLIENT

Our client app, will connect to our server and show queries based on user imput.

At first we must connect:

LOCAL cNetServer	:= '127.0.0.1'	// Server Address
LOCAL nNetPort		:= 50000	// Server Port
LOCAL cNetPass		:= 'secret'	// Server Password
LOCAL nCompressionLevel := 9		// Data Compression Level

	lConnect := netio_connect( cNetServer , nNetPort ,, cNetPass, nCompressionLevel )
	IF .Not. lConnect
		MSGSTOP("Can't Connect To Server!")
	ELSE
		MSGINFO('Connected!')
	ENDIF

The user search string will be on a texbox (Main.Query_String.Value).

Since we should be prepared for large recordsets, we will use a virtual grid.

The following code will retrieve a recordset as a response to the user query and show in Grid_1:

	
	aRecordset := netio_funcexec( "query_001" , Main.Query_String.Value )

	Main.Grid_1.ItemCount := LEN(aRecordset)

netio_funcexec() is the way to execute a function located on the server (in our case¬†“query_001”).

Main.Query_String.Value is our parameter, containing the user’s search expression.

Is important to note that netio_funcexec ‘wait’ until execution on the server ends.

XII. THE DEMO

To make it easy to test, I’ve included (besides the sources and project files) the executables, son you can test immediately.

If you plan to modify it, you will need (at least) HMG 3.4.1 (with bugfix patch).

To test it, you must start the server: \netio\server\server.exe and then, the client: \netio\client\client.exe

Now you are doing ‘local’ RPC, it works well, but the real advantage of NETIO RPC, comes, when you run the server app on another machine (on your LAN or on a remote server).

Now, we will see how to do this…

XIII. NETIO ON A LAN

You just must choose a computer as your server and assure that it has a static IP (if you don’t know how to do it, please, ask to your network admin). Then you must run the server app on it.

Then, you must change ‘cNetServer’ address on your netio_connect() call to use the LAN server address on your client app (the server app will not require changes)

Now, you are doing NETIO RPC on your LAN. It is faster and safer than traditional file server (shared folder) method… A LOT!

XIV. NETIO ON INTERNET

To make available your NETIO server over the Internet, you must do some things:

  • From the LAN”s router, you must to forward the port used in your app to the server local IP.
  • Get a static public IP from your ISP
  • Change, the ‘cNetServer’ address on your client app, with the static public IP that you’ve got from your ISP.

Now you’ll be able to access your server app, from any place.

alternatively, instead of get a static IP from your ISP, you could use any dynamic IP service.

XV. THE SKY IS THE LIMIT!

Had you heard about Android, mobile devices, etc?

I’m pretty sure that your customers does ūüôā

So… sooner or later, you should face it and NETIO is the answer for that too.

HDROIDGUI is a wonderful tool,designed to write Harbour apps for Android. It supports NETIO, and its browse widgets can use an array as data source, so… you can do the math…

Yes… exactly the same server app, could allow Android clients to work along WIndows ones… and… yes again… it is so wonderful as it sounds!

XVI. DOWNLOAD THE DEMO!

That’s all, you only must download netio_demo.7z¬†From Here

XVII. DOWNLOAD HMG

HMG Latest Version: Click Here

XVIII. THAT’S ALL

Enjoy!

Roberto.

You can discuss about this post on the HMG SourceForge forum Here

Posted in Uncategorized | 17 Comments