Thursday, March 11, 2021

REST service programming using the UniBasic Extensions

 Previously I posted about the simple case of executing an HTTP GET against a URL with querystring parameters and then catching and parsing the JSON return from a REST service.  However recently I needed to dig a little deeper into UniBasic based REST service programming. My goal was to interact with Text Messaging services from Manage 2000 UniBasic code.

Nexmo and Twilio, at least, provide extensive examples for interfacing with their service from various environments, but not Multi-Value. In each case they provide handy toolkit wrappers that hide all the actual service details and expose the interface as some sort of object with methods and properties, sometimes coded as a JSON request string.

Unfortunately to implement an interface from within UniData I needed to understand some of the things that their nice little wrappers were hiding. One could imagine the JSON request string posting to the service URL and returning another JSON request string. That is, in fact, NOT what happens.

These services accept HTTP POST requests with form variables and then return JSON responses. So simply creating a GET request with a querystring, like my previous post, will not get the job done. Neither will changing the GET to POST and assuming the querystring variables will be magically transformed into form variables in the fashion of the jQuery AJAX command.

Now the UniBasic Extensions submitRequest command does includes a spot for post data:

 * submitRequest(request_handle, time_out, post_data, response_headers, response_data, http_status)

And one is tempted to try to put some type of querystring name=value expression into post_data, but I  could not get that to work. If these sites did accept JSON post data, then it would obviously fit into this argument.

What does work for x_www_form_urlencoded requests is adding in form variables to an HTTP POST request using addRequestParameter:

Ret = addRequestParameter(HANDLE, NAME, VALUE, "text/plain")

Using addRequestParameter works for both GET querystring handling and POST form variable handling so you get the nice symmetry that jQuery AJAX command exhibits.

This has the added benefit of encoding the text you are stuffing into parameters so you don't inadvertently corrupt the request.

The UniBasic Extensions HTTP POST pattern involves:

  1. preparing the context for the HTTP Request using a number of setHTTP... commands
  2. creating the request
  3. adding the form variables with addRequestParameter
  4. submitting the request

So a whole Request transaction might go something like:

 * Prepare HTTPS Request

     SECURITY.VERSION = "TLSv1.1"
     HTTP.METHOD = "POST"
     QS = ""
     CONTEXT = ""
     HTTP.REQUEST.HANDLE = ""
     HTTP.RESPONSE.HEADERS = ""
* Set HTTP version
    Ret = setHTTPDefault("VERSION", "1.1")
 * Add basic authentication credentials
     Ret = setHTTPDefault("AUTHENTICATE", AUTH.TOKEN)
 * Add content-type header
     HTTP.HEADERS = "content-type":@VM:"x-www-form-urlencoded"
     HTTP.HEADERS<2> = "user-agent":@VM:"UniData 6.0" 
     Ret = setHTTPDefault("HEADERS", HTTP.HEADERS) 
 * Create a security context.
     Ret = createSecurityContext(CONTEXT,SECURITY.VERSION)
 * Set the authentication to 'generous' while testing, otherwise UniData will require a certificate
     Ret = addAuthenticationRule(CONTEXT ,'2', 'VerificationStrength' , 'generous')
* Create Request
     Ret = createSecureRequest(URL:"?":QS,HTTP.METHOD,HTTP.REQUEST.HANDLE, CONTEXT)
 * Add POST DATA using addRequestParameter
     PARAM.CNT = DCOUNT(PARAM.NAMES,@AM)
     FOR PARAM.IDX = 1 TO PARAM.CNT
        Ret = addRequestParameter(HTTP.REQUEST.HANDLE, PARAM.NAMES<PARAM.IDX>, PARAM.VALUES<PARAM.IDX>, "text/plain")
        IF Ret <> 0 THEN 
           HTTP.RESPONSE.ERROR = "Error in addRequestParameter ":PARAM.NAMES<PARAM.IDX>:" ":Ret 
           RETURN
        END        
     NEXT     
* Submit Request
    Ret = submitRequest(HTTP.REQUEST.HANDLE,'','',HTTP.RESPONSE.HEADERS,HTTP.RESPONSE.DATA,HTTP.RESPONSE.STATUS)
 
Of course you want to check 'Ret' after each operation and expose error messages for trouble shooting.

This pattern will allow you to POST requests out of UniBasic to REST service endpoints and receive their responses. You may want to look into the UDO (U2 Dynamic Objects) commands for parsing the returned JSON.

All of this 'plumbing' code has been wrapped up in a new Tools subroutine named REST.SERVICE. More on that later.