Smart Mobile Studio
  • News
  • Forums
  • Download
  • Store
  • Showcases
    • Featured demos
    • The Smart Contest 2013, Round 1 – Graphics
  • Documentation
    • Get the book
    • System requirements
    • Prerequisites
    • Getting started
      • Introduction
      • Application architecture
      • The application object
      • Forms and navigation
      • Message dialogs
      • Themes and styles
    • Project types
      • Visual project
      • Game project
      • Console project
    • Layout manager
    • Networking
      • TW3HttpRequest
      • TW3JSONP
      • Loading files
  • About

Writing REST Clients with Smart 2.1.2

Posted on 14.12.2014 by gabr42 Posted in Documentation, News

Besides a bunch of fixes, Smart 2.1.2 introduced a new unit, SmartCL.Inet.REST. This unit greatly simplifies writing REST clients in Smart. A simplest way to introduce it is with an example.

Introduction

Let’s say we have a REST server listening at http://someserver/hbbtv/streamevents. It supports one request – PUT – and three parameters – applicationid, eventid, and eventdata. To send a request, one would just open a HTTP connection (TW3HttpRequest would do that for you in Smart) and send something like http://someserver/hbbtv/streamevents/applicationid=1&eventid=233&eventdata=Test (or http://someserver/hbbtv/streamevents/1/233/Test if your REST server is not completely braindead). Still, you would have to set up the TW3HttpRequest object, concatenate the URL and parse the response. And whenever you write some code, there’s an opportunity to introduce a bug so it’s best if you code as little as possible.

The REST client for the example above can be written in one statement.

REST['someserver', '/hbbtv/streamevents']
  .Get([1, 233, 'Test'])
  .OnDone(lambda (http) Log('Event sent'); end)
  .OnError(LogHttpError);

We can deconstruct the code as follows:

  • REST[server, path] specifies an API – an entry point to a REST server. Typically you would store the result of this operation in a local variable or in a field if you issue more than one request to the same API.
  • .Get([parameters]) constructs an URL request and sends it to the server.
  • .OnDone provides an event which is called on completion (when response is received from the server).
  • .OnError provides an event which is called in case of a TCP error (server not found etc). This event is also called if REST server respondes with a HTTP code not in the 2xx range (for example with 404 not found).

sendeventThe improvements over plain TW3HttpRequest become even more obvious in a real-life application. The code fragment below comes from a small Smart app which I’m using for in-house tests. It allows the user to enter server name and all three parameters and then sends the request when a button is clicked.

const
  CHbbTvAPI = '/hbbtv/streamevents';

REST[inpServerAddress.Text, CHbbTvAPI]
  .Get([inpApplicationID.Text, inpEventID.Text, inpEventData.Text])
  .OnDone(lambda (http) Log('Event sent'); end)
  .OnError(LogHttpError);

More examples

ipsearchLet’s take a look at few other examples. New in Smart 2.1.2 is the REST Client demo, which can be found in the Demos\Featured Demos\Business\ folder. It contains many examples which access public REST servers kindly provided by http://jsontest.com.

The IP Address button demonstrates the simplest possible REST call.

  // simple inline create&send request
  //   Create: REST[]
  //   Send: Get()

  REST['http://ip.jsontest.com', '']
    .Get()
    .OnDone(LogIPAddress)
    .OnError(LogRESTError);

The path is empty (which is equivalent to the ‘/’ path) and there are no parameters. The server, however, sends a result, which is processed in the LogIPAddress method.

procedure TFormMain.LogIPAddress(http: TW3HttpRequest);
begin
 // simple response processor
 var resp := JSON.Parse(http.ResponseText);

 // resp is of type Variant
 if VarIsValidRef(resp.ip) then
   Log('Your IP address is ' + resp.ip)
 else
   Log('Failed to parse server response: ' + http.ResponseText);
end;
Response handler receives the TW3HttpRequest as a parameter so it can access its properties. As the server at ip.jsontest.com returns the response in JSON format ({"ip": "213.253.107.11"}), we are using JSON.Parse to parse it into a Variant variable resp. Then we can access JSON fields as fields of this variable (ip in this example). It is a good habbit to check whether the field was present at all in the JSON response (VarIsValidRef).

The error handler LogHttpError is used in all examples and simply logs HTTP status code and response text.

procedure TFormMain.LogRESTError(http: TW3HttpRequest);
begin
 Log('ERROR ' + http.Status.ToString + ' ' + http.StatusText);
 Log(' ' + http.ResponseText);
end;

The Date and time example is very similar except that the REST API is initialized in the InitializeForm method. Button’s OnClick handler then just issues the Get request and handles the response.

// create 'Date & time' API entry point
FDateAPI := REST['http://date.jsontest.com', ''];
// set default error handler
FDateAPI.OnError(LogRESTError);

procedure TFormMain.btnDateTimeClick(Sender: TObject);
begin
 // use pre-created API
 FDateAPI.Get().OnDone(LogDateTime);
end;

LogDateTime shows a different approach to JSON parsing. Instead of Variant, we can define an appropriate class derived from JObject (in this example TRestData which itself derives from JObject is used as a parent).

//Expected JSON response:
//{
//   "time": "12:10:22 PM",
//   "milliseconds_since_epoch": 1418559022932,
//   "date": "12-14-2014"
//}
type
 TDateAndTime = class(TRestData)
 time: string;
 date: string;
 end;

procedure TFormMain.LogDateTime(http: TW3HttpRequest);
begin
 // use an object to accept returned data
 var dt := TDateAndTime(JSON.Parse(http.ResponseText));

 // dt.date will be 'undefined' if a bad response is returned
 // this may cause problems in a more complex code but works fine here
 Log('Date: ' + dt.date);

 // better way is to test explicitely
 Log('Time: ' + if VarIsValidRef(dt.time) then dt.time else 'unknown');
