DataSnap REST メッセージング プロトコル
DataSnap REST への移動
このドキュメントでは DataSnap の REST メッセージング プロトコルについて説明します。このプロトコルにより、サーバー メソッドのリモート呼び出しのほか、重量コールバックの登録も可能になります。 このドキュメントに記載されている詳細情報を参考にすると、HTTP 要求を使って DataSnap サーバーと通信できるクライアントを任意の言語で実装できるようになります。
目次
リモート メソッド呼び出し
HTTP 接続を処理するアクティブ コンポーネントが DataSnap サーバーに存在する場合は、メッセージング プロトコルが公開されます。HTTP 要求をサーバーに発行して、サーバーに登録されているクラスの public メソッドを TDSServerClass コンポーネントで呼び出すことができます。 サーバー メソッドを呼び出すには、以下の URL と空のメッセージ本文から成る GET 要求を使用するのが最も基本的なやり方でしょう。
http://host:port/datasnap/rest/[ClassName]/[MethodName]/[ParamValue]
<ポート>
は省略可能で、[パラメータ値]
も同様です。 'datasnap' と 'rest' もほぼ省略可能ですが、HTTP 接続のサーバー上の DSContext プロパティと RESTContext プロパティの値によっては、代わりに何か他のものを指定しなければならない場合もあります。
URL 内のパラメータ
[パラメータ値]
はスラッシュ(/)で区切られたパラメータ値リストを表し、呼び出されるサーバー メソッドに必要な入力パラメータに対応します。 サーバー メソッド(関数/手続き)に入力パラメータがない場合は、URL 内のメソッド名の後には何も指定しません。 URL 内のパラメータ値を表す部分に 2 つのスラッシュ(//)がある場合は、その位置のパラメータ値が空文字列に設定されます。
こうして渡された値は URL セーフな形式にエンコードされる必要があり、それには Unicode 文字のエンコードも含まれます。 JSON オブジェクト表現または JSON 配列表現の値は、このようには渡せません。 これらは HTTP 要求の本文に渡される必要があり、その要求タイプは POST か PUT でなければなりません。
要求本文内のパラメータ
GET 要求や DELETE 要求でサポートされていない入力パラメータ(つまり、文字列、数値、論理値、null のいずれかで表せないもの)を持つサーバー メソッドを呼び出す場合や、要求の内容を通じてパラメータを 1 つ以上渡すだけの場合は、POST か PUT の要求タイプを使用する必要があります。
たとえば、以下のようなサーバー メソッドを呼び出すとしましょう。
function TServerMethods1.updateEchoAttribute(Key: String; Obj: TJSONObject): String;
この場合、要求は次のようになります。
POST /datasnap/rest/TServerMethods1/EchoAttribute/Attr1 HTTP/1.1 ....*additional headers*... Accept: application/json Content-Type: text/plain;charset=UTF-8 {"Attr1":"ValueToReturn"}
上記の要求には、さらに説明の必要な項目が少し含まれています。 まず第一に、渡されるメソッド名が "updateEchoAttribute" ではなく "EchoAttribute" であることに注意してください。 これは、POST で呼び出されるあらゆるメソッドの名前にはデフォルトで先頭に 'update' が付加されるからです。 同様に、DELETE 要求の場合には 'cancel' が、PUT 要求の場合には 'accept' が、それぞれメソッド名の先頭に付加されます。 このようなプレフィックス(接頭辞)付加は、メソッド名を引用符で囲むことで避けることができ、その場合、上記要求の 1 行目は次のようになるでしょう。
POST /datasnap/rest/TServerMethods1/%22updateEchoAttribute%22/Attr1 HTTP/1.1
この場合もサーバー メソッド updateEchoAttribute が呼び出されますが、引用符が付いているので、指定されたサーバー メソッド名の先頭に 'update' がさらに付加されることはありません。 また、このようにメソッド名を引用符で囲むことで、POST、PUT、DELETE のいずれかでサーバー メソッドを呼び出すことも可能になります。これらは対応する要求タイプに一致する名前が接頭辞として付加されないものです。 たとえば、サーバー メソッドが上記の代わりに次のようなものであったとしましょう。
function TServerMethods1.EchoAttribute(Key: String; Obj: TJSONObject): String;
この場合は、TJSONObject の値が要求 URL でサポートされていないため、メソッド名を引用符で囲む必要があります。つまり、GET 要求を使用できないということです。この要求タイプだけは、メソッド名にデフォルトで接頭辞が付加されることを前提としていません。 ポストは次のようになります。
POST /datasnap/rest/TServerMethods1/%22EchoAttribute%22/Attr1 HTTP/1.1
これらの例はすべて同じ結果になります。Key
パラメータの値として "Attr1" を渡してサーバー メソッドが呼び出され、Obj の値は、"Attr1" というプロパティとそれに対応する値を持つ JSON オブジェクトになります。 応答の内容には、サーバー メソッドの呼び出し結果(この例では "ValueToReturn")が文字列として格納されます。
要求の内容に複数のパラメータ値を渡す場合は、それらを正しい順序で JSON 配列に格納し、その配列が JSON オブジェクト ペアの値になるようにしたうえで、"_parameters" というキーを付ける必要があります。 結果として生成される JSON オブジェクトは、次のように文字列として表され要求の内容として設定されます。
{"_parameters":["Param1", "Param2"]}
この形式を使用する場合は、入力パラメータがまず要求 URL から取り出された後、残りの入力値が _parameters 配列から順に取り出されます。
要求では任意のクエリ パラメータを使用できます。 ユーザーは、(DBXPlatform を uses 句に追加したうえで GetInvocationMetadata() を呼び出して)呼び出しメタデータを取得し、そこに格納されているパラメータのリストにアクセスすることで、URL に渡されたクエリ パラメータのいずれにもサーバー メソッドからアクセスすることができます。 サーバーの HTTP サービスには今では FormatResult イベントがあるため、特定のメソッド呼び出しに対応して任意の形式で JSON 応答を返すことができます。
呼び出し応答
呼び出し要求から返される応答には、JSON オブジェクトの文字列表現となる以下のような応答テキストが含まれています。
{"result":["ValueToReturn"]}
これが JSON オブジェクト インスタンスに正しく解析されると、'result' プロパティの JSON 配列を取得できるようになります。 この配列には、あらゆるパラメータの値が、サーバー メソッドのシグネチャで出現する順に格納され、それらが返されます(out、var、result の各タイプ)。 結果は常に配列の最後の要素になります。上記の例では、EchoAttribute サーバー メソッドの実装から返される値を持つ JSON 文字列になります。
呼び出し中にサーバー上でエラー(セッションの有効期限切れ、権限のないユーザー、無効な入力値など)が発生した場合は、返される JSON オブジェクトには、'result' プロパティではなく 'error' プロパティまたは 'SessionExpired' プロパティが格納されます。たとえば、以下のようなものです。
{"SessionExpired":"The specified session has expired due to inactivity or was an invalid Session ID"}
ストリーム応答タイプを指定する
メソッド呼び出しの結果が TStream の場合、そのストリームは JSON 配列か応答のコンテンツ ストリームとして返すことができます。 そのどちらを返すかを指定するには、以下のように、URL パラメータ 'json' を使用できます。
http://host:port/datasnap/rest/[Class]/[Method]/?json=true
その値は 'true' か 'false' のどちらかに設定できます。'true' の場合は、結果は常に JSON 配列として返され、'false' の場合は、他の var/out/result パラメータが一緒に返されない限り、結果はコンテンツ ストリームとして返されます。
認証
サーバーで認証が必要な場合は、適切な形式の認証情報(ユーザー名とパスワード)を要求の 'Authorization' ヘッダーに渡す必要があります。 ただし、これが必要なのは、要求にセッション ID を指定しない場合だけです。 Authorization ヘッダーの値の形式は以下のとおりです。
Basic base64(user:password)
あるいは、たとえばユーザー名が 'admin' でパスワードが 'admin' の場合は以下のようになります。
Basic YWRtaW46YWRtaW4=
base64 でエンコードする文字列は、ユーザー名とパスワードをコロンで区切ったものを表す単一の文字列でなければならないことに注意してください。
要求フィルタ
DataSnap には、要求フィルタを使って結果をフィルタリングするという概念があります。 これらの定義済みフィルタが API に関連付けられているため、パラメータごとにどのフィルタを使用するか、また特定のフィルタにどのような値を渡すかを、その API で URL 内に指定することができます。 たとえば、サーバー関数からは非常に長い文字列が返されるが、必要なのはそのうちの先頭の数文字だけだとしましょう。 この場合には、SubString フィルタを使用して、文字列の中の必要な範囲を指定します。 次に示すのは、1 つの入力パラメータを受け取り、結果として文字列を返す、サーバー関数の URL の例です。
http://host:port/datasnap/rest/[Class]/[Method]/[ParamValue]?ss.r=1,3
'?' に続く部分では、どのフィルタをどのパラメータに使用するかを定義します。 この例では、SubString フィルタの範囲機能は返される結果に対して使用され(パラメータ インデックスが指定されない場合、フィルタは結果に対して適用されます)、結果の第 2 文字以降の 3 文字分を返すことを指定しています。
詳細については、「要求フィルタ」を参照してください。
セッション情報
サーバー メソッドの呼び出し時に要求にセッション ID を渡さなかった場合は、サーバー上で新しいセッションが作成され、そのセッション ID が 'Pragma' ヘッダーに自動的に設定されます。 このヘッダーに設定される値には、'dssession' と 'dssessionexpires' という 2 つのキーが含まれています。 前者はサーバー上のセッションの ID で、後者はそのセッションの有効期限が切れるまでの残り時間(ミリ秒単位)の近似値です。 'dssessionexpires' が指定されないか負の値の場合、セッションは有効期限切れを起こすことはなく、クライアント側で不要になったときに閉じなければなりません。 セッション ID がいったん得られたら、追加要求のたびに、その値を以下の形式で 'Pragma' ヘッダーに渡す必要があります。
dssession=[session ID]
セッションの有効期限が切れるまでの残り時間(ミリ秒)で、そのセッション ID がもう有効でないかどうかを判断でき、無効と判断される場合は SessionExpired エラーが返されるため、以後の要求でそれを渡さないようにすることができます。 サーバーから SessionExpired の結果が返された場合は、そのたびに、格納してあるセッション ID をクリアして、サーバーへの次の要求ではサーバーが新しいセッション ID を自動的に設定できるようにしなければなりません。
有効期限が切れる前にセッションを閉じるには、以下の URL で GET 要求を実行すると共に、必ず、上記のようにセッション ID を要求の Pragma ヘッダーに渡します。
http://host:port/datasnap/rest/CloseSession/
これで、指定された ID のセッションが終了し、正常に終了した場合は以下の応答テキストが返されます(正常に終了しなかった場合は SessionExpired メッセージが返されます)。
{"result":[true]}
セッション パラメータ キャッシング
サーバーに存在するセッションの用途の 1 つは、前の呼び出しで渡されたパラメータ値を任意に格納して、メソッド呼び出しの結果の一部を、メソッドそのものを何度も呼び出さなくても繰り返し取得できるようにすることです。 キャッシュに保存できるパラメータは、ストリームやユーザー定義型など、JSON オブジェクトや JSON 配列での表現が必要なものです。
キャッシュの使用を指定するには、要求の 'Accept' ヘッダー値を "application/rest" に設定する必要があります。 たとえば TJSONObject を結果とするサーバー メソッドを呼び出すと、新しいキャッシュ項目が作成され、結果として生成される JSON オブジェクトにアクセスできるようになります。 その呼び出しで返される値は次のようになります。
{"result":["0/0/0"],"cacheId":0,"cmdIndex":0}
"0/0/0" は、実際の呼び出し結果が通常どこにあるかを表します。 これは、キャッシュに含まれている特定の項目の識別子と相対パスです。 この相対パスの形式は以下のように解釈できます。
[Cache Item ID]/[Command Index]/[Parameter Index]
[キャッシュ項目 ID] は、実行したメソッド呼び出しの一意な識別子です。 キャッシュに結果を保存した実行のみ、一意な識別子が与えられます。 [コマンド インデックス] は、結果が実行のどのコマンドに対応するかを識別するものです。 これは、バッチ実行(一度に複数のサーバー メソッドを実行する場合)を行った場合にのみ、ゼロ以外の値になります。 [パラメータ インデックス]
は、返す複合型パラメータのインデックスです。 これは 0 から始まる値で、コマンドの全パラメータではなく複合型パラメータにのみ基づいたものです。
'cacheId' や 'cmdIndex' という追加値は、"0/0/X" の先頭の 2 つの数値として出現する値と同じものです。 キャッシュに保存されているメソッド呼び出し結果が複数ある場合、'result' 配列には複数の項目が格納されますが、それらはすべて、同じキャッシュ済み項目の一部であり、同じメソッド呼び出しの結果であるため、cacheId 値と cmdIndex 値を共有しています。
これらの相対パスを使用して、キャッシュ URL を作成します。 このキャッシュ URL では 'rest'(RESTContext)ではなく 'cache'(CacheContext)が使用され、呼び出し結果で返された相対 URL が末尾に付加されます。
キャッシュ URL では、GET と DELETE の各要求タイプをサポートしています。 GET 要求を発行する際には、以下の URL を使用できます。
http://host:port/datasnap/cache |
キャッシュの内容を記述した JSON オブジェクトを返します。 |
http://<ホスト>:<ポート>/datasnap/cache/(キャッシュ ID) |
指定された ID のキャッシュ オブジェクトを記述した JSON オブジェクトを返します。 |
http://<ホスト>:<ポート>/datasnap/cache/(キャッシュ ID)/(コマンド インデックス) |
指定されたコマンドの JSON 表現を返します。 |
http://<ホスト>:<ポート>/datasnap/cache/(キャッシュ ID)/(コマンド インデックス)/(パラメータ インデックス) |
実パラメータを JSON またはストリームとして返します。 |
DELETE 要求を発行する際には、以下の URL がサポートされています。
http://host:port/datasnap/cache | キャッシュされているすべての項目を削除します。 |
http://<ホスト>:<ポート>/datasnap/cache/(キャッシュ ID) | キャッシュされている項目とそのすべてのコマンドやパラメータを削除します。 |
キャッシュをクリアしない場合は、セッションの有効期限が切れたときに、キャッシュがクリアされ破棄されます。 詳細については、「DBX パラメータ キャッシング」を参照してください。
重量コールバック
REST 重量コールバックを使用すると、サーバーに登録されたコールバックとして機能する、実行時間の長い HTTP 要求をクライアント側で発行できます。 要求には情報が関連付けられているため、サーバーには応答方法がわかります。 応答が取得されると、サーバーに新しい要求が発行され、'コールバック' がリスン状態に戻ります。
重量コールバック(クライアント チャネル)を登録する
元の要求は重量コールバックのハンドシェイクを開始するものですが、以下のような形式でなければならず、GET 要求として発行される必要があります。
http://host:port/datasnap/rest/DSAdmin/ConsumeClientChannel/[SCN]/[CHID]/[CBID]/[SCNS]/[ST]//
このハンドシェイクでは、実は、委譲先のコールバックが自分自身に 1 つ以上含まれているコールバックを登録しています。 SCN は、このコールバックのリスン先となるサーバー チャネルの名前です。 この値は、潜在的にサーバーのブロードキャスト先または通知先となり得るものであれば何でもかまいません。 SCNS は、登録されたコールバックのサーバー チャネル名が、カンマ区切りでリストになっているものです。これは空のリスト(コールバックがチャネルの ServerChannelName
をそのまま使用)の場合も、チャネルの ServerChannelName
に追加されるリストである場合もあります。この詳細については、REST 重量コールバックに関する docwiki ドキュメントを参照してください。 たとえば、サーバーが以下のようにブロードキャストするとしましょう。
Val := TJSONString.Create(Memo1.Text); [DSServer].BroadcastMessage('MemoChannel', Val);
この場合は、渡される SCN の値が 'MemoChannel' となり、その特定のブロードキャストをリスンできるようになることを想定しています。
CHID は、クライアントのコールバック チャネルに使用する一意な ID です。 これは何でもかまいませんが、サーバー上に既に存在する値と一致する場合は、呼び出しが失敗します。 CBID は、クライアントのコールバック チャネルの最初のコールバックの一意な ID です。 コールバック チャネルには常にコールバックが少なくとも 1 つ必要なので、このパラメータは必須です。 追加の子コールバックは後から登録できます。 ST は、ここで定義するセキュリティ トークンです。 コールバックの追加や削除など、クライアント チャネルを変更するために以後発行するすべての呼び出しでは、このセキュリティ トークンを指定して、変更しようとしているチャネルの所有者であることを証明する必要があります。 最後のパラメータ(//)は、チャネルの登録時に空の文字列として渡されます。
登録が正常に行われた場合は、以下のような応答がすぐに返されます。
{"result":[{"invoke":["cb12345",{"created":true},1]}]}
ここで、'cb12345' は、クライアント チャネルの作成時に登録されたコールバックの一意な ID(CBID)です。 必ず、別の要求をすぐに発行して(その方法については以下の手順を参照してください)、コールバックをアクティブな状態に保つようにしてください。
重量コールバックを停止する
前に作成された重量コールバックを停止する場合は、以下の URL への GET 要求を発行します。
http://host:port/datasnap/rest/DSAdmin/CloseClientChannel/[SCN]/[CHID]/[ST]
ここで、SCN、CHID、ST は初回の作成時に渡されたものと同じ値です。
クライアント チャネルにコールバックを追加する
クライアント チャネルには、ネストした複数のコールバックを含めることができますが、コールバックが少なくとも 1 つ含まれている必要があります。 クライアント チャネルに別のコールバックを登録するには、以下の URL で GET 要求を発行します。
http://host:port/datasnap/rest/DSAdmin/RegisterClientCallbackServer/[SCN]/[CHID]/[CBID]/[SCNS]/[ST]/
ここで、SCN、CHID、SCNS、ST はクライアント チャネルの作成時に使用したものと同じ値であり、CBID は新しいコールバックの ID でクライアント チャネル内で一意なものです。
クライアント チャネルからコールバックを削除する
クライアント チャネルからコールバックを削除できます。 最後のコールバックを削除したら、チャネルが閉じられます。 コールバックを削除するには、以下の URL で GET 要求を発行します。
http://host:port/datasnap/rest/DSAdmin/UnregisterClientCallback/[SCN]/[CHID]/[CBID]/[SCNS]/[ST]/
ここで、SCN、CHID、SBID、SCNS、ST は、コールバックの登録時に(ConsumeClientChannel メソッドか RegisterClientCallbackServer メソッドのどちらかを通じて)渡されたものと同じ値です。
サーバーからの応答を取得する
重量コールバック要求に応答が返される状況はいくつかあります。 1 つは、先に述べたように、クライアント チャネルが正常に作成された場合です。 もう 1 つは、重量コールバックが閉じられた場合です。 しかし、最もよくある応答はブロードキャストか通知です。 サーバーからの通知はクライアント チャネルの単一のコールバックに向けられたものであるのに対して、ブロードキャストはすべてのコールバックに送られます。
'cd12345' という ID のコールバックがある場合、発行された要求の応答テキストは、たとえば以下のようなものになる可能性があります。
{"result":[{"invoke":["cb12345","Hello World" ,1]}]}
ここに記述されている内容は、呼び出し(通知)がサーバーから送信され、"cd12345" という ID のコールバックを呼び出して "Hello World" という文字列値(タイプ 1)を渡すように指示されたということです。 タイプ 1 とは値を JSON として扱うということであるのに対して、タイプ 2 とは値がユーザー オブジェクトの JSON オブジェクト表現であるということです。 おそらくは、タイプ 1 の方が一般的でしょう。 値は文字列である必要はなく、任意の JSON 値でかまいません。
サーバーからブロードキャストが送信された場合、それはクライアント チャネルのすべてのコールバックに向けられるため、ID は指定されません。 応答テキストはたとえば次のようになります:
{"result":[{"broadcast":["Hello World",1]}]}
重量コールバックが終了すると、以下の応答テキストが返されます。
{"result":[{"close":true}]}
サーバーからの応答に応答する
重量コールバック要求がサーバーからの応答を受け取ったら、そのメッセージをなるべくすぐに処理した後、新しい要求を発行する必要があります。 発行する要求は、クライアント チャネルの登録時に発行された要求とほぼ同一で、些細な違いが少しあるだけです。 まず、コマンドは POST として発行する必要があります。 要求 URL は次のようになります。
http://host:port/datasnap/rest/DSAdmin/ConsumeClientChannel/[SCN]/[CHID]//[ST]
ここでは、新しいコールバックが登録されるわけではないため、CBID は空文字列のままです。また、これは POST なので、最後のパラメータ(クライアント チャネルの登録時には 2 つのスラッシュを使って空の文字列として指定されたもの)は、今回は要求の本文で渡されるため、省略されています。
要求の本文には、クライアント チャネルから返された結果(任意の JSON 値)が文字列形式で含まれている必要があります。 一般に、'true' か 'false' のどちらかを返すのがよいでしょう。 通知が行われた場合、結果は、特定のコールバックが呼び出されたときの戻り値でなければなりません。 一方、ブロードキャストが行われた場合は、戻り値は、コールバックから返されるすべての値に基づいたものでなければなりません(たとえば、すべてのコールバックが 'true' を返した場合以外は 'false' とするなど)。
詳細は、「REST 重量コールバック」を参照してください。
PHP による DataSnap REST メッセージングの例
以下は、配列を受け渡す DataSnap REST をネイティブ PHP コードで利用する例です。
<?php // type -> unit name (uCidade) . object name (TCidade)
// fileds -> fields must be the same in declared class
$cidade = array( "type" => "uCidade.TCidade" , "id" => 1 , "fields" => array ( "FId" => 41000 , "FDescricao" => "LINS" , "FUF" => "SP" ) );
$url = 'http://localhost:8080/datasnap/rest/TServerMethodsMain/%22AddCidade%22/' ;
$ch = curl_init() ;
curl_setopt( $ch , CURLOPT_HTTPHEADER, array ( "Accept: application/json" , "Content-Type: text/xml; charset=utf-8" ) ) ;
curl_setopt( $ch , CURLOPT_HEADER , FALSE ) ;
curl_setopt( $ch , CURLOPT_RETURNTRANSFER , true ) ;
curl_setopt( $ch , CURLOPT_POST , TRUE ) ;
curl_setopt( $ch , CURLOPT_URL , $url ) ;
curl_setopt( $ch , CURLOPT_POSTFIELDS , json_encode( $cidade ) ) ;
$result = curl_exec( $ch ) ;
echo '<pre>';
print_r ($result);
echo '</pre>';
?>
メモ: RAD PHP には、独自のプロキシ ジェネレータがありますが、上記の例のように TJSONObject を生成しません。