Protocole de messagerie DataSnap REST

De RAD Studio
Aller à : navigation, rechercher

Remonter à DataSnap REST


Ce document décrit le protocole de messagerie REST avec DataSnap, qui permet une invocation à distance des méthodes serveur, ainsi que le recensement des rappels (callbacks) avancés. Grâce aux détails de ce document, vous serez capable d'implémenter un client dans n'importe quel langage qui peut communiquer avec un serveur DataSnap en utilisant des requêtes HTTP.

Invocation des méthodes à distance

Quand un serveur DataSnap a un composant actif gérant les connexions HTTP, un protocole de messagerie est exposé partout où des requêtes HTTP peuvent être émises sur le serveur pour invoquer les méthodes publiques d'une classe recensée pour le serveur avec un composant TDSServerClass. Pour invoquer une méthode serveur, le moyen le plus simple serait avec une requête GET, avec l'URL suivante, et aucun contenu dans le corps du message :

http://host:port/datasnap/rest/[ClassName]/[MethodName]/[ParamValue]

Port est facultatif, comme ParamValue. 'datasnap' et 'rest' sont aussi facultatifs, et peuvent être remplacés par autre chose selon les valeurs des propriétés DSContext et RESTContext sur le serveur pour la connexion HTTP.

Paramètres de l'URL

ParamValue représente une liste de valeurs de paramètres séparées par une barre oblique (/), assorties aux paramètres d'entrée requis par la méthode serveur invoquée. Si la méthode serveur (fonction / procédure) n'a pas de paramètres d'entrée, rien ne doit alors suivre le nom de la méthode dans l'URL. Deux barres obliques (//), dans la partie de l'URL représentant les valeurs des paramètres, définissent la valeur du paramètre à cet index par une chaîne vide.

Les valeurs passées de cette façon doivent être encodées afin de sécuriser l'URL, ce qui inclut aussi l'encodage des caractères Unicode. Les valeurs avec une représentation de tableau JSON ou d'objet JSON ne peuvent pas être passées de cette façon : elles doivent être passées dans le corps de la requête HTTP, et le type de la requête doit être POST ou PUT.

Paramètres dans le corps de la requête

Si vous voulez invoquer une méthode serveur ayant un paramètre d'entrée non supporté par une requête GET ou DELETE (par exemple, un paramètre qui ne peut pas être représenté en tant que chaîne, nombre, booléen ou null) ou si vous voulez simplement passer un ou plusieurs paramètres par l'intermédiaire du contenu de la requête, vous devez alors utiliser un type de requête POST ou PUT.

Par exemple, si vous voulez invoquer une méthode serveur comme suit :


 function TServerMethods1.updateEchoAttribute(Key: String; Obj: TJSONObject): String;

Votre requête doit alors ressembler à ceci :


 POST /datasnap/rest/TServerMethods1/EchoAttribute/Attr1 HTTP/1.1
 ....*additional headers*...
 Accept: application/json
 Content-Type: text/plain;charset=UTF-8
 
 {"Attr1":"ValueToReturn"}

La requête ci-dessus contient quelques éléments qui nécessitent une explication supplémentaire. Notez d'abord que le nom de la méthode passé est "EchoAttribute" et non "updateEchoAttribute". La raison est que, par défaut, le préfixe 'update' est assigné à toute méthode invoquée avec POST. De même, le préfixe 'cancel' est utilisé pour les requêtes DELETE, et le préfixe 'accept' pour les requêtes PUT. Ce préfixage peut être évité en plaçant des marques de délimitation autour du nom de la méthode, ce qui transforme la première ligne de la requête en :

POST /datasnap/rest/TServerMethods1/%22updateEchoAttribute%22/Attr1 HTTP/1.1

Ceci invoquera aussi la méthode serveur updateEchoAttribute, mais les marques de délimitation empêchent le préfixage d'un 'update' supplémentaire avec le nom de serveur donné. Cette délimitation du nom de la méthode permet aussi l'invocation de méthodes serveur avec POST, PUT ou DELETE, qui ne sont pas préfixées avec les noms correspondant au type de requête correspondant. Par exemple, si la méthode serveur était à la place :

function TServerMethods1.EchoAttribute(Key: String; Obj: TJSONObject): String;

La délimitation du nom de la méthode serait alors requis, puisque la valeur d'un TJSONObject n'est pas supportée dans l'URL de la requête, signifiant ainsi qu'une requête GET ne peut pas être utilisée, ce qui est le seul type de requête qui ne prend pas un préfixe de méthode par défaut. Le POST devrait ressembler à ceci :

POST /datasnap/rest/TServerMethods1/%22EchoAttribute%22/Attr1 HTTP/1.1

Tous ces exemples ont le même résultat, à l'endroit où la méthode serveur est invoquée avec "Attr1" en tant que valeur du paramètre Key, et la valeur de Obj sera un objet JSON ayant une propriété appelée "Attr1" et une valeur correspondante. Le contenu de la réponse contiendra le résultat de l'invocation de la méthode serveur, qui dans cet exemple est sous-entendu comme étant "ValueToReturn", sous la forme d'une chaîne.

Si vous voulez passer plusieurs valeurs de paramètres dans le contenu de la requête, vous devez alors les mettre dans un tableau JSON en respectant l'ordre correct, et faire en sorte que ce tableau soit la valeur d'une paire d'objets JSON, avec la clé "_parameters". L'objet JSON résultant doit être représenté sous la forme d'une chaîne et défini comme le contenu de la requête, comme suit :

{"_parameters":["Param1", "Param2"]}

Avec ce format, les paramètres d'entrée seront d'abord pris dans l'URL de la requête, puis les valeurs d'entrée restantes seront prises, dans l'ordre, dans le tableau _parameters.

Tous les paramètres de requête sont autorisés dans la requête. L'utilisateur aura accès depuis les méthodes serveur à tous les paramètres de requête passés à l'URL, en obtenant les métadonnées d'invocation (par le biais d'un appel à GetInvocationMetadata(), après l'ajout du DBXPlatform à la clause uses) et en accédant à la liste de paramètres stockée. Le service HTTP du serveur a maintenant un événement FormatResult, qui lui permet de retransmettre la réponse JSON dans le format de votre choix pour les invocations de méthodes spécifiques.

Réponse d'une invocation

La réponse renvoyée à partir d'une requête d'invocation aura un texte de réponse qui est une représentation chaîne d'un objet JSON, ressemblant à ceci :

{"result":["ValueToReturn"]}

Quand l'analyse dans une instance d'objet JSON est correcte, vous serez capable d'obtenir le tableau JSON pour la propriété 'result'. Ce tableau contient les valeurs des paramètres, dans l'ordre de leur placement dans la signature de la méthode serveur, qui sont renvoyées (types out, var, result). Le résultat est toujours le dernier élément du tableau, et est dans l'exemple ci-dessus une chaîne JSON avec la valeur telle qu'elle serait renvoyée par l'implémentation de la méthode serveur EchoAttribute.

Si une erreur a lieu sur le serveur pendant l'invocation (telle que l'expiration d'une session, un utilisateur non autorisé, ou des valeurs d'entrée non valides), ainsi au lieu d'une propriété 'result', l'objet JSON renvoyé contiendra une propriété 'error' ou 'SessionExpired', comme suit :