end;

The third test, Echo, is a bit weird, as the server expects URL in form of /key/value pairs like http://echo.jsontest.com/one/test/two/test2/three/test3. This example URL will return the following JSON response.

{
   "two": "test2",
   "one": "test",
   "three": "test3"
}

The code uses hardcoded key names one and two and just inserts user-provided values in appropriate places. Response is not parsed, just logged.

  // use a on-the-flight created API
 REST['http://echo.jsontest.com', '']
   // append parameters to the path
   .Get(['one', inpEcho1.Text, 'two', inpEcho2.Text])
   // just log the response without parsing; anonymous method is used this time
   .OnDone(lambda(http) Log('Response: ' + http.ResponseText); end)
   // standard error handler
   .OnError(LogRESTError);

The validate.jsontest.com service validates a given JSON object against the reference parser. Entry point is created in the InitializeForm method.

// create 'Validation' API entry point and set default error handler
FValidationAPI := REST['http://validate.jsontest.com', '/'].OnError(LogRESTERror);

There are two buttons on the form – one sends a valid JSON data and the other an invalid one.

Valid JSON is created by stringifying an instance of the TDateAndTime object. Data is then sent to the server with the Post method (instead of a Get as in all examples above). Because the server expects the JSON string to be passed in a HTML encoded form, we must encode it with HTMLTextEncode. We must also set Content-Type header to be application/x-www-form-urlencoded. To do that, the code passes an additional parameter header: array of string; to the request.

// send a valid JSON and log the response
// test JSCN is built directly from a JavaScript object and should therefore always be valid
var testJson := JSON.Stringify(TDateAndTime.Create);

// validate.jsontest.com expects parameter to be in a form of a key=value pair
// also, content type must be set to x-www-form-urlencoded so we'll send an appropriate header
FValidationAPI.Post('json=' + HTMLTextEncoder.Encode(testJson), [],
  ['Content-Type: application/x-www-form-urlencoded'])
  .OnDone(lambda(http) Log('Response: ' + http.ResponseText); end);

The second button sends a manually created invalid JSON. This time, the JSON is sent as a Get request parameter which simplifies the call as we don’t have to provide the custom content type. The server expects a named parameter (json=…) and currently the only way to achieve this is to manually prefix the parameter with the ?json= string.

// send an invalid JSON and log the response
// this time, JSON is sent with Get, not Post
FValidationAPI.Get(['?json=' + HTMLTextEncoder.Encode('{"key":"value"')])
  .OnDone(lambda(http) Log('Response: ' + http.ResponseText); end);

The last example sends a string to the server and receives a JSON response containing MD5 hash of the string and original data. Two equivalent ways to call the server – using a Get and a Post are shown.

type
  TMD5Response = class(TRestData)
    md5: string;
    original: string;
  end;

procedure TFormMain.btnMD5Click(Sender: TObject);
begin
  var FMD5API := REST['http://md5.jsontest.com', ''];
  var source := HTMLTextEncoder.Encode(inpMD5Source.Text);

  // you can also set an onPrepare handler which can inspect/modify
  // http request before it is sent
  FMD5API.Get(['?text=' + source], [], LogHttpDetails).OnDone(LogMD5);

  // we can also modify headers in onPrepare instead of passing them
  // as a parameter
  FMD5API.Post('text=' + source, [], [], LogHttpDetails).OnDone(LogMD5);
end;

procedure TFormMain.LogMD5(http: TW3HttpRequest);
begin
  var resp := TMD5Response(JSON.Parse(http.ResponseText));
  Log("MD5('" + resp.original + "') = " + resp.md5);
end;

« Smart Mobile Studio 2.1 (Hotfix 2)
Using DataSnap Connector Importer »

Pages

  • About
  • Feature Matrix
  • Forums
  • News
  • Release History
  • Download
  • Showcases
    • The Smart Contest 2013, Round 1 – Graphics
  • Store
  • Documentation
    • Creating your own controls
    • Debugging, exceptions and error handling
    • Differences between Delphi and Smart
    • Get the book
    • Getting started
      • Introduction
      • Local storage, session storage and global storage
      • Application architecture
      • The application object
      • Forms and navigation
      • Message dialogs
      • pmSmart Box Model
      • Themes and styles
    • Layout manager
    • Networking
      • Loading files
      • TW3HttpRequest
      • TW3JSONP
    • Prerequisites
    • Real data, talking to sqLite
    • System requirements
    • Project types
      • Visual project
      • Game project
      • Console project

Archives

  • December 2019
  • December 2018
  • November 2018
  • July 2018
  • June 2018
  • February 2018
  • September 2017
  • April 2017
  • November 2016
  • October 2016
  • September 2016
  • April 2016
  • March 2016
  • January 2016
  • October 2015
  • September 2015
  • July 2015
  • April 2015
  • January 2015
  • December 2014
  • October 2014
  • September 2014
  • August 2014
  • July 2014
  • June 2014
  • March 2014
  • February 2014
  • January 2014
  • December 2013
  • November 2013
  • October 2013
  • August 2013
  • July 2013
  • June 2013
  • May 2013
  • April 2013
  • March 2013
  • February 2013
  • January 2013
  • December 2012
  • November 2012
  • August 2012
  • July 2012
  • June 2012
  • May 2012
  • April 2012
  • March 2012
  • February 2012
  • January 2012
  • November 2011
  • October 2011
  • September 2011

Categories

  • Announcements (25)
  • Developers log (119)
  • Documentation (26)
  • News (104)
  • News and articles (16)

WordPress

  • Register
  • Log in
  • WordPress

Subscribe

  • Entries (RSS)
  • Comments (RSS)
© Optimale Systemer AS