Delphi Labs: DataSnap XE - REST Web Application

By: Pawel Glowacki

Abstract: Today we are using "Delphi REST Application" wizard to generate a complete project consisting of a web application running in the stand-alone VCL Forms web server application and a pure JavaScript client embedded in the html markup.

    Introduction

In this lab exercise we are going to use Delphi XE to build a simple REST Web Application and a JavaScript client embedded in the html markup.

    Using “DataSnap REST Application” Wizard

The first step is to use Delphi XE new “DataSnap REST Application” wizard.

Select “File -> New -> Other” and from the “New Items” dialog double-click on the “DataSnap REST Application” icon in the “Delphi Projects -> DataSnap Server” category.

Hide image
New DataSnap REST App Icon

This wizard will create for us a complete system with a web application server and JavaScript client.

On the first screen of the wizard we can decide how our web application will be packaged. We have the option to create it as a DLL to be deployed into the IIS Web Server or as a standalone VCL application that will be both: a web server and a web application in one executable. This is new to Delphi XE and a very useful option for development, testing and deployment.

Hide image
Click to see full-sized image

expand view>>

In the first screen of the wizard keep the default “WebBroker Project Type”, which is “Stand-alone VCL application”.

Hide image
Click to see full-sized image

expand view>>

In the second screen of the wizard keep the default HTTP port 8080 and click on “Test Port” to see if it is available.

Hide image
Click to see full-sized image

expand view>>

In the third screen of the wizard with “Server Features” keep the default selection.

Hide image
Click to see full-sized image

expand view>>

Keep the default server methods ancestor class as well.

Hide image
Click to see full-sized image

expand view>>

The last screen of the wizard is a little bit tricky. You have to select your project location. The last part of the path is also going to be our project name, so cannot contain spaces. It has to be a valid project name.

Hide image
ProjectMgr

The wizard has generated for us lots of files in different subfolders. This a complete web application with cascading style sheets, images, JavaScript code, html templates and Delphi units with application logic.

Save All. Name the first unit “FormMainUnit” and the second unit “WebModuleMain”. The whole application will be called “DelphiLabsDataSnapRESTWebApp”.

Let’s have a little walk through different parts of our project.

Below is the main form of our application. This is also a kind of admin console for our web server. Buttons “Start” and “Stop” are respectively starting and stopping the web server. Pressing on “Open Browser” will launch a default web browser, which is going to host the actual client of our application: a web page with JavaScript code.

Hide image
FormMain

Below is the source code for the app main form generated by the wizard.

unit FormMainUnit;

interface
  
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, AppEvnts, StdCtrls, IdHTTPWebBrokerBridge, HTTPApp;

type
  TFormMain = class(TForm)
    ButtonStart: TButton;
    ButtonStop: TButton;
    EditPort: TEdit;
    Label1: TLabel;
    ApplicationEvents1: TApplicationEvents;
    ButtonOpenBrowser: TButton;
    procedure FormCreate(Sender: TObject);
    procedure ApplicationEvents1Idle(Sender: TObject; var Done: Boolean);
    procedure ButtonStartClick(Sender: TObject);
    procedure ButtonStopClick(Sender: TObject);
    procedure ButtonOpenBrowserClick(Sender: TObject);
  private
    FServer: TIdHTTPWebBrokerBridge;
    procedure StartServer;
    { Private declarations }
  public
    { Public declarations }
  end;

var
  FormMain: TFormMain;

implementation

{$R *.dfm}

uses
  ShellApi, DSService;

procedure TFormMain.ApplicationEvents1Idle(Sender: TObject; var Done: Boolean);
begin
  ButtonStart.Enabled := not FServer.Active;
  ButtonStop.Enabled := FServer.Active;
  EditPort.Enabled := not FServer.Active;
end;

procedure TFormMain.ButtonOpenBrowserClick(Sender: TObject);
var
  LURL: string;
begin
  StartServer;
  LURL := Format('http://localhost:%s', [EditPort.Text]);
  ShellExecute(0,
        nil,
        PChar(LURL), nil, nil, SW_SHOWNOACTIVATE);
end;

procedure TFormMain.ButtonStartClick(Sender: TObject);
begin
  StartServer;
end;

procedure TerminateThreads;
begin
  if TDSSessionManager.Instance <> nil then
    TDSSessionManager.Instance.TerminateAllSessions;
end;

procedure TFormMain.ButtonStopClick(Sender: TObject);
begin
  TerminateThreads;
  FServer.Active := False;
  FServer.Bindings.Clear;
end;

procedure TFormMain.FormCreate(Sender: TObject);
begin
  FServer := TIdHTTPWebBrokerBridge.Create(Self);
end;