{"SessionExpired" : "La session spécifiée a expiré en raison d'une inactivité, ou l'ID de la session n'est pas valide"}

Spécification du type de réponse flux

Si le résultat de l'invocation d'une méthode est un TStream, le flux peut alors être renvoyé sous la forme d'un tableau JSON ou en tant que flux de contenu de la réponse. Pour spécifier ce qu'il faut renvoyer, vous pouvez utiliser le paramètre d'URL 'json', comme suit :

http://host:port/datasnap/rest/[Class]/[Method]/?json=true

Vous pouvez définir la valeur sur 'True' ou 'False'. 'True' renvoie toujours le résultat sous la forme d'un tableau JSON, et 'False' le renvoie sous la forme d'un flux de contenu, tant qu'aucun autre paramètre var / out / result n'est aussi renvoyé.

Authentification

Si le serveur nécessite une authentification, vous devez alors passer vos informations d'authentification (nom d'utilisateur et mot de passe) correctement formatées dans l'en-tête d'autorisation de votre requête. Toutefois, ceci est seulement nécessaire quand vous ne spécifiez pas un ID de session dans la requête. Voici le format de la valeur pour l'en-tête d'autorisation :

Basic base64(user:password)

Ou par exemple, en supposant que le nom d'utilisateur est 'admin' et que le mot de passe est 'admin' :

Basic YWRtaW46YWRtaW4=

Notez que la chaîne encodée en base64 doit être une chaîne unique représentant le nom et le mot de passe, avec un deux-points entre eux.

Filtres de requêtes

DataSnap inclut le concept de filtrage des résultats par le biais des filtres de requêtes. Ces filtres prédéfinis se connectent à une API, qui vous permet de spécifier dans l'URL le filtre à utiliser pour chaque paramètre et les valeurs à passer dans le filtre spécifique. Par exemple, une fonction serveur peut renvoyer une très grande chaîne, dont seuls les premiers caractères vous intéressent. Pour ce faire, vous pouvez utiliser le filtre SubString, en spécifiant l'étendue de la chaîne qui vous intéresse. Voici un exemple de l'URL pour une fonction serveur avec un seul paramètre d'entrée et un résultat de type chaîne :

