Delphi Labs: DataSnap XE - REST Web アプリケーション

By: Chikako Yonezawa

Abstract: 本日は、スタンドアロン VCL フォームの Web サーバーアプリケーションで動作している Web アプリケーションと、html マークアップに埋め込まれたピュア JavaScript クライアントで構成された完全なプロジェクトを生成するための "Delphi REST アプリケーション" ウィザードを使用します。

    はじめに

このラボでは、Delphi XE を使用して、簡単な REST Web アプリケーションと、html マークアップに埋め込まれた JavaScript クライアントを構築します。

    「DataSnap REST アプリケーション」ウィザードを使用する

最初のステップは、Delphi XE の新しい「DataSnap REST アプリケーション」ウィザードを使用することです。

IDE メニューの [ファイル|新規作成|その他] を選択し、「新規作成」ダイアログの “Delphi プロジェクト” の “DataSnap Server” カテゴリ内の “DataSnap REST アプリケーション” のアイコンをダブルクリックします。

Hide image
DataSnap70

このウィザードは、Web アプリケーションサーバーと、JavaScript クライアントの完全なシステムを作成します。

ウィザードの最初の画面で、Web アプリケーションをパッケージ化する方法を決定できます。IIS Web サーバーに配布するための DLL として、または、

単一の実行形式で Web サーバーと Web アプリケーションの両方になるスタンドアロンの VCL アプリケーションとして作成するためのオプションがあります。これは、Delphi XE で新しく搭載され、そして、開発、テスト、配布のためのとても使いやすいオプションです。

Hide image
Click to see full-sized image

expand view>>

ウィザードの最初の画面では、“WebBroker プロジェクトの種類” をデフォルトの “スタンドアロン VCL アプリケーション” のままにします。

Hide image
Click to see full-sized image

expand view>>

ウィザードの2番目の画面では、デフォルトの HTTP ポートの 8080のままにし、それが有効であるかを確認するために [ポートのテスト] をクリックします。

Hide image
Click to see full-sized image

expand view>>

“サーバーの機能” を選択するウィザードの3番目の画面は、デフォルトの選択のままとします。

Hide image
Click to see full-sized image

expand view>>

サーバーメソッドの上位クラスは、デフォルトのままとします。

Hide image
Click to see full-sized image

expand view>>

ウィザードの最後の画面は、ほんの少しだけトリッキーです。プロジェクトの場所を選択しなければなりません。パスの最後の部分は、プロジェクト名になりますのでスペースを含むことはできません。それは有効なプロジェクト名である必要があります。

Hide image
DataSnap76

このウィザードは、それぞれのサブフォルダに多くのファイルを生成します。カスケード・スタイル・シート (CSS)、イメージ、JavaScript コード、html テンプレート、アプリケーションロジックを含む Delphi のユニットを使った完全な Web アプリケーションです。

すべてを保存します。最初のユニットを “FormMainUnit”、2つ目のユニットを “WebModuleUnit” と命名します。アプリケーション全体を “DelphiLabsDataSnapRESTWebApp” と呼ぶこととします。

プロジェクトのそれぞれの部品を通して、細かく見ていきましょう。

以下は、アプリケーションのメインフォームです。これは、一種の Web サーバーのための管理者コンソールです。[開始] と [停止] のボタンは、それぞれWeb サーバーの起動と停止を行います。[ブラウザを開く] を押すと、アプリケーションの実際のクライアント、つまり JavaScript コードの Webページをホストするデフォルトの Web ブラウザが起動します。

Hide image
DataSnap77

以下が、ウィザードによって生成されたアプリケーションのメインフォームのソースコードです。

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.

“FormCreate” イベントの中でインスタンス化される、フォームクラスの private 宣言内の “FServer: TIdHTTPWebBrokerBridge” を確認してください。これは、スタンドアロン Web サーバーの機能を実装している新しい Indy のクラスです。

“WebModuleUnit” を開きます。

Hide image
DataSnap78

すべての WebBroker Web アプリケーションは、クライアントから到達するすべての HTTP リクエストが処理される、独自の “Web モジュール” を持っています。

Web モジュールは、単にデータモジュールの特別な形式です。

この Web モジュールの面のどこかをクリックすると、オブジェクトインスペクタで、そのプロパティを確認できます。おそらく、最も重要なプロパティは、HTTP リクエストがアプリケーションに到達した際に、実行可能なアクションのコレクションである “Actions” です。

Hide image
DataSnap79

アクションコレクションエディタを表示するために、“Actions” プロパティの省略ボタン […] をクリックします。

Hide image
DataSnap80

HTTP リクエストの中のパス情報 (PathInfo) に対応して、それぞれのアクションが特定のリクエストに対してサービスを提供するために呼び出され、また、特殊な “producer” コンポーネントを、このタスクのために使用することができます。