procedure TFormMain.StartServer;
begin
  if not FServer.Active then
  begin
    FServer.Bindings.Clear;
    FServer.DefaultPort := StrToInt(EditPort.Text);
    FServer.Active := True;
  end;
end;

end.

Check out the “FServer: TIdHTTPWebBrokerBridge” reference in the private section of the form class, which is instantiated inside the “FormCreate” event. This is this new Indy class that implements standalone web server functionality.

Now open the “WebModuleUnit”.

Hide image
WebModuleComponentsDefault

Every WebBroker web application has its own “web module”, which is the place where all HTTP requests arriving from clients are processed. A web module is a really just a specialized type of a data module.

If you click somewhere at the surface of this the web module, you should see its properties in the Object Inspector. Probably the most important property is “Actions”, which is a collection of actions that can be executed when an HTTP request arrives at the application.

Hide image
WebModuleMain - Actions in OI

Click on the ellipsis button next to “Actions” property to display actions collection editor.

Hide image
WebModuleMain - Actions in collection dialog

Depending on the path info inside the HTTP request, a different action can be called to service a particular request and also specialized “producer” components can be used for this task.

Our web application contains two html templates (“ReverseString.html” and “ServerFunctionInvoker.html”), so there are also two “TPageProducer” components on the web module that are connected to individual actions.

Hide image
Click to see full-sized image

expand view>>

Double-click on the “ReverseString.html” file in the Project Manager and you should see the Delphi HTML designer opening the “DataStap REST Project” page.

Hide image
Click to see full-sized image

expand view>>

If you switch to the editor, you will find the following code generated by the wizard.

<!-- !DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" -->
<html>
<head>
<title>DataSnap REST Project</title>
<link rel="stylesheet" type="text/css" href="css/main.css" />
<script type="text/javascript" src="js/base64.js"></script>
<script type="text/javascript" src="js/json-min.js"></script>
<script type="text/javascript" src="js/serverfunctionexecutor.js"></script>
<script type="text/javascript" src="js/connection.js"></script>
<script type="text/javascript" src="<#serverfunctionsjs>"></script>
<script type="text/javascript">

var loginRequired = false;

function onLoad()
{
  showTime();
  loginRequired = <#loginRequired>;
  setConnection('<#host>', '<#port>', '<#urlpath>');
  if (loginRequired)
  {
    showLogin(true);
  }
  else
  {
    showLogin(false);
  }
}

function onLogin()
{
  if (loginRequired)
  {
    if (AdminInst == null)
    {
        if (!setCredentials(document.getElementById('userField').value, document.getElementById('passwrdField').value))
        {
          loginCorrect(false);
          return;
        }
        else
        {
          loginCorrect(true);
          showLogin(false);
        }
    }
  }
  else
    showLogin(false);
}

function loginCorrect(isCorrect)
{
  var errorDiv = document.getElementById('loginError');
  if ( errorDiv != null )
  {
    errorDiv.innerHTML= isCorrect ? "" : "login incorrect";
  }
}

function showLogin(show)
{
  var loginDiv = document.getElementById('logindiv');
  var contentDiv = document.getElementById('contentdiv');
  if (show)
  {
      // show div
      loginDiv.style.display="block";
      contentDiv.style.display="none";
  }
  else
  {
      // show div
      loginDiv.style.display="none";
      contentDiv.style.display="block";
  }
}

function showTime()
{
  var d = new Date();
  var h = d.getHours();
  var m = d.getMinutes();
  var s = d.getSeconds();
  var timeElement = document.getElementById('timeElement');
  if ( timeElement != null )
  {
    timeElement.innerText=
      (h <= 9 ? "0" : "") + h + ":" +
      (m <= 9 ? "0" : "") + m + ":" +
      (s <= 9 ? "0" : "") + s;
  }
}

function serverMethods()
{
  return new <#classname>(connectionInfo);
}

function onReverseStringClick()
{
  if (loginRequired && (AdminInst == null))
  {
    showLogin(true);
    return;
  }
  var valueField = document.getElementById('valueField');
  var s = serverMethods().ReverseString(valueField.value);
  valueField.value = s.result;
}

</script>
</head>
<body onload="onLoad()">
  <#serverfunctioninvoker>
    <h1>DataSnap REST Project</h1>
    <div> Page loaded at <span id="timeElement"></span>
    </div>
    <div id="logindiv" style="display:none">
      <p class="divlabel">Login</p><br />
      <form onsubmit="onLogin(); return false;">
        <table class="authtable">
          <tr>
            <td>User Name:</td>
            <td><input id="userField" class="loginField" type="text" /></td>
          </tr>
          <tr>
            <td>Password:</td>
            <td><input id="passwrdField" class="loginField" type="password" /></td>
          </tr>
        </table>
        <div id="loginError" class="errorMsg"></div><br /><input id="loginButton" type="submit" value="LOG IN" />
      </form>
    </div>
    <div id="contentdiv" class="contentdiv" style="display:none">
      <table>
        <tr>
          <td><input id="valueField" class="loginField" type="text" value="A B C" /></td>
          <td><button onclick='javascript:onReverseStringClick();'>ReverseString</button></td>
        </tr>
      </table>
    </div>