http://host:port/datasnap/rest/[Class]/[Method]/[ParamValue]?ss.r=1,3

La partie après le '?' définit le filtre à utiliser et sur quels paramètres. Dans ce cas, la fonction range du filtre SubString est utilisée sur le résultat renvoyé (si aucun index de paramètre n'est spécifié, le filtre est appliqué sur le résultat), spécifiant que le résultat doit être une sous-chaîne de trois caractères, en commençant par le second caractère du résultat.

Pour de plus amples informations, voir Filtres de requêtes.

Informations de session

Lors de l'invocation d'une méthode serveur sans avoir passé un ID de session dans la requête, une nouvelle session est alors créée sur le serveur et l'ID de la session est fourni dans l'en-tête 'Pragma'. La valeur stockée pour cet en-tête contient deux clés, 'dssession' et 'dssessionexpires'. La première clé est l'ID de la session sur le serveur, tandis que l'autre clé est une proche approximation du nombre de millisecondes restantes avant l'expiration de la session. Si 'dssessionexpires' n'est pas fourni ou est une valeur négative, la session n'expirera pas et elle doit être fermée quand le client n'en a plus besoin. Dès que vous obtenez l'ID de la session, vous devez le passer dans l'en-tête 'Pragma' de chaque requête supplémentaire, avec la valeur spécifiée au format :

dssession=[session ID]

Avec le nombre de millisecondes restantes avant l'expiration de la session, vous pouvez déterminer si l'ID de la session n'est plus valide, et opter de ne pas le passer lors d'une requête ultérieure, puisqu'il serait probablement invalide et qu'une erreur SessionExpired serait renvoyée. Si vous recevez un résultat SessionExpired depuis le serveur, vous devez effacer l'ID de session que vous avez stocké, et autoriser le serveur à vous fournir un nouvel ID sur votre prochaine requête au serveur.

Pour fermer votre session avant son expiration, effectuez une requête GET avec l'URL suivante, en vous assurant de passer aussi l'ID de la session dans l'en-tête Pragma de la requête, comme mentionné ci-dessus :

http://host:port/datasnap/rest/CloseSession/

Ceci termine la session avec l'ID fourni, et renvoie le texte de réponse suivant en cas de succès (en cas d'échec, le message SessionExpired est renvoyé) :

{"result":[true]}

Mise en cache des paramètres d'une session

Une utilisation des sessions sur le serveur consiste à stocker facultativement les valeurs des paramètres à partir des invocations précédentes. Ainsi, certains résultats de l'invocation d'une méthode peuvent être récupérés plusieurs fois sans invoquer la méthode elle-même plus d'une fois. Les paramètres qu'il est possible de stocker dans le cache sont ceux qui nécessitent une représentation objet JSON ou tableau JSON, telle que les flux et les types définis par l'utilisateur.

Pour spécifier que vous voulez utiliser le cache, vous devez définir la valeur de l'en-tête 'Accept' sur "application/rest" dans la requête. Quand vous invoquez une méthode serveur qui a, par exemple, le résultat TJSONObject, un nouvel élément de cache est alors créé, vous permettant d'accéder à l'objet JSON résultant. La valeur renvoyée depuis l'invocation devrait ressembler à ceci :

{"result":["0/0/0"],"cacheId":0,"cmdIndex":0}

Le "0/0/0" se trouve à l'endroit où le résultat de l'invocation réelle devrait normalement être. C'est l'identificateur et le chemin relatif à l'élément spécifique du cache. Le format de ce chemin relatif peut être interprété comme suit :

[Cache Item ID]/[Command Index]/[Parameter Index]

'Cache Item ID' est l'identificateur unique pour l'invocation de méthode exécutée. Seules les exécutions qui stockent des résultats dans le cache obtiennent des identificateurs uniques. 'Command Index' identifie la commande de l'exécution à laquelle le résultat est destiné. C'est une valeur différente de zéro seulement si vous avez effectué une exécution groupée (plusieurs méthodes serveur à la fois). Parameter Index est l'index du paramètre complexe à renvoyer. C'est un index de base zéro, qui est basé sur les paramètres complexes de la commande, et pas sur tous les paramètres de la commande.

