Web Database Access from Desktop Applications

By

This article was published by DeveloperTutorials.com, 2008-05-06, as a feature tutorial.

There may be growing interest in moving computer applications from the desktop to the Web. But desktop applications are still a huge market, whose demise has been prematurely reported by the media in the past, and will most likely be reported in the future. In addition, for many domain areas, a Web browser — even for an AJAX-powered site — will be insufficient for many application needs. Microsoft is just one of several companies experiencing an assortment of technical problems with Web-based applications and software as a service (SaaS). These problems include server outages and other technical glitches.

With desktop application usage and development still going strong, every Web developer, at some time or another in their career, may encounter a problem that has long plagued desktop applications in the Internet age: If the database contains information needed by the desktop application, but the database is hosted in a Web account that does not allow remote procedure calls (RPCs) — and most of them do not allow them — then how can the desktop application communicate with the database?

There are countless situations in which this need arises. For instance, you may have created a proprietary desktop application for customers who pay for a license to use it for a limited amount of time. You probably also want to allow prospects to try your program for free, during a trial period, so they can decide if it is worth investing in a license. So how can you set up both your application and your database so that the former can query the latter to obtain the user's expiration date?

In this tutorial, we will explore a straightforward solution that I devised, and that you can use in your own systems. We will be using MySQL, but the principles apply to any other online RDBMS. The database access script and desktop application both utilize PHP, but this technique works for any desktop language that can access Web pages.

Sample Database

Most programming ideas are best taught through example, and this one is no exception. To demonstrate this technique, we will use a sample database, a desktop application, and a PHP script to connect the two together.

Continuing the aforesaid scenario, let's assume that when a prospect downloads your program, your website requires them to provide their name and e-mail address. The following SQL code will work for creating a simple but sufficient MySQL database, user, and table for storing the user data:

DROP DATABASE IF EXISTS product_database;
CREATE DATABASE product_database;

GRANT ALL ON product_database.* TO user@localhost IDENTIFIED BY 'password';

USE product_database;
DROP TABLE IF EXISTS product_registration;
CREATE TABLE product_registration (
     registration_number INT UNSIGNED AUTO_INCREMENT PRIMARY KEY
    ,first_name VARCHAR(20) NOT NULL
    ,last_name VARCHAR(20) NOT NULL
    ,email_address VARCHAR(20) NOT NULL UNIQUE
    ,expiration_date DATE
    );

INSERT INTO product_registration VALUES
     ( 1, 'John', 'Jones', 'john@example.com', '2008-05-01' )
    ,( 2, 'Jane', 'Smith', 'jane@example.com', '2008-05-30'  )
    ;

To readers unfamiliar with SQL, a brief description of the code should suffice: Firstly, we create the database itself. Secondly, we create a new user with the name "user" and the password "password" (neither value of which you should use in any production database, for security reasons), with all privileges on the database. Thirdly, we create a new table for the product registration data. Lastly, we populate that new table with two sample records.

Database Access Script

The essence of this technique is a combination of GET calls and Web page content — encrypted wherever necessary (more on that later). More specifically, the database content is made available to the desktop application in the form of a Web page, which is called by the desktop application in a URL that passes its parameters to the superglobal array $_GET.

In our example, we need a PHP script that, when given a valid registration number, returns the expiration date for that particular product registration. The following script is short, but suffices to demonstrate the methodology. For purposes of brevity, I have not included the code for executing the query to the database, contained in the function SQL_query(), which is defined in the library file SQL_functions.php. PHP programmers typically have their own preferred code for accessing databases — whether hand-coded or using a framework such as PEAR — and you can make use of your favorite approach.

The function returns an array comprising the query status as a Boolean value, and the data value if the query was successful. Note that the MySQL code would be the logical place to escape any characters in the registration number that a hacker might use for SQL injection or any other sort of attack.

Here is a script, which we will name product_expiration_date.php:

<?php
require_once 'SQL_functions.php';

if ( ! isset( $_GET[ 'registration_number' ] ) ) {
    exit( 'Error: required parameter registration_number is missing' );
}

