Débogage des API REST à l'aide de %CSP.Request et %CSP.Response
Il existe un grand nombre d'excellents outils pour tester vos API REST, surtout lorsqu'elles sont en ligne. Postman, diverses extensions de navigateur Web et même des scripts ObjectScript personnalisés écrits au moyen d'objets %Net.HttpRequest sont à votre disposition pour accomplir cette tâche. Cependant, il est souvent difficile de tester uniquement l'API REST sans impliquer involontairement le schéma d'authentification, la configuration de l'application Web ou même la connectivité du réseau. Cela représente beaucoup d'étapes à franchir simplement pour tester le code dans votre classe de répartition. La bonne nouvelle, c'est que si nous prenons le temps de comprendre comment fonctionne la classe %CSP.REST, nous trouverons une autre option permettant de tester uniquement le contenu de la classe de répartition. Nous pouvons configurer les objets de requête et de réponse pour invoquer directement les méthodes.
À propos de %request et %response
Si vous avez déjà utilisé des API REST dans InterSystems, vous avez probablement rencontré les deux objets qui pilotent l'ensemble du processus: %request et %response. Pour comprendre leur utilité, nous devons nous rappeler les principes fondamentaux d'ObjectScript concernant les "variables %". En général, une variable dans ObjectScript n'est accessible directement que dans le processus qui la définit. Cependant, une variable dotée d'un signe de pourcentage est publique dans ce processus. Cela signifie que toutes les méthodes du même processus peuvent y accéder. C'est pourquoi, en définissant nous-mêmes ces variables, nous pouvons déclencher nos méthodes API REST exactement comme elles s'exécuteraient lors de la réception d'une véritable requête entrante dans l'API.
Prenons l'exemple de la classe %CSP.REST suivante, qui présente deux fonctionnalités: deux routes et deux méthodes correspondantes. La première route accepte une requête contenant une date de naissance au format ODBC et renvoie l'âge en jours. La seconde accepte un nom en tant que paramètre URL, vérifie que la méthode de requête est correcte et renvoie simplement un message de bienvenue.
Class User.REST Extends %CSP.REST
{
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/calc/age" Method="POST" Call="CalcAge" />
<Route Url=”/echoname/:david” Method=”GET” Call=”EchoName” />
</Routes>
}
ClassMethod CalcAge() As %Status
{
try{
set reqObj = {}.%FromJSON(%request.Content)
set dob = reqObj.%Get("dob")
set today = $P($H,",",1)
set dobh = $ZDH(dob,3)
set agedays = today - dobh
set respObj = {}.%New()
do respObj.%Set("age_in_days",agedays)
set %response.ContentType = “application/json”
write respObj.%ToJSON()
return $$$OK
}
catch ex{
return ex.AsStatus()
}
}
ClassMethod EchoName(name As %String) As %Status
{
try{
if %request.Method '= "GET"{
$$$ThrowStatus($$$ERROR($$$GeneralError,"Wrong method."))
}
write "Hello, "_name
}
catch ex{
write "I'm sorry, "_name_". I'm afraid I can't do that."
}
}
}
Si nous essayons d'appeler ces méthodes à partir d'une session de terminal, elles échoueront car %request n'est pas défini.
USER>w $SYSTEM.Status.GetErrorText(##class(User.REST).CalcAge())
ERROR #5002: ObjectScript error: <INVALID OREF>CalcAge+2^User.REST.1
Normalement, la classe %CSP.REST crée cet objet à partir de la requête HTTP reçue en tant qu'instance de %CSP.Request. De même, l'objet %response est développé en tant qu'instance de %CSP.Response. Au lieu d'envoyer une requête HTTP, nous définirons nous-mêmes ces objets.
USER>set %request = ##class(%CSP.Request).%New()
USER>set %request.Content = ##class(%CSP.CharacterStream).%New()
USER>do %request.Content.Write("{""dob"":""1900-01-01""}")
USER>set %response = ##class(%CSP.Response).%New()
USER>do ##class(User.REST).CalcAge()
{"age_in_days":45990}
Dans ces quelques lignes, nous définissons la requête et initialisons sa propriété Content à %CSP.CharacterStream. Cette étape est essentielle, car autrement cette propriété resterait indéfinie et il serait impossible de l'écrire. Une autre alternative valable serait %CSP.BinaryStream. Nous remplissons le flux de contenu au moyen de JSON et initialisons la %response en tant que %CSP.Response sans avoir à intervenir. Il suffit simplement de la définir pour que la méthode s'exécute. Cette fois, lorsque nous appelons notre méthode de classe, nous pouvons voir le résultat attendu. Nous pourrions également émettre une commande zwrite %response pour inspecter le type de contenu de la réponse ou le code d'état HTTP afin de faciliter le dépannage des problèmes qui pourraient survenir.
Le test du résultat de notre méthode de classe ne nécessite que cinq lignes. Il s'agit évidemment d'un exemple volontairement simplifié. Cependant, cette méthode est bien plus efficace que de naviguer dans le portail de gestion du système pour configurer une application et créer les requêtes HTTP nécessaires pour authentifier et appeler cette méthode! Cela nous permet également de vérifier la méthode de manière totalement autonome. Lorsque nous envoyons une requête via les outils classiques que nous utilisons pour tester les API REST, nous vérifions l'ensemble de la pile: connectivité du réseau, configuration de l'application, mécanisme d'authentification, répartition des requêtes et méthode, le tout en une seule fois. L'approche décrite ci-dessus, en revanche, nous permet de tester exclusivement la méthode définie dans l'API, ce qui facilite la recherche du problème pendant le débogage.
De la même manière, nous pouvons tester la deuxième méthode en définissant l'objet %request approprié, puis en l'appelant. Dans ce cas, la transmission du nom se fera comme pour toute autre méthode de classe.
USER>set %request = ##class(%CSP.Request).%New()
USER>set %request.Method = “GET”
USER>set %response = ##class(%CSP.Response).%New()
USER>do ##class(User.REST).EchoName(“Dave”)
Hello, Dave
Dispatching des requêtes
Nous pouvons aller plus loin et tester également le dispatching. Vous avez peut-être remarqué que j'ai vérifié manuellement la méthode HTTP de la requête CSP dans l'exemple EchoName. Je l'ai fait parce que les appels de méthode directs contournent les règles de routage, ce qui empêche généralement les mappages incorrects.
Si nous décidons de poursuivre notre session de terminal au moyen de la requête %request que nous avons définie, nous pouvons déterminer sa méthode HTTP comme suit:
USER>set %request.Method = “GET”
Pour tester l'envoi, il suffit d'appeler la méthode DispatchRequest de la classe. Si l'opération réussit, nous verrons à nouveau le résultat de notre méthode:
USER>do ##class(User.REST).DispatchRequest(“/calc/age/Dave”,%request.Method)
Hello, Dave
Au moyen de l'objet %request correctement configuré, y compris la méthode HTTP, nous pouvons valider nos routes. Nous pouvons effectuer un test similaire pour le point de terminaison de calcul de l'âge en configurant à nouveau notre %request comme nous l'avons fait précédemment et en utilisant la méthode POST
USER>set %request = ##class(%CSP.Request).%New()
USER>set %request.Content = ##class(%CSP.CharacterStream).%New()
USER>do %request.Content.Write("{""dob"":""1900-01-01""}")
USER>set %request.Method = “POST”
USER>set %response = ##class(%CSP.Response).%New()
USER>do ##class(User.REST).CalcAge()
USER>do ##class(User.REST).DispatchRequest(“/calc/age”,%request.Method)
{"age_in_days":45990}
Notez que nous avons défini l'argument URL pour la méthode DispatchRequest comme étant uniquement la partie de l'URL située dans le nœud de route défini dans le XData de la classe dispatch. C'est tout ce dont nous avons besoin pour tester la répartition! Cela nous permet de tester les éléments indépendamment sans avoir besoin de connaître l'URL de déploiement finale ou la configuration de son application web. Ainsi, nous avons pu tester tout ce que nous définissons généralement dans une classe %CSP.REST sans avoir à nous soucier des problèmes externes habituels qui l'entourent.
D'ailleurs, j'ai commencé les sessions en ayant %request sur une nouvelle instance de %CSP.Request. Cependant, si vous prévoyez d'effectuer plusieurs tests à la suite, vous pouvez également utiliser la méthode %request.Reset() pour recommencer avec votre objet de requête. Cela vaut également pour l'objet %response.
Comme votre requête peut être plus complexe que celle-ci, je vous invite vivement à vous familiariser avec les propriétés et les méthodes de la classe %CSP.Request. Par exemple, il est assez courant que votre API vérifie le type de contenu de la requête et s'assure qu'il correspond bien à ce qu'elle attend. Dans ce cas, vous pouvez définir %request.ContentType sur application/json ou toute autre valeur appropriée à votre utilisation. Vous pouvez configurer les cookies, les données MIME et les données de requête. Vous pouvez également définir la propriété Secure pour déterminer de manière simulée si la requête a utilisé HTTPS ou non.
Quelques avertissements
Il y a deux considérations principales à prendre en compte lorsque vous effectuez des tests de cette manière. Tout d'abord, si notre API renvoie un fichier, le résultat affiché sur le terminal peut devenir un peu difficile à gérer. Dans ce cas, vous devez rediriger le résultat vers un fichier. Nous pouvons y parvenir au moyen des commandes suivantes:
USER>set %request = ##class(%CSP.Request).%New()
USER>set %request.Method = "GET"
USER>set io = "C:\Users\DavidHockenbroch\Desktop\output.txt"
USER>open io:"NW":10
USER>write $TEST
1
USER>use io do ##class(User.REST).DispatchRequest("/echoname/Dave",%request.Method)
USER>close io
Je dispose désormais d'un fichier texte sur mon desktop intitulé "Hello, Dave". À l'aide de N et W dans la commande open, nous créons le fichier s'il n'existe pas encore, puis nous l'ouvrons pour pouvoir y écrire. Notez que nous vérifions $TEST. Si cette variable est égale à 0 lorsque nous la vérifions, cela signifie qu'un problème est survenu lors de l'ouverture du fichier en tant que fichier d'écriture. Cela peut indiquer un problème d'autorisation d'accès au fichier, ou le fichier peut déjà être ouvert et verrouillé par un autre processus. Par mesure de bonne pratique, nous devons toujours penser à fermer le fichier.
Le deuxième problème est que si nous prévoyons de définir une classe d'événements de session spéciale dans la configuration de l'application web et que ces événements personnalisés ont un impact sur le fonctionnement de nos méthodes REST, cela peut poser des problèmes, car nous contournons ces méthodes. Nous devrons les appeler manuellement en tant que méthodes de classe dans le terminal.
Et maintenant, en avant!
Maintenant que nous avons défini des moyens de tester notre API sans contrainte supplémentaire, nous sommes prêts à passer à notre véritable objectif: les tests unitaires. Restez connectés pour le prochain article!