Les valeurs supplémentaires 'cacheId' et 'cmdIndex' sont les valeurs qui apparaîtront dans les deux premiers nombres de "0/0/X". Si plusieurs résultats en cache sont issus de l'invocation de la méthode, le tableau 'result' contient davantage d'éléments, mais ils partagent tous les valeurs cacheId et cmdIndex, étant donné qu'ils font partie du même élément en cache, et sont les résultats de la même invocation de méthode.

Utilisez ces chemins relatifs pour construire une URL de cache. L'URL de cache utilise 'cache' (CacheContext) au lieu de 'rest' (RESTContext), puis est suffixé par l'URL relative renvoyée dans le résultat de l'invocation.

Les URLs de cache supportent les types de requête GET et DELETE. Lors de l'émission d'un GET, les URLs suivantes sont disponibles :

http://host:port/datasnap/cache

Renvoie un objet JSON décrivant le contenu du cache

http://host:port/datasnap/cache/(CacheID)

Renvoie un objet JSON décrivant l'objet de cache avec l'ID donné

http://host:port/datasnap/cache/(CacheID)/(CmdIndex)

Renvoie une représentation JSON de la commande donnée

http://host:port/datasnap/cache/(CacheID)/(CmdIndex)/(ParameterIndex)

Le paramètre réel, en tant que JSON ou flux

Lors de l'émission d'un DELETE, les URLs suivantes sont supportées :

http://host:port/datasnap/cache Supprime tous les éléments en cache
http://host:port/datasnap/cache/(CacheID) Supprime l'élément en cache et tous ses paramètres/commandes.

Si vous n'effacez pas le cache, il sera effacé et détruit à l'expiration de la session. Pour de plus amples informations, voir Mise en cache des paramètres DBX.

Rappels avancés

Les rappels (callbacks) avancés REST permettent à un client d'émettre une requête HTTP à longue exécution qui agit comme un rappel recensé avec le serveur. Comme des informations sont associées à la requête, le serveur sait comment répondre. Quand une réponse est récupérée, une nouvelle requête est émise au serveur pour remettre le 'rappel' dans un état d'écoute.

Recensement du rappel avancé (canal client)

La requête originale, qui commence le transfert du rappel avancé, doit être formatée comme suit et émise en tant que requête GET :

http://host:port/datasnap/rest/DSAdmin/ConsumeClientChannel/[SCN]/[CHID]/[CBID]/[SCNS]/[ST]//

Ce transfert recense à vrai dire un rappel qui contient lui-même un ou plusieurs rappels sur lesquels il délègue. SCN est le nom du canal serveur sur lequel ce rappel doit être à l'écoute. Cette valeur peut être quelque chose que le serveur pourrait potentiellement diffuser ou notifier. SCNS est une liste de noms de canaux serveur pour le rappel recensé, délimités par des virgules. Ce peut être une liste vide (le rappel utilisera seulement le ServerChannelName du canal) ou une liste qui s'ajoutera au ServerChannelName du canal. Pour de plus amples informations, voir la documentation docwiki sur les rappels avancés REST. En exemple, si le serveur diffusait comme suit :


 Val := TJSONString.Create(Memo1.Text);
 [DSServer].BroadcastMessage('MemoChannel', Val);

Vous voudrez alors que la valeur de SCN passée soit 'MemoChannel', afin d'écouter cette diffusion spécifique.

CHID est un ID unique à utiliser pour le canal des rappels du client. Il peut être n'importe quoi, mais l'invocation échouera s'il n'est pas unique par rapport aux valeurs déjà existantes sur le serveur. CBID est un ID unique pour le premier rappel du canal des rappels du client. Puisqu'un canal de rappel nécessite la présence d'au moins un rappel à tous moments, ce paramètre est obligatoire. Des rappels enfant supplémentaires peuvent être recensés ultérieurement. ST est un jeton de sécurité que vous définissez ici. Dans tous les prochains appels que vous émettrez pour modifier le canal client, tels que l'ajout ou le retrait de rappels, vous devrez spécifier ce jeton de sécurité pour valider votre possession du canal que vous essayez de modifier. Le dernier paramètre (//) est passé sous la forme d'une chaîne vide lors du recensement du canal.

Si le recensement a été créé avec succès, une réponse est immédiatement renvoyée, comme suit :

{"result":[{"invoke":["cb12345",{"created":true},1]}]}

Où 'cb12345' est l'ID unique du rappel recensé avec la création du canal client (CBID). N'oubliez pas d'émettre immédiatement une autre requête (voir les étapes ci-dessous sur la façon de procéder) pour conserver le rappel actif.

Arrêt du rappel avancé

Si vous voulez arrêter un rappel avancé préalablement créé, émettez une requête GET pour l'URL suivante :