</body>
</html>

OK. Let’s see how our Delphi XE DataSnap REST server behaves at runtime. Click on the green triangle icon in the IDE to run the project. It will display the main form of the application, which is in fact our web server console. Click on “Start” and “Open Browser”.

You should see something like this:

Hide image
ReverseString in Chrome

In my case this is a web page running in Chrome browser. If you click on the “ReverseString” button, you should see that the contents of the edit are reversed!

That’s a pure JavaScript client running in the browser that has no knowledge of Delphi!

Notice that clicking on the “ReverseString” button does not change the time when the page was initially loaded. This is great for the responsiveness of the web page and for the end user experience. The JavaScript files added by the wizard implements AJAX-style server method invocation, so we do not have to worry about it. That’s a very good thing!

Now click on the “Server Functions” link at the top of the page.

You should see the special “Server Function Invoker” test page, where you can call any of the available server methods.

Expand “TServerMethods1” class and its “ReverseString” method.

Enter a string in the “Value” field and click on the “EXECUTE” button. At the bottom of the screen you should see the JSON string with the original value and the result.

Hide image
Click to see full-sized image

expand view>>

That’s cool! Now close the browser. Click “Stop” button on the server form and close the window.

    Dynamic Client Proxy Generation

Open server methods unit and remove test functions “EchoString” and “ReverseString”. Implement a different server method.

I have implemented a simple “Add” method that takes two doubles and returns a double.

unit ServerMethodsUnit1;

interface

uses
  SysUtils, Classes, DSServer;

type
{$METHODINFO ON}
  TServerMethods1 = class(TComponent)
  private
    { Private declarations }
  public
    function Add(a, b: double): double;
  end;
{$METHODINFO OFF}

implementation

function TServerMethods1.Add(a, b: double): double;
begin
  Result := a + b;
end;

end.

If you now rerun the application and open the “Server Function Invoker” you should see that the test page has been updated and now shows the “Add” method!

Hide image
Click to see full-sized image

expand view>>

How this was possible? It looks a bit like magic.

The answer is in the “OnBeforeDispatch” event of the “WebFileDispatcher1” component in the web module:

procedure TWebModuleMain.WebFileDispatcher1BeforeDispatch(Sender: TObject;
  const AFileName: string; Request: TWebRequest; Response: TWebResponse;
  var Handled: Boolean);
var
  D1, D2: TDateTime;
begin
  Handled := False;
  if SameFileName(ExtractFileName(AFileName), 'serverfunctions.js') then
    if FileAge(AFileName, D1) and FileAge(WebApplicationFileName, D2) and (D1 < D2) then
    begin
      DSProxyGenerator1.TargetDirectory := ExtractFilePath(AFileName);
      DSProxyGenerator1.TargetUnitName := ExtractFileName(AFileName);
      DSProxyGenerator1.Write;
    end;
end;

The server methods proxy code is generated dynamically. If the “serverfunctions.js” file is out of date, then it is dynamically regenerated before the page is dispatched.

That is very useful and great for dynamic languages like JavaScript. You just do not need to worry. Your proxy code is always up-to-date.

Here we came to one of the strongest and most exciting new features in the DataSnap XE architecture: pluggable proxy generators!

If you select “DSProxyGenerator1” component on the form, and expand its “Writer” property you can see that generating proxy code for clients implemented in different programming languages is just a matter of selecting a different writer!

Hide image
dsproxy generator writer options

RAD Studio XE makes it possible to build DataSnap servers in Delphi and C++Builder, and clients in Delphi, C++, JavaScript, Delphi Prism and PHP.

That’s the Delphi multitier technology for the future!

    Summary

In this lab we have used “Delphi REST Application” wizard to generate a complete project consisting of a web application running in the stand-alone VCL Forms web server application and a pure JavaScript client embedded in the html markup. We have reviewed different parts of the application and explored the functionality of “on-the-fly” client proxy generation, so the client JavaScript code is always in sync with the DataSnap server methods.

The source code for this article is available at the Code Central (http://cc.embarcadero.com) and the video version of this demo is available on YouTube (http://www.youtube.com/ ).

More information about "Delphi Labs" is available at http://blogs.embarcadero.com/pawelglowacki and http://www.embarcadero.com/rad-in-action/delphi-labs.

Server Response from: ETNASC03