Web アプリケーションには、2つの html テンプレート (“ReverseString.html” と “ServerFunctionInvoker.html”) が含まれており、それらは、個別のアクションに接続されている Web モジュール上の 2つの “TPageProducer” コンポーネントをもっています。

Hide image
Click to see full-sized image

expand view>>

プロジェクトマネージャ内の “ReverseString.html” ファイルをダブルクリックすると、Delphi HTML デザイナが表示され、“DataSnap REST Project” ページが開かれます。

Hide image
Click to see full-sized image

expand view>>

エディタに切り替えると、ウィザードによって生成された以下のコードを見つけるでしょう。

<!-- !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>

さて、Delphi XE DataSnap REST Server が、実行時にどのような動作をするか確認しましょう。プロジェクトを実行するために IDE の緑色の三角のアイコンをクリックします。アプリケーションのメインフォーム (実際には Web サーバーコンソールです) が表示されます。[開始] と [ブラウザを開く] をクリックします。

次のような画面が表示されます:

Hide image
ReverseString in Chrome

私の場合、Chrome ブラウザ内で Web ページが実行しています。[ReverseString] ボタンをクリックすると、入力した文字が、さかさまになるのを確認できます!

それは、Delphi のナレッジではなく、ブラウザ内で実行しているピュア JavaScript クライアントです!

[ReverseString] ボタンをクリックしても、ページが最初にロードされた時間は変わりません。これは、Web ページの応答性とエンドユーザーのエクスペリエンスのために重要なことです。ウィザードによって追加された JavaScript ファイルは、AJAX スタイルのサーバーメソッドの呼び出しを実装します。それについて何も心配することはありません。それはとても良いことです!

ページの上のほうにある “Server Functions” のリンクをクリックします。

利用可能なサーバーメソッドを呼び出すことができる特別な “Server Function Invoker” テストページが表示されます。

“TServerMethods1” クラスとその “ReverseString” メソッドを展開します。

“Value” フィールドに文字を入力し、[EXECUTE] ボタンをクリックします。画面の下部に、元の値と、結果の値である JSON 文字列が表示されます。

Hide image
Click to see full-sized image

expand view>>

クールだ!! ブラウザを閉じます。サーバーフォームの [停止] ボタンをクリックして、ウィンドウを閉じます。

    動的なクライアントプロキシの生成

ServerMethodsUnit を開き、テスト関数の “EchoString” と “ReverseString” を削除します。別のサーバーメソッドを実装します。

2つの double 値から 1つの double 値を返却する単純な “Add” メソッドを実装します。

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.

アプリケーションを再実行し、“Server Function Invoker” を開くと、テストページが更新され、 “Add” メソッドが表示されることを確認できます!

Hide image
Click to see full-sized image

expand view>>

これが、どうして可能だったか?それは、ちょっとした魔法のように見えます。

答えは、Web モジュールの “WebFileDispatcher1” コンポーネントの “OnBeforeDispatch” イベントにあります。

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;

サーバーメソッドのプロキシコードは、動的に生成されます。“serverfunctions.js” ファイルが古い場合、ページがディスパッチされる前に動的に再生成されます。

JavaScript のような動的言語にとっては非常に使いやすく、すばらしいところです。心配する必要はありません。プロキシコードは、常に最新です。

これが、DataSnap XE アーキテクチャの最も強力で、エキサイティングな新しい機能の一つ、つまり、プラグ可能なプロキシジェネレータ!

フォーム上の “DSProxyGenerator1” コンポーネントを選択し、その “Writer” プロパティを展開すると、異なるプログラミング言語で実装されたクライアント用のプロキシコードを生成するには、それぞれの writer を選択するだけであることを確認できます!

Hide image
DataSnap83

RAD Studio XE の Delphi および C++Builder で DataSnap サーバーを、Delphi, C++, JavaScript, Delphi Prism, PHP でクライアントを構築することが可能です。

未来の Delphi 多層テクノロジです!

    まとめ

このラボでは、スタンドアロン VCL フォームの Web サーバーアプリケーションで動作している Web アプリケーションと、html マークアップに埋め込まれたピュア JavaScript クライアントで構成された完全なプロジェクトを生成するために “Delphi REST アプリケーション” ウィザードを使いました。

アプリケーションのそれぞれの部品を確認し、 “オンザフライ” クライアントプロキシの生成の機能を確認しました。それにより、クライアントの JavaScript コードは、常に DataSnap サーバーメソッドと同期を取っています。

この記事のソースコードは、Code Central (http://cc.embarcadero.com) で、デモのビデオ版は、YouTube (http://www.youtube.com/ )で公開されています。

"Delphi Labs" の詳細情報につきましては、http://blogs.embarcadero.com/pawelglowacki およひ http://www.embarcadero.com/rad-in-action/delphi-labs で公開しています。.

Server Response from: ETNASC03