list( $query_okay, $expiration_date ) = SQL_query( 'SELECT expiration_date FROM product_registration WHERE registration_number = '
        . $_GET[ 'registration_number' ] );
if ( ! $query_okay ) {
    exit( 'Error: invalid registration_number passed' );
}

echo $expiration_date;
?>

In this script, we first confirm that a registration number has been passed in, and, if not, terminate the script with an error message. We then query the database for the expiration date corresponding to the registration number. Should the query fail — presumably because the registration number was not found — we output an error message. The application that calls this script naturally should check for the presence of the term "Error" in the output that it receives.

Assuming that a valid registration number was passed in correctly, then the expiration date is output.

If the above PHP script is located in the root directory of your website (at http://www.example.com/), then we would access it via the URL http://www.example.com/product_expiration_date.php. Using the sample records that we populated our table with, the URL for the customer John would be http://www.example.com/product_expiration_date.php?registration_number=1. This returns a Web page consisting of the expiration date "2008-05-01".

Desktop Application Access

Even though PHP is rarely used for desktop applications, for the purposes of consistency, we will consider a test application written in PHP. Here is a function for checking whether or not the user's installation of your program has expired:

function validate_program_license( $registration_number ) {
    $expiration_date = file_get_contents( "http://www.example.com/product_expiration_date.php?registration_number=$registration_number" );

    if ( substr( $expiration_date, 0, 5 ) == 'Error' ) {
        exit( 'Error: problem accessing database' );
    }
    if ( strtotime( $expiration_date ) < date() ) {
        exit( 'Error: program license has expired' );
    }
}

Presumably, you would call this function in your PHP desktop application to confirm that the user's license is still valid. For instance:

if ( strtotime( validate_program_license( $registration_number ) ) < date() ) {
    exit( 'Error: program license has expired' );
}

In a production environment, the registration number could be stored in a hidden license file created when the user downloads and installs your application, or perhaps in the Windows Registry.

Database Updating

Up to this point, we have seen how to read data from a remote database. But what if you need to modify data within that database? The same technique can be used, as illustrated in the expanded version of the PHP script below:

<?php
require_once 'SQL_functions.php';

if ( ! isset( $_GET[ 'registration_number' ] ) ) {
    exit( 'Error: required parameter registration_number is missing' );
}

if ( isset( $_GET[ 'expiration_date' ] ) ) {
    $query = SQL_query( 'UPDATE product_registration SET expiration_date = "' . $_GET[ 'expiration_date' ]
            . '" WHERE registration_number = ' . $_GET[ 'registration_number' ] );
    if ( ! $query_okay ) {
        echo 'Expiration date updated';
    }
    else {
        exit( 'Error: invalid registration_number passed' );
    }
}
else {
    list( $query_okay, $expiration_date ) = SQL_query( 'SELECT expiration_date FROM product_registration WHERE registration_number = '
            . $_GET[ 'registration_number' ] );
    if ( ! $query_okay ) {
        exit( 'Error: invalid registration_number passed' );
    }
    echo $expiration_date;
}
?>

For example, if you want to add another month to Jane's license, the following URL would work: http://www.example.com/product_expiration_date.php?registration_number=1&expiration_date=2008-06-30

Conclusion

This tutorial demonstrates how your desktop applications can read from and update server-side databases that are otherwise inaccessible. Yet there is more that you can do along these lines: For instance, we did not address the issue of security. Obtaining the expiration date of a registration record is of little value to the malicious user. In contrast, if such a user were to somehow detect the URL that your desktop application is calling in order to update the program's expiration date, then he could set his own registration record to have an expiration date far off in the future, and thus effectively avoid having to pay to renew his license for your program. For any such sensitive data access or modification, you should encrypt the values being passed back and forth between your desktop application and the PHP scripts on your server, so hackers cannot exploit your system.

As noted earlier, this general technique is not limited to MySQL and PHP. It can be used for any server-side database, any Web scripting language that can access such a database, and any programming language that can be used on the desktop and can get the contents of Web pages — using either native functions or making use of a Web access library.

Copyright © 2008 Michael J. Ross. All rights reserved.