Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

Webservices and Pick

5.00/5 (1 vote)
19 Aug 2013CPOL4 min read 9.2K  
Making the web work with Pick

Introduction

Articles on RESTful webservices, an article on Dojo, and another on jQuery easyUI combined to interest me in a concept of using webservices to connect to a 40 year old database environment.

My 40 year old database is Pick or Multi-value. Very popular 20 years ago, but not much written about it now although it still runs some very large systems around the world in healthcare, banking, and motor parts.

Background

Pick Databases

There are a number of flavours available, but I am using scarletdme which is an open source cut of openQM.

The advantage of this type of database is that it is impervious to most forms of attack including SQL injection. It also handles large amounts of data and could be considered a precursor to no-SQL databases and big data.

The disadvantage was that it is primarily text based and the communications are green screen or proprietary connections but this article explains how that has changed.

REST Basics

Representational state transfer (REST) is a style of software architecture and the thing about REST is that it is not tied to any particular technology or platform, requires no licence and requires no proprietary connector. A REST web service is a simply a service implemented using HTTP and the principles of REST.

Whilst the type of data supported is often JSON , XML or YAML, the easy option for the Pick world is to choose JSON (Javascript Object Notation).

JSON is text -[{"id":"EUR","text":"Euro"},{"id":"HUF","text":"Hungarian forint"},{"id":"GBP","text":"Uk pound"},{"id":"USD","text":"Us dollar"}] - is a JSON object. This is easily constructed by a routine in a Pick database from “LIST CURRENCY DESCRIPTION”. The triviality of the transformation demonstrates that this puts text based MultiValue right back in the game.

When we consider these RESTful services, it becomes clear that the service is something provided by the database server and the presentation becomes the domain of the web builder.

Using the Code

The first step is to make a connection point to the database from the web. This is achieved by creating a sites available connection within Apache2.

* create a directory to store our entry point
sudo mkdir /var/www/qmweb

* edit /var/www/qmweb/webapp
001 #!/bin/sh
002 cat – | qm –abase –quiet WEBAPP

That creates an entry point for account base where WEBAPP is our connect routine.

Head off and create a website at http://localhost/qmweb.

* edit /etc/apache2/sites–available/qmweb
001 <Location /qmweb>
002 SetHandler cgi–script
003 Options +ExecCGI
004 </Location>

Enable the site and set up the permissions correctly and restart Apache.

sudo a2ensite qmweb
sudo chown –R www–data:www–data /var/www/qmweb
sudo chmod –R 755 /var/www/qmweb
sudo /etc/init.d/apache2 reload

The major point of interest is that everything on our website downstream of /var/www/qmweb is passed to WEBAPP and we have complete control of everything that happens or is sent to our website.

To recap on how the web works, if the request is GET then the variables are sent in an environment variable QUERY_STRING. If the request is POST then the variables are sent as a buffered data string and must be retrieved with an IN or INPUT statement.

Environment variable CONTENT_LENGTH is a count of the number of characters in the data string.

The following code snippet shows how this information can be retrieved:

HTTP$ELEMENTS = ENV('QUERY_STRING')
IF CONTENT_LENGTH THEN
   * this is post
   ECHO OFF
   FOR J = 1 TO CONTENT_LENGTH
        IN A
        HTTP$DETAILS := CHAR(A)
   NEXT J
   ECHO ON
END
MX = DCOUNT(HTTP$DETAILS,'&')
EPS = 0
FOR J = 1 TO MX
   EPS += 1
   KA = FIELD(HTTP$DETAILS,'&',J)
   HTTP$DATA<1,EPS> = URLDECODE(FIELD(KA,'=',1))
   HTTP$DATA<2,EPS> = URLDECODE(FIELD(KA,'=',2))
NEXT J

MX = DCOUNT(HTTP$ELEMENTS,'&')
FOR J = 1 TO MX
   EPS += 1
   KA = FIELD(HTTP$ELEMENTS,'&',J)
   HTTP$DATA<1,EPS> = URLDECODE(FIELD(KA,'=',1))
   HTTP$DATA<2,EPS> = URLDECODE(FIELD(KA,'=',2))
NEXT J

MX = DCOUNT(HTTP$COOKIE,';')
FOR J = 1 TO MX
   KA = TRIM(FIELD(HTTP$COOKIE,';',J))
   COOKIE$DATA<1,J> = URLDECODE(FIELD(KA,'=',1))
   COOKIE$DATA<2,J> = FIELD(KA,'=',2)
NEXT J

The requested page is in environment variable PATH_INFO so we can extract the requested page.

We now have all of the information to respond to the request.

It must be remembered that every response we send must be prefaced with a Content-Type and a line feed like:

PRINT "Content–Type: text/html"
PRINT
PRINT R.DBREAD

where R.DBREAD is our webpage response.


How should we deal with webservice requests? In order to differentiate between the different types of web service we want to handle, the methods and properties need to be standardised. Since we are in complete control of our website, we can define those standards and they can be particular and peculiar to our website. Definitions of webservice for our purpose are described here.

The convention employed is that the service requested will consist of the database table plus the service definition. A service requesting a list from the country table will be described as country.lst.

In our WEBAPP routine, we define how to deal with our webservices and how to respond in case of error. The following code snippet explains this.

BEGIN CASE
   CASE REQUEST_METHOD = 'GET'   ; CALL JSON.FORMAT
   CASE REQUEST_METHOD = 'POST'  ; CALL PWEBSAVE
   CASE REQUEST_METHOD = 'PUT'   ; CALL PWEBSAVE
   CASE REQUEST_METHOD = 'DELETE'; CALL PWEBSAVE
   CASE REQUEST_METHOD = 'LIST'  ; CALL PFORMLIST
   CASE 1
        FATAL$ERROR = WEBERROR(6034:":Method:":REQUEST_METHOD:" not available")
END CASE
BEGIN CASE
   CASE REQUEST_METHOD = 'GET' OR REQUEST_METHOD = 'LIST'
        PRINT "Content–Type: text/html"
        PRINT
        PRINT COMI
   CASE FATAL$ERROR
        PRINT "Content–Type: text/html"
        PRINT
        PRINT '{"msg":':DQUOTE(EMGTEXT):'}'
   CASE 1
        PRINT "Content–Type: text/html"
        PRINT
        PRINT '{"success":true}'
END CASE

In this code, there are some variables with global scope so that appropriate messages can be returned. Because this is a webservice request, the requesting webpage is expecting a json response not a web page.

So, all of the components to build a generic website handler have now been defined. If the request is htm then load a webpage from the database. If the request is for a webservice, call the correct webservice and deliver the data. The format of the response is controlled by the server and the construct is such that we could write a web page generator for each table that we want to serve because all of the calls are of a generic nature.

I put together a website at pwchest.no-ip.biz to showcase this technology and provide code and links to go and build your own version. The website only uses a multi-value database, HTML5, CSS and some JavaScript - it is a PHP-free and SQL-free zone.

History

  • 2013-08-13: First draft
  • 2013-08-19: Additions
  • 2013-08-19: Modified the json string as it wasn't valid json

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)