http://host:port/datasnap/rest/DSAdmin/CloseClientChannel/[SCN]/[CHID]/[ST]

Où SCN, CHID et ST sont les valeurs passées lors de la création initiale.

Ajout d'un rappel à un canal client

Un canal client peut contenir plusieurs rappels imbriqués, mais il doit en contenir au moins un. Pour recenser un autre rappel avec le canal client, émettez l'URL suivante avec une requête GET :

http://host:port/datasnap/rest/DSAdmin/RegisterClientCallbackServer/[SCN]/[CHID]/[CBID]/[SCNS]/[ST]/

Où SCN, CHID, SCNS et ST sont les valeurs utilisées lors de la création du canal client, et CBID est un ID unique au canal client pour le nouveau rappel.

Retrait d'un rappel d'un canal client

Vous pouvez retirer des rappels d'un canal client. Si vous retirez le dernier rappel, le canal est alors fermé. Pour retirer un rappel, émettez l'URL suivante avec une requête GET :

http://host:port/datasnap/rest/DSAdmin/UnregisterClientCallback/[SCN]/[CHID]/[CBID]/[SCNS]/[ST]/

Où SCN, CHID, SBID, SCNS et ST sont les valeurs passées lors du recensement du rappel (par le biais de la méthode ConsumeClientChannel ou RegisterClientCallbackServer).

Obtention d'une réponse depuis le serveur

Plusieurs situations se présentent quand une réponse revient à la requête du rappel avancé. Comme mentionné plus tôt, l'un des cas se produit quand le canal client a été créé avec succès. Un autre cas se produit si le rappel avancé est fermé. Toutefois, la réponse la plus courante sera une diffusion ou une notification. Les notifications d'un serveur ciblent un rappel unique d'un canal client, tandis que les diffusions doivent être données à tous les rappels.

Si vous avez un rappel avec l'ID 'cd12345', il est alors possible que le texte de la réponse d'une requête émise ressemble à ceci :

{"result":[{"invoke":["cb12345","Hello World" ,1]}]}

La réponse indique qu'une invocation (notifier) a été envoyée du serveur, l'invocation du rappel d'ID "cd12345" et le passage de la valeur chaîne "Hello World", de type 1. Le type 1 signifie que la valeur doit être traitée en tant que JSON, tandis que le type 2 signifie que c'est une représentation d'objet JSON d'un objet utilisateur. Le type 1 sera probablement le plus courant. Il n'est pas nécessaire que la valeur soit une chaîne, elle peut être une valeur JSON.

Si une diffusion est envoyée du serveur, elle est alors dirigée vers tous les rappels du canal client, sans spécification d'ID. Le texte de la réponse devrait ressembler à ceci :

{"result":[{"broadcast":["Hello World",1]}]}

Si le rappel avancé est fermé, le texte de réponse suivant est renvoyé :

{"result":[{"close":true}]}

Répondre à une réponse du serveur

Quand votre requête du rappel avancé reçoit une réponse du serveur, vous devez gérer le message aussi vite que possible, puis émettre une nouvelle requête. La requête que vous émettrez sera presque identique à la requête émise pour le recensement du canal client, avec seulement quelques légères différences. La première différence est que la commande doit être émise en tant que POST. Voici à quoi devrait ressembler l'URL de la requête :

http://host:port/datasnap/rest/DSAdmin/ConsumeClientChannel/[SCN]/[CHID]//[ST]

CBID est laissé en tant que chaîne vide (car aucun nouveau rappel n'est recensé) et puisque c'est un POST, le dernier paramètre (qui était une chaîne vide en utilisant deux barres obliques pour le recensement du canal client) est ignoré, car il sera passé avec le corps de la requête.

Le corps de la requête doit contenir le résultat renvoyé par le canal client, qui peut être une valeur JSON, au format chaîne. En général, c'est une bonne idée de renvoyer 'True' ou 'False'. Si une notification a été effectuée, le résultat doit alors être la valeur renvoyée pour le rappel spécifique invoqué. Si une diffusion est effectuée, la valeur renvoyée doit être basée sur toutes les valeurs renvoyées par les rappels (telle que 'False' à moins que tous les rappels aient renvoyé 'True'.)

Pour de plus amples informations, voir Rappels avancés REST.

Un exemple de messagerie PHP DataSnap REST

L'exemple suivant utilise un code PHP natif pour consommer un DataSnap REST qui passe un tableau.

  <?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>';
  ?>

Remarque : RAD PHP a son propre générateur de proxy mais il ne génèrera pas un objet TJSONObject, comme illustré dans l'exemple ci-dessus.