#REST API

0 Abonnés · 43 Publications

Le transfert d'état représentationnel (Representational state transfer, REST) est un style architectural logiciel qui définit un ensemble de contraintes à utiliser pour créer des services web. Les services web conformes au style architectural REST, appelés Web RESTful Services (RWS), assurent l'interopérabilité entre les systèmes informatiques sur Internet. Les Web RESTful Services permettent aux systèmes demandeurs d'accéder aux représentations textuelles des ressources web et de les manipuler en utilisant un ensemble uniforme et prédéfini d'opérations sans état. D'autres types de services web, tels que les Web SOAP Services, fournissent leurs propres ensembles arbitraires d'opérations.

En savoir plus.

Article Lucas Enard · Sept 18, 2022 8m read

1. Fhir-client-net

Ceci est un client fhir simple en c# pour s'exercer avec les ressources fhir et les requêtes CRUD vers un serveur fhir.
Notez que pour la majeure partie, l'autocomplétion est activée.

GitHub

2. Préalables

Assurez-vous que git et Docker desktop sont installé.

Si vous travaillez à l'intérieur du conteneur, ces modules sont déjà installés.
Si ce n'est pas le cas, utilisez nugget pour installer Hl7.Fhir.R4, nous allons utiliser Model et Rest à partir de celui-ci :
Hl7.Fhir.Model

Hl7.Fhir.Rest

3. Installation

3.1. Installation pour le développement

Clone/git tire le repo dans n'importe quel répertoire local, par exemple comme indiqué ci-dessous :

git clone https://github.com/LucasEnard/fhir-client-net.git

Ouvrez le terminal dans ce répertoire et lancez :

docker build .

3.2. Portail de gestion et VSCode

Ce référentiel est prêt pour VS Code.

Ouvrez le dossier fhir-client-net cloné localement dans VS Code.

Si vous y êtes invité (coin inférieur droit), installez les extensions recommandées.

3.3. Avoir le dossier ouvert à l'intérieur du conteneur

Vous pouvez être à l'intérieur du conteneur avant de coder si vous le souhaitez.
Pour cela, il faut que docker soit activé avant d'ouvrir VSCode.
Ensuite, dans VSCode, lorsque vous y êtes invité ( coin inférieur droit ), rouvrez le dossier à l'intérieur du conteneur afin de pouvoir utiliser les composants python qu'il contient.
La première fois que vous effectuez cette opération, cela peut prendre plusieurs minutes, le temps que le conteneur soit préparé.

Si vous n'avez pas cette option, vous pouvez cliquer dans le coin inférieur gauche et cliquer sur press reopen in container puis sélectionner From Dockerfile

Plus d'informations ici

Architecture


En ouvrant le dossier à distance, vous permettez à VS Code et à tous les terminaux que vous ouvrez dans ce dossier d'utiliser les composants c# dans le conteneur.

Si vous y êtes invité (coin inférieur droit), installez les extensions recommandées.

4. Serveur FHIR

Pour réaliser cette présentation, vous aurez besoin d'un serveur FHIR.
Vous pouvez soit utiliser le vôtre, soit vous rendre sur le site InterSystems free FHIR trial et suivre les étapes suivantes pour le configurer.

En utilisant notre essai gratuit, il suffit de créer un compte et de commencer un déploiement, puis dans l'onglet Overview vous aurez accès à un endpoint comme https://fhir.000000000.static-test-account.isccloud.io que nous utiliserons plus tard.
Ensuite, en allant dans l'onglet d'informations d'identification Credentials, créez une clé api et enregistrez-la quelque part.

C'est maintenant terminé, vous avez votre propre serveur fhir pouvant contenir jusqu'à 20 Go de données avec une mémoire de 8 Go.

5. Présentation pas à pas

La présentation pas à pas du client se trouve à /Client.cs.

Le code est divisé en plusieurs parties, et nous allons couvrir chacune d'entre elles ci-dessous.

5.1. Partie 1

Dans cette partie, nous connectons notre client à notre serveur en utilisant Fhir.Rest.


// Partie 1

// Creation of an htpclient holding the api key of the server as an header
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("x-api-key", "api-key");

var settings = new FhirClientSettings
    {
        Timeout = 0,
        PreferredFormat = ResourceFormat.Json,
        VerifyFhirVersion = true,
        // PreferredReturn can take Prefer.ReturnRepresentation or Prefer.ReturnMinimal to return the full resource or an empty payload
        PreferredReturn = Prefer.ReturnRepresentation
    };
// Création de notre client en utilisant une correcte url
var client = new FhirClient("url",httpClient,settings);

Afin de vous connecter à votre serveur, vous devez modifier la ligne :

httpClient.DefaultRequestHeaders.Add("x-api-key", "api-key");

Et cette ligne aussi :

var client = new FhirClient("url",httpClient,settings);

L'url' est un point de terminaison tandis que l'"api-key" est la clé d'api pour accéder à votre serveur.

Notez que si vous n'utilisez pas un serveur InterSystems, vous pouvez vérifier comment autoriser vos accès si nécessaire.

Comme ça, nous avons un client FHIR capable d'échanger directement avec notre serveur.

5.2. Partie 2

Dans cette partie, nous créons un Patient en utilisant Fhir.Model et nous le complétons avec un HumanName, en suivant la convention FHIR, use et family sont des chaînes et given est une liste de chaînes. De la même manière, un patient peut avoir plusieurs HumanNames, donc nous devons mettre notre HumanName dans une liste avant de le mettre dans notre patient nouvellement créé.


// Partie 2

// Building a new patient and setting the names
var patient0 = new Patient();
patient0.Name.Add(new HumanName().WithGiven("GivenName").AndFamily("FamilyName"));

// Creation of our client in the server
// It is to be noted that using SearchParams you can check if an equivalent resource already exists in the server
// For more information https://docs.fire.ly/projects/Firely-NET-SDK/client/crud.html
var created_pat = client.Create<Patient>(patient0);

Console.Write("Partie 2 : Identifiant du patient nouvellement créé : ");
Console.WriteLine(created_pat.Id);

Après cela, nous devons sauvegarder notre nouveau Patient sur notre serveur en utilisant notre client.

Notez que si vous lancez Client.cs plusieurs fois, plusieurs Patients ayant le nom que nous avons choisi seront créés.
C'est parce que, suivant la convention FHIR, vous pouvez avoir plusieurs Patients avec le même nom, seul l' id est unique sur le serveur.
Vérifier la documentation pour avoir plus d'information.
Il est à noter qu'en utilisant SearchParams vous pouvez vérifier si une ressource équivalente existe déjà sur le serveur avant de la créer.
Pour plus d'information https://docs.fire.ly/projects/Firely-NET-SDK/client/crud.html

Nous conseillons donc de commenter la ligne après le premier lancement.

5.3. Partie 3

Dans cette partie, nous avons un client qui recherche un patient nommé d'après celui que nous avons créé précédemment.


// Part 3

// This gets all the Patient having the exact name "FamilyName" and we take the first one
// Note that if you have multiple patients with the same name, you will get only the first one 
// We advise to use the id, the names, and any other informations for the SearchParams to be sure to get the right patient
var q = new SearchParams().Where("name:exact=FamilyName");
Bundle bund = client.Search<Patient>(q);
patient0 = bund.Entry[0].Resource as Patient;

Console.Write("Part 3 : Name of the patient we found by searching : ");
Console.WriteLine(patient0.Name[0]);


// Creation of our patient telecom, here a phone number
patient0.Telecom.Add(new ContactPoint(new ContactPoint.ContactPointSystem(),new ContactPoint.ContactPointUse(),"1234567890"));

// Change the given name of our patient
patient0.Name[0].Given = new List<string>() { "AnotherGivenName" };

Console.Write("Part 3 : Name of the changed patient : ");
Console.WriteLine(patient0.Name[0]);

Console.Write("Part 3 : Phone of the changed patient : ");
Console.WriteLine(patient0.Telecom[0].Value);

// Update the patient
var update_pat = client.Update<Patient>(patient0);

Une fois que nous l'avons trouvé, nous ajoutons un numéro de téléphone à son profil et nous changeons son prénom en un autre.

Maintenant nous pouvons utiliser la fonction de mise à jour de notre client pour mettre à jour notre patient sur le serveur.

5.4. Partie 4

Dans cette section, nous voulons créer une observation pour notre patient. Pour ce faire, nous avons besoin de son identifiant, qui est son identifiant unique.
A partir de là, nous remplissons notre observation et ajoutons comme sujet, l'identifiant de notre Patient.


// Part 4

// Building of our new observation
Observation obsv = new Observation {

    Value = new Quantity(70, "kg"),
    Code = new CodeableConcept {
        Coding = new List<Coding> {
            new Coding {
                System = "http://loinc.org",
                Code = "29463-7",
                Display = "Body weight"
            }
        }},
    Category = new List<CodeableConcept> {
        new CodeableConcept {
            Coding = new List<Coding> {
                new Coding {
                    System = "http://snomed.info/sct",
                    Code = "276327007",
                    Display = "Body weight"
                }
            }
        }},
    Status = new ObservationStatus {},
    Subject = new ResourceReference {
        Reference = "Patient/" + update_pat.Id}

    };

// Creation of our observation in the server
var new_obsv = client.Create<Observation>(obsv);

Console.Write("Part 4 : Id of the observation : ");
Console.WriteLine(new_obsv.Id);

Ensuite, nous enregistrons notre observation à l'aide de la fonction create.

5.5. Conclusion de la présentation

Si vous avez suivi ce parcours, vous savez maintenant exactement ce que fait Client.cs, vous pouvez le lancer et vérifier votre Patient et votre Observation nouvellement créés sur votre serveur.

Pour le lancer, ouvrez un terminal VSCode et entrez :

dotnet run

Vous devriez voir des informations sur le Patient créé et son observation.

Si vous utilisez un serveur Intersystems, allez à API Deployement, autorisez-vous avec la clé api et d'ici vous pouvez OBTENIR par id le patient et l'observation que nous venons de créer.

7. Comment commencer le codage

Ce référentiel est prêt à être codé dans VSCode avec les plugins InterSystems. Ouvrez Client.cs pour commencer à coder ou utiliser l'autocomplétion.

8. Ce qu'il y a dans le référentiel

8.1. Dockerfile

Un dockerfile pour créer un dot net env pour que vous puissiez travailler.
Utilisez docker build . pour construire et rouvrir votre fichier dans le conteneur pour travailler à l'intérieur de celui-ci.

8.2. .vscode/settings.json

Fichier de paramètres

8.3. .vscode/launch.json

Fichier de configuration si vous voulez déboguer

0
0 183
Article Lucas Enard · Sept 14, 2022 12m read

1. Fhir-client-python

Ceci est un client fhir simple en python pour s'exercer avec les ressources fhir et les requêtes CRUD vers un serveur FHIR.
Notez que pour la plupart, l'autocomplétion est activée, c'est la principale raison d'utiliser fhir.resources.

GitHub

2. Préalables

Assurez-vous que git et Docker desktop sont installé.

Si vous travaillez à l'intérieur du conteneur, comme il est montré dans 3.3., vous n'avez pas besoin d'installer fhirpy et fhir.resources.

Si vous n'êtes pas dans le conteneur, vous pouvez utiliser pip pour installer fhirpy et fhir.resources.
Vérifiez fhirpy et fhir.resources pour plus d'information.

3. Installation

3.1. Installation pour le développement

Clone/git tire le repo dans n'importe quel répertoire local, par exemple comme indiqué ci-dessous :

git clone https://github.com/LucasEnard/fhir-client-python.git

Ouvrez le terminal dans ce répertoire et lancez :

docker build.

3.2. Portail de gestion et VSCode

Ce référentiel est prêt pour VS Code.

Ouvrez le dossier fhir-client-python cloné localement dans VS Code.

Si vous y êtes invité (coin inférieur droit), installez les extensions recommandées.

3.3. Avoir le dossier ouvert à l'intérieur du conteneur

Vous pouvez être à l'intérieur du conteneur avant de coder si vous le souhaitez.
Pour cela, il faut que docker soit activé avant d'ouvrir VSCode.
Ensuite, dans VSCode, lorsque vous y êtes invité ( coin inférieur droit ), rouvrez le dossier à l'intérieur du conteneur afin de pouvoir utiliser les composants python qu'il contient.
La première fois que vous effectuez cette opération, cela peut prendre plusieurs minutes, le temps que le conteneur soit préparé.

Si vous n'avez pas cette option, vous pouvez cliquer dans le coin inférieur gauche et cliquer sur Ouvrir à nouveau dans le conteneur puis sélectionner De Dockerfile

Plus d'informations ici

Architecture


En ouvrant le dossier à distance, vous permettez à VS Code et à tous les terminaux que vous ouvrez dans ce dossier d'utiliser les composants python dans le conteneur.

4. Serveur FHIR

Pour réaliser cette présentation, vous aurez besoin d'un serveur FHIR.
Vous pouvez soit utiliser le vôtre, soit vous rendre sur le site InterSystems free FHIR trial et suivre les étapes suivantes pour le configurer.

En utilisant notre essai gratuit, il suffit de créer un compte et de commencer un déploiement, puis dans l'onglet Overview vous aurez accès à un endpoint comme https://fhir.000000000.static-test-account.isccloud.io que nous utiliserons plus tard.
Ensuite, en allant dans l'onglet d'informations d'identification Credentials, créez une clé api et enregistrez-la quelque part.

C'est maintenant terminé, vous avez votre propre serveur fhir pouvant contenir jusqu'à 20 Go de données avec une mémoire de 8 Go.

5. Présentation pas à pas

La présentation pas à pas du client se trouve à src/client.py.

Le code est divisé en plusieurs parties, et nous allons couvrir chacune d'entre elles ci-dessous.

5.1. Partie 1

Dans cette partie, nous connectons notre client à notre serveur en utilisant fhirpy et nous obtenons nos ressources Patient à l'intérieur de la variable patients_resources.
A partir de cette variable nous pourrons fecth n'importe quel Patient et même les trier ou obtenir un Patient en utilisant certaines conditions.

#Partie 1----------------------------------------------------------------------------------------------------------------------------------------------------
#Créer notre client connecté à notre serveur
client = SyncFHIRClient(url='url', extra_headers={"x-api-key":"api-key"})

#Obtenir les ressources de notre patient dans lesquelles nous pourrons aller chercher et rechercher
patients_resources = client.resources('Patient')

Afin de vous connecter à votre serveur, vous devez modifier la ligne :

client = SyncFHIRClient(url='url', extra_headers={"x-api-key":"api-key"})

L'url' est un point de terminaison tandis que l'"api-key" est la clé d'api pour accéder à votre serveur.

Notez que si vous n'utilisez pas un serveur InterSystems, vous pouvez vérifier comment changer extra_headers={"x-api-key":"api-key"} en authorization = "api-key".

Comme ça, nous avons un client FHIR capable d'échanger directement avec notre serveur.

5.2. Partie 2

Dans cette partie, nous créons un Patient en utilisant Fhir.Model et nous le complétons avec un HumanName, en suivant la convention FHIR, use et family sont des chaînes et given est une liste de chaînes. e la même manière, un patient peut avoir plusieurs HumanNames, donc nous devons mettre notre HumanName dans une liste avant de le mettre dans notre patient nouvellement créé.

#Partie 2----------------------------------------------------------------------------------------------------------------------------------------------------
#Nous voulons créer un patient et l'enregistrer sur notre serveur

#Créer un nouveau patient en utilisant fhir.resources
patient0 = Patient()

#Créer un HumanName et le remplir avec les informations de notre patient
name = HumanName()
name.use = "official"
name.family = "familyname"
name.given = ["givenname1","givenname2"]

patient0.name = [name]

#Vérifier notre patient dans le terminal
print()
print("Our patient : ",patient0)
print()

#Sauvegarder (post) notre patient0, cela va le créer sur notre serveur
client.resource('Patient',**json.loads(patient0.json())).save()

Après cela, nous devons sauvegarder notre nouveau Patient sur notre serveur en utilisant notre client.

Notez que si vous lancez client.py plusieurs fois, plusieurs Patients ayant le nom que nous avons choisi seront créés.
C'est parce que, suivant la convention FHIR, vous pouvez avoir plusieurs Patients avec le même nom, seul l' id est unique sur le serveur.
Alors pourquoi n'avons-nous pas rempli notre Patient avec un id de la même façon que nous avons rempli son nom ?
Parce que si vous mettez un id à l'intérieur de la fonction save(), la sauvegarde agira comme une mise à jour avant d'agir comme un sauveur, et si l'id n'est en fait pas déjà sur le serveur, il le créera comme prévu ici. Mais comme nous avons déjà des patients sue notre serveur, ce n'est pas une bonne idée de créer un nouveau patient et d'allouer à la main un Identifiant puisque la fonction save() et le serveur sont faits pour le faire à votre place.

Nous conseillons donc de commenter la ligne après le premier lancement.

5.3. Partie 3

Dans cette partie, nous avons un client qui cherche dans nos patients_resources un patient nommé d'après celui que nous avons créé précédemment.

#Partie 3----------------------------------------------------------------------------------------------------------------------------------------------------
#Maintenant, nous voulons obtenir un certain patient et ajouter son numéro de téléphone et changer son nom avant de sauvegarder nos changements sur le serveur

#Obtenez le patient en tant que fhir.resources Patient de notre liste de ressources de patients qui a le bon nom, pour la commodité, nous allons utiliser le patient que nous avons créé avant
patient0 = Patient.parse_obj(patients_resources.search(family='familyname',given='givenname1').first().serialize())

#Créer le nouveau numéro de téléphone de notre patient
telecom = ContactPoint()

telecom.value = '555-748-7856'
telecom.system = 'phone'
telecom.use = 'home'

#Ajouter le téléphone de notre patient à son dossier
patient0.telecom = [telecom]

#Changer le deuxième prénom de notre patient en "un autre prénom"
patient0.name[0].given[1] = "anothergivenname"

#Vérifiez notre Patient dans le terminal
print()
print("Notre patient avec le numéro de téléphone et le nouveau prénom : ",patient0)
print()

#Sauvegarder (mettre) notre patient0, ceci sauvera le numéro de téléphone et le nouveau prénom au patient existant de notre serveur
client.resource('Patient',**json.loads(patient0.json())).save()

Une fois que nous l'avons trouvé, nous ajoutons un numéro de téléphone à son profil et nous changeons son prénom en un autre.

Maintenant nous pouvons utiliser la fonction de mise à jour de notre client pour mettre à jour notre patient sur le serveur.

5.4. Partie 4

Dans cette partie, nous voulons créer une observation pour notre patient précédent, pour ce faire, nous cherchons d'abord notre patients_resources pour notre patient, puis nous obtenons son identifiant, qui est son identificateur unique.

#Partie 4----------------------------------------------------------------------------------------------------------------------------------------------------
#Maintenant nous voulons créer une observation pour notre client

#Obtenir l'identifiant du patient auquel vous voulez attacher l'observation
id = Patient.parse_obj(patients_resources.search(family='familyname',given='givenname1').first().serialize()).id
print("id of our patient : ",id)

#Placer notre code dans notre observation, code qui contient des codages qui sont composés de système, code et affichage
coding = Coding()
coding.system = "https://loinc.org"
coding.code = "1920-8"
coding.display = "Aspartate aminotransférase [Activité enzymatique/volume] dans le sérum ou le plasma"
code = CodeableConcept()
code.coding = [coding]
code.text = "Aspartate aminotransférase [Activité enzymatique/volume] dans le sérum ou le plasma"

#Créer une nouvelle observation en utilisant fhir.resources, nous entrons le statut et le code dans le constructeur car ils sont nécessaires pour valider une observation
observation0 = Observation(status="final",code=code)

#Définir notre catégorie dans notre observation, catégorie qui détient les codages qui sont composés de système, code et affichage
coding = Coding()
coding.system = "https://terminology.hl7.org/CodeSystem/observation-category"
coding.code = "laboratoire"
coding.display = "laboratoire"
category = CodeableConcept()
category.coding = [coding]
observation0.category = [category]

#Définir l'heure de notre date effective dans notre observation
observation0.effectiveDateTime = "2012-05-10T11:59:49+00:00"

#Régler l'heure de notre date d'émission dans notre observation
observation0.issued = "2012-05-10T11:59:49.565+00:00"

#Définir notre valueQuantity dans notre observation, valueQuantity qui est composée d'un code, d'un unir, d'un système et d'une valeur
valueQuantity = Quantity()
valueQuantity.code = "U/L"
valueQuantity.unit = "U/L"
valueQuantity.system = "https://unitsofmeasure.org"
valueQuantity.value = 37.395
observation0.valueQuantity = valueQuantity

#Définir la référence à notre patient en utilisant son identifiant
reference = Reference()
reference.reference = f"Patient/{id}"
observation0.subject = reference

#Vérifiez notre observation dans le terminal
print()
print("Notre observation : ",observation0)
print()

#Sauvegarder (poster) notre observation0 en utilisant notre client
client.resource('Observation',**json.loads(observation0.json())).save()

Ensuite, nous enregistrons notre observation à l'aide de la fonction save().

5.5. Conclusion de la présentation

Si vous avez suivi ce parcours, vous savez maintenant exactement ce que fait client.py, vous pouvez le lancer et vérifier votre Patient et votre Observation nouvellement créés sur votre serveur.

6. Comment ça marche

6.1. Les importations

from fhirpy import SyncFHIRClient

from fhir.resources.patient import Patient
from fhir.resources.observation import Observation
from fhir.resources.humanname import HumanName
from fhir.resources.contactpoint import ContactPoint

import json

La première importation est le client, ce module va nous aider à nous connecter au serveur, à obtenir et exporter des ressources.

Le module fhir.resources nous aide à travailler avec nos ressources et nous permet, grâce à l'auto-complétion, de trouver les variables dont nous avons besoin.

La dernière importation est json, c'est un module nécessaire pour échanger des informations entre nos 2 modules.

6.2. Création du client

client = SyncFHIRClient(url='url', extra_headers={"x-api-key":"api-key"})

L''url' est ce que nous avons appelé avant un point de terminaison tandis que l'"api-key"' est la clé que vous avez générée plus tôt.

Notez que si vous n'utilisez pas un serveur InterSystems, vous voudrez peut-être changer le extra_headers={"x-api-key" : "api-key"} en authorization = "api-key"
Comme ça, nous avons un client FHIR capable d'échanger directement avec notre serveur.

Par exemple, vous pouvez accéder à vos ressources Patient en faisant patients_resources = client.resources('Patient') , fà partir de là, vous pouvez soit obtenir vos patients directement en utilisant patients = patients_resources.fetch() ou en allant chercher après une opération, comme :
patients_resources.search(family='familyname',given='givenname').first() cette ligne vous donnera le premier patient qui sort ayant pour nom de famille 'familyname' et pour nom donné 'givenname'.

6.3. Travailler sur nos ressources

Une fois que vous avez les ressources que vous voulez, vous pouvez les analyser dans une ressource fhir.resources.

Par exemple :

patient0 = Patient.parse_obj(patients_resources.search(family='familyname',given='givenname1').first().serialize())

patient0 est un patient de fhir.resources, pour l'obtenir nous avons utilisé nos patients_resources comme cela a été montré précédemment où nous avons sélectionné un certain nom de famille et un prénom, après cela nous avons pris le premier qui est apparu et l'avons sérialisé.
En mettant ce patient sérialisé à l'intérieur d'un Patient.parse_obj nous allons créer un patient de fhir.resources .

Maintenant, vous pouvez accéder directement à n'importe quelle information que vous voulez comme le nom, le numéro de téléphone ou toute autre information.
Pour ce faire, utilisez juste par exemple :

patient0.name

Cela renvoie une liste de HumanName composée chacune d'un attribut use un attribut family un attribut given as the FHIR convention is asking.
Cela signifie que vous pouvez obtenir le nom de famille de quelqu'un en faisant :

patient0.name[0].family

6.4. Sauvegarder nos changements

Pour enregistrer toute modification de notre serveur effectuée sur un fhir.resources ou pour créer une nouvelle ressource serveur, nous devons utiliser à nouveau notre client.

client.resource('Patient',**json.loads(patient0.json())).save()

En faisant cela, nous créons une nouvelle ressource sur notre client, c'est-à-dire un patient, qui obtient ses informations de notre fhir.resources patient0. Ensuite, nous utilisons la fonction save() pour poster ou mettre notre patient sur le serveur.

7. Comment commencer le codage

Ce référentiel est prêt à être codé dans VSCode avec les plugins InterSystems. Ouvrez /src/client.py pour commencer à coder ou utiliser l'autocomplétion.

8. Ce qu'il y a dans le référentiel

8.1. Dockerfile

Le dockerfile le plus simple pour démarrer un conteneur Python.
Utilisez docker build . pour construire et rouvrir votre fichier dans le conteneur pour travailler à l'intérieur de celui-ci.

8.2. .vscode/settings.json

Fichier de paramètres.

8.3. .vscode/launch.json

Fichier de configuration si vous voulez déboguer.

0
0 808
Article Lucas Enard · Sept 12, 2022 9m read

1. Fhir-client-java

Ceci est un client fhir simple en java pour s'exercer avec les ressources fhir et les requêtes CRUD vers un serveur fhir.
Notez que pour la majeure partie, l'autocomplétion est activée.

GitHub

2. Préalables

Assurez-vous que git et Docker desktop sont installé.

Déjà installé dans le conteneur :
Hapi Fhir modèle et client

3. Installation

3.1. Installation pour le développement

Clone/git tire le repo dans n'importe quel répertoire local, par exemple comme indiqué ci-dessous :

git clone https://github.com/LucasEnard/fhir-client-java.git

Ouvrez le terminal dans ce répertoire et exécutez :

docker build .

3.2. Portail de gestion et VSCode

Ce référentiel est prêt pour CodeVS.

Ouvrez le dossier fhir-client-java cloné localement dans VS Code.

Si vous y êtes invité (coin inférieur droit), installez les extensions recommandées.

3.3. Avoir le dossier ouvert à l'intérieur du conteneur

Vous pouvez être à l'intérieur du conteneur avant de coder si vous le souhaitez.
Pour cela, il faut que docker soit activé avant d'ouvrir VSCode.
Ensuite, dans VSCode, lorsque vous y êtes invité ( coin inférieur droit ), rouvrez le dossier à l'intérieur du conteneur afin de pouvoir utiliser les composants python qu'il contient.
La première fois que vous effectuez cette opération, cela peut prendre plusieurs minutes, le temps que le conteneur soit préparé.

Si vous n'avez pas cette option, vous pouvez cliquer dans le coin inférieur gauche et cliquer sur Ouvrir à nouveau dans le conteneur puis sélectionner De Dockerfile

Plus d'informations ici

Architecture


En ouvrant le dossier à distance, vous permettez à VS Code et à tous les terminaux que vous ouvrez dans ce dossier d'utiliser les composants java dans le conteneur.

4. Serveur FHIR

Pour réaliser cette présentation, vous aurez besoin d'un serveur FHIR.
Vous pouvez soit utiliser le vôtre, soit vous rendre sur le site Essai FHIR gratuit d'InterSystems et suivre les étapes suivantes pour le configurer.

En utilisant notre essai gratuit, il suffit de créer un compte et de commencer un déploiement, puis dans l'onglet Overview vous aurez accès à un endpoint comme https://fhir.000000000.static-test-account.isccloud.io que nous utiliserons plus tard.
Ensuite, en allant dans l'onglet d'informations d'identification Credentials, créez une clé api et enregistrez-la quelque part.

C'est maintenant terminé, vous avez votre propre serveur fhir pouvant contenir jusqu'à 20 Go de données avec une mémoire de 8 Go.

5. Présentation pas à pas

La présentation pas à pas du client se trouve à src/java/test/Client.java.

Le code est divisé en plusieurs parties, et nous allons couvrir chacune d'entre elles ci-dessous.

5.1. Partie 1

Dans cette partie, nous connectons notre client à notre serveur en utilisant Fhir.Rest.


// Partie 1

      // Créer un contexte en utilisant FHIR R4
      FhirContext ctx = FhirContext.forR4();

      // créer un en-tête contenant la clé api pour le httpClient
      Header header = new BasicHeader("x-api-key", "api-key");
      ArrayList<Header> headers = new ArrayList<Header>();
      headers.add(header);

      // créer un constructeur httpClient et lui ajouter l'en-tête
      HttpClientBuilder builder = HttpClientBuilder.create();
      builder.setDefaultHeaders(headers);

      // créer un httpClient à l'aide du constructeur
      CloseableHttpClient httpClient = builder.build();

      // Configurer le httpClient au contexte en utilisant la fabrique
      ctx.getRestfulClientFactory().setHttpClient(httpClient);

      // Créer un client
      IGenericClient client = ctx.newRestfulGenericClient("url");

Afin de vous connecter à votre serveur, vous devez modifier la ligne :

Header header = new BasicHeader("x-api-key", "api-key");

Et cette ligne aussi :

IGenericClient client = ctx.newRestfulGenericClient("url");

L'url' est un point de terminaison tandis que l'"api-key" est la clé d'api pour accéder à votre serveur.

Notez que si vous n'utilisez pas un serveur InterSystems, vous pouvez vérifier comment autoriser vos accès si nécessaire.

Comme ça, nous avons un client FHIR capable d'échanger directement avec notre serveur.

5.2. Partie 2

Dans cette partie, nous créons un Patient en utilisant Fhir.Model et nous le complétons avec un HumanName, en suivant la convention FHIR, use et family sont des chaînes et given est une liste de chaînes. De la même manière, un patient peut avoir plusieurs HumanNames, donc nous devons mettre notre HumanName dans une liste avant de le mettre dans notre patient nouvellement créé.


// Partie 2

      // Créer un patient et lui ajouter un nom
      Patient patient = new Patient();
      patient.addName()
         .setFamily("FamilyName")
         .addGiven("GivenName1")
         .addGiven("GivenName2");

      // Voir aussi patient.setGender ou setBirthDateElement

      // Créer le patient ressource sur le serveur
		MethodOutcome outcome = client.create()
         .resource(patient)
         .execute();

      // Enregistrez l'ID que le serveur a attribué
      IIdType id = outcome.getId();
      System.out.println("");
      System.out.println("Created patient, got ID: " + id);
      System.out.println("");

Après cela, nous devons sauvegarder notre nouveau Patient sur notre serveur en utilisant notre client.

Notez que si vous lancez Client.java plusieurs fois, plusieurs Patients ayant le nom que nous avons choisi seront créés.
C'est parce que, suivant la convention FHIR, vous pouvez avoir plusieurs Patients avec le même nom, seul l' id est unique sur le serveur.
Vérifier the doc pour avoir plus d'information.
Nous conseillons donc de commenter la ligne après le premier lancement.

5.3. Partie 3

Dans cette partie, nous avons un client qui recherche un patient nommé d'après celui que nous avons créé précédemment.

// Partie 3

      // Rechercher un patient unique avec le nom de famille exact "NomDeFamille" et le prénom exact "Prénom1"
      patient = (Patient) client.search()
         .forResource(Patient.class)
         .where(Patient.FAMILY.matchesExactly().value("FamilyName"))
         .and(Patient.GIVEN.matchesExactly().value("GivenName1"))
         .returnBundle(Bundle.class)
         .execute()
         .getEntryFirstRep()
         .getResource();

      // Créer une télécommunication pour le patient
      patient.addTelecom()
         .setSystem(ContactPointSystem.PHONE)
         .setUse(ContactPointUse.HOME)
         .setValue("555-555-5555");

      // Changer le prénom du patient en un autre
      patient.getName().get(0).getGiven().set(0,  new StringType("AnotherGivenName"));

      // Mettre à jour le patient de ressource sur le serveur
      MethodOutcome outcome2 = client.update()
         .resource(patient)
         .execute();

Une fois que nous l'avons trouvé, nous ajoutons un numéro de téléphone à son profil et nous changeons son prénom en un autre.

Maintenant nous pouvons utiliser la fonction de mise à jour de notre client pour mettre à jour notre patient sur le serveur.

5.4. Partie 4

Dans cette section, nous voulons créer une observation pour notre patient. Pour ce faire, nous avons besoin de son identifiant, qui est son identifiant unique.
A partir de là, nous remplissons notre observation et ajoutons comme sujet, l'identifiant de notre Patient.

// Partie 4

      // Créer un CodeableConcept et le remplir
      CodeableConcept codeableConcept = new CodeableConcept();
      codeableConcept.addCoding()
         .setSystem("http://snomed.info/sct")
         .setCode("1234")
         .setDisplay("CodeableConceptDisplay");

      // Créer une quantité et la remplir
      Quantity quantity = new Quantity();
      quantity.setValue(1.0);
      quantity.setUnit("kg");

      // Créer une catégorie et la remplir
      CodeableConcept category = new CodeableConcept();
      category.addCoding()
         .setSystem("http://snomed.info/sct")
         .setCode("1234")
         .setDisplay("CategoryDisplay");

      // Créer une liste de CodeableConcepts et y placer des catégories
      ArrayList<CodeableConcept> codeableConcepts = new ArrayList<CodeableConcept>();
      codeableConcepts.add(category);

      // Créer une observation
      Observation observation = new Observation();
      observation.setStatus(Observation.ObservationStatus.FINAL);
      observation.setCode(codeableConcept);
      observation.setSubject(new Reference().setReference("Patient/" + ((IIdType) outcome2.getId()).getIdPart()));
      observation.setCategory(codeableConcepts);
      observation.setValue(quantity);

      System.out.println("");
      System.out.println("Created observation, reference : " + observation.getSubject().getReference());
      System.out.println("");

       // Créer l'observation de ressource sur le serveur
      MethodOutcome outcome3 = client.create()
         .resource(observation)
         .execute();

      // Imprimer la réponse du serveur
      System.out.println("");
      System.out.println("Created observation, got ID: " + outcome3.getId());
      System.out.println("");

Ensuite, nous enregistrons notre observation à l'aide de la fonction de création.

5.5. Conclusion de la présentation

Si vous avez suivi ce parcours, vous savez maintenant exactement ce que fait Client.java, vous pouvez le lancer et vérifier votre Patient et votre Observation nouvellement créés sur votre serveur.

Pour le lancer, ouvrez un terminal VSCode et entrez :

dotnet run

Vous devriez voir des informations sur le Patient créé et son observation.

Si vous utilisez un serveur Intersystems, allez à API Deployement, autorisez-vous avec la clé api et d'ici vous pouvez OBTENIR par id le patient et l'observation que nous venons de créer.

6. Comment commencer le codage

Ce référentiel est prêt à être codé dans VSCode avec les plugins InterSystems. Ouvrez Client.java pour commencer à coder ou utiliser l'autocomplétion.

7. Ce qu'il y a dans le référentiel

7.1. Dockerfile

Un dockerfile pour créer un dot net env pour que vous puissiez travailler.
Utilisez docker build . pour construire et rouvrir votre fichier dans le conteneur pour travailler à l'intérieur de celui-ci.

7.2. .vscode/settings.json

Fichier de paramètres.

7.3. .vscode/launch.json

Fichier de configuration si vous voulez déboguer

0
0 220
Article Sarah Schlegel · Sept 6, 2022 8m read

Bonjour la communauté !

Cet article vise à donner un aperçu des webservices REST JSON développés pour TrakCare.

Ces webservices ont été développés dans le but de permettre aux utilisateurs d’accéder aux données de TrakCare depuis l’extérieur, notamment via des applications externes.

Ils sont développés en REST avec ObjectScript, et permettent d’accéder aux données via quatre modes :

0
0 414
Article Iryna Mykhailova · Juil 1, 2022 10m read

Dans le premier article de cette série, nous avons vu comment lire un "gros" volume de données dans le corps brut d'une méthode HTTP POST et l'enregistrer dans une base de données en tant que propriété de flux d'une classe. Le deuxième article explique comment enregistrer des fichiers et leurs noms dans un format JSON.

Examinons maintenant de plus près l'idée d'envoyer des fichiers volumineux par parties au niveau du serveur. Il existe plusieurs approches que nous pouvons utiliser pour y parvenir. Cet article traite de l'utilisation de l'en-tête Transfer-Encoding pour indiquer un transfert par blocs. La spécification HTTP/1.1 a introduit l'en-tête Transfer-Encoding, et RFC 7230, section 4.1 l'a décrit, mais il n'est pas mentionné dans la spécification HTTP/2. 

Transfer-Encoding 

L'objectif de l'en-tête Transfer-Encoding est de spécifier la forme d'encodage utilisée pour transférer le corps de la charge utile à l'utilisateur en toute sécurité. Vous utilisez cet en-tête principalement pour délimiter avec précision une charge utile générée dynamiquement et pour distinguer les codages de charge utile pour l'efficacité du transport ou la sécurité des caractéristiques de la ressource sélectionnée.  

Vous pouvez utiliser les valeurs suivantes dans cet en-tête :  

  • Chunked (en blocs)  
  • Compress (compresser)  
  • Deflate (dégonfler)  
  • gzip 

Le codage de transfert est égal à l'encodage par blocs  

Lorsque vous définissez le codage de transfert par blocs, le corps du message est constitué d'un nombre non spécifié de blocs réguliers, d'un bloc de fin, d'un trailer et du caractère retour chariot (CRLF).  

Chaque partie commence par une dimension de bloc représentée par un nombre hexadécimal, suivie d'une extension facultative et d'un CRLF. Ensuite vient le corps du bloc avec CRLF à la fin. Les extensions contiennent les métadonnées du bloc. Par exemple, les métadonnées peuvent inclure une signature, un hachage, des informations de contrôle à mi-message, etc. Le bloc de fin de message est un bloc régulier de longueur nulle. Un trailer, qui consiste en des champs d'en-tête (éventuellement vides), suit le block de fin.  

Pour rendre tout cela plus facile à imaginer, voici la structure d'un message avec Transfer-Encoding = par blocs : 

Voici un exemple de message court et découpé en blocs : 

13\r\n
Transferring Files \r\n
4\r\n
on\r\n
1A\r\n
community.intersystems.com
0\r\n
\r\n 

Ce corps de message se compose de trois blocs significatifs. Le premier bloc a une longueur de dix-neuf octets, le deuxième en a quatre et le troisième en a vingt-six. Vous pouvez constater que les CRLF de fin qui marquent la fin des blocs ne sont pas pris en compte dans la taille du bloc. Mais si vous utilisez le CRLF comme marqueur de fin de ligne (EOL), alors le CRLF est compté comme une partie du message et prend deux octets. Le message décodé ressemble à ceci : 

Transferring Files on
community.intersystems.com 

Formation de messages groupés dans IRIS 

Pour ce tutoriel, nous allons utiliser la méthode sur le serveur créé dans le premier article. Cela signifie que nous allons envoyer le contenu du fichier directement dans le corps de la méthode POST. Comme nous envoyons le contenu du fichier dans le corps, nous envoyons le POST à http://webserver/RestTransfer/file. 

Maintenant, voyons comment nous pouvons former un message en bloc dans IRIS. Comme indiqué dans Envoi de requêtes HTTP, à la section Envoi d'une requête par blocs, vous pouvez envoyer une requête HTTP par blocs si vous utilisez HTTP/1.1. La meilleure partie de ce processus est que %Net.HttpRequest calcule automatiquement la longueur du contenu de l'ensemble du corps du message côté serveur, de sorte qu'il n'est pas nécessaire de modifier le côté serveur du tout. Par conséquent, pour envoyer une requête en bloc, vous devez suivre ces étapes dans le client uniquement. 

La première étape consiste à créer une sous-classe de %Net.ChunkedWriter et à implémenter la méthode OutputStream. Cette méthode doit recevoir un flux de données, l'examiner, décider s'il faut le diviser en parties ou non, et comment le diviser, et invoquer les méthodes héritées de la classe pour écrire la sortie. Dans notre cas, nous appellerons la classe RestTransfer.ChunkedWriter. 

Ensuite, dans la méthode côté client responsable de l'envoi des données (appelée ici "SendFileChunked"), vous devez créer une instance de la classe RestTransfer.ChunkedWriter et la remplir avec les données demandées que vous souhaitez envoyer. Comme nous envoyons des fichiers, nous ferons tout le travail dans la classe RestTransfer.ChunkedWriter. Nous ajoutons une propriété nommée Filename As %String et un paramètre nommé “MAXSIZEOFCHUNK = 10000.” Bien sûr, vous pouvez décider de définir une dimension maximale autorisée pour le bloc en tant que propriété et la définir pour chaque fichier ou message. 

Enfin, définissez la propriété EntityBody de %Net.HttpRequest comme étant égale à l'instance créée de la classe RestTransfer.ChunkedWriter. 

Ces étapes correspondent simplement au nouveau code que vous devez écrire et remplacer dans votre méthode existante qui envoie des fichiers à un serveur. 

La méthode ressemble à ceci : 

ClassMethod SendFileChunked(aFileName) As %Status
{
  Set sc = $$$OK   
  Set request = ..GetLink()
  set cw = ##class(RestTransfer.ChunkedWriter).%New()
  set cw.Filename = aFileName
  set request.EntityBody = cw
  set sc = request.Post("/RestTransfer/file")  
  Quit:$System.Status.IsError(sc) sc
  Set response=request.HttpResponse
  do response.OutputToDevice()
  Quit sc
} 

La classe %Net.ChunkedWriter est une classe de flux abstraite qui fournit une interface et possède quelques méthodes et propriétés implémentées. Ici, nous utilisons les propriétés et méthodes suivantes : 

  • La propriété TranslateTable en tant que %String force la traduction automatique des blocs lors de leur écriture dans le flux de sortie (EntityBody). Nous nous attendons à recevoir des données brutes, nous devons donc définir TranslateTable sur "RAW". 
  • La méthode OutputStream est une méthode abstraite surchargée par une sous-classe pour faire tout le découpage en blocs. 
  • La méthode WriteSingleChunk(buffer As %String) écrit l'en-tête HTTP Content-Length suivi du corps de l'entité en un seul bloc. Nous vérifions si la dimension du fichier est inférieure à la méthode MAXSIZEOFCHUNK, auquel cas, nous utilisons cette méthode. 
  • La méthode WriteFirstChunk(buffer As %String) écrit l'en-tête Transfer-Encoding suivi du premier bloc. Il doit toujours être présent. Il peut être suivi de zéro ou plusieurs appels pour écrire d'autres blocs, puis d'un appel obligatoire pour écrire le dernier bloc avec la chaîne vide. Nous vérifions que la longueur du fichier est supérieure à la méthode MAXSIZEOFCHUNK et appelons cette méthode. 
  • La méthode WriteChunk(buffer As %String) écrit des blocs conséquents. Vérifiez si le reste du fichier après le premier bloc est toujours supérieur à MAXSIZEOFCHUNK puis utilisez cette méthode pour envoyer les données. Nous continuons à le faire jusqu'à ce que la taille de la dernière partie du fichier soit inférieure à MAXSIZEOFCHUNK.  
  • La méthode WriteLastChunk(buffer As %String) écrit le dernier bloc suivi d'un bloc de longueur zéro pour marquer la fin des données.  

Sur la base de tout ce qui précède, notre classe RestTransfer.ChunkedWriter est comme suit : 

Class RestTransfer.ChunkedWriter Extends %Net.ChunkedWriter
{
  Parameter MAXSIZEOFCHUNK = 10000;
  Property Filename As %String;
  Method OutputStream()
  {
    set ..TranslateTable = "RAW"
    set cTime = $zdatetime($Now(), 8, 1)  
    set fStream = ##class(%Stream.FileBinary).%New()
    set fStream.Filename = ..Filename
    set size = fStream.Size  
    if size &lt; ..#MAXSIZEOFCHUNK {  
      set buf = fStream.Read(.size, .st)
      if $$$ISERR(st)
      {
        THROW st
      } else {
        set ^log(cTime, ..Filename) = size
        do ..WriteSingleChunk(buf)
      }
    } else {
      set ^log(cTime, ..Filename, 0) = size
      set len = ..#MAXSIZEOFCHUNK
      set buf = fStream.Read(.len, .st)
      if $$$ISERR(st)
      {
        THROW st
      } else {
        set ^log(cTime, ..Filename, 1) = len
        do ..WriteFirstChunk(buf)
      }  
      set i = 2
      While 'fStream.AtEnd {  
        set len = ..#MAXSIZEOFCHUNK
        set temp = fStream.Read(.len, .sc)
    if len&lt;..#MAXSIZEOFCHUNK  
    {
      do ..WriteLastChunk(temp)    
    } else {
          do ..WriteChunk(temp)
        }
        set ^log(cTime, ..Filename, i) = len
        set i = $increment(i)
      }  
    }
  }
} 

Pour voir comment ces méthodes divisent le fichier en parties, nous ajoutons une globale ^log avec la structure suivante : 

//for transfer in a single chunk
^log(time, filename) = size_of_the_file
//pour un transfert en plusieurs blocs
^log(time, filename, 0) = size_of_the_file
^log(time, filename, idx) = size_of_the_idx’s_chunk 

Maintenant que la programmation est terminée, voyons comment ces trois approches fonctionnent pour différents fichiers. Nous écrivons une simple méthode de classe pour faire des appels au serveur : 

ClassMethod Run()
{
  // D'abord, je supprime les globales.  
  kill ^RestTransfer.FileDescD
  kill ^RestTransfer.FileDescS
  // Ensuite, je forme une liste des fichiers que je veux envoyer         
  for filename = "D:\Downloads\wiresharkOutput.txt", // 856 bytes
     "D:\Downloads\wiresharkOutput.pdf", // 60 134 bytes
     "D:\Downloads\Wireshark-win64-3.4.7.exe", // 71 354 272 bytes
     "D:\Downloads\IRIS_Community-2021.1.0.215.0-win_x64.exe" //542 370 224 bytes
  {  
    write !, !, filename, !, !
    // Et je lance les trois méthodes d'envoi de données au serveur.
    set resp1=##class(RestTransfer.Client).SendFileChunked(filename)
    if $$$ISERR(resp1) do $System.OBJ.DisplayError(resp1)
    set resp1=##class(RestTransfer.Client).SendFile(filename)
    if $$$ISERR(resp1) do $System.OBJ.DisplayError(resp1)  
    set resp1=##class(RestTransfer.Client).SendFileDirect(filename)
    if $$$ISERR(resp1) do $System.OBJ.DisplayError(resp1)
  }
} 

Après avoir exécuté la méthode de classe Run, dans la sortie des trois premiers fichiers, l'état était correct. Mais pour le dernier fichier, alors que les premier et dernier appels ont fonctionné, celui du milieu a renvoyé une erreur : 5922, Dépassement de délai en attente de réponse. Si nous examinons notre méthode des globales, nous voyons que le code n'a pas enregistré le onzième fichier. Cela signifie que ##class(RestTransfer.Client).SendFile(filename) a échoué - ou pour être précis, la méthode qui déballe les données de JSON n'a pas réussi.  

Maintenant, si nous regardons nos flux, nous voyons que tous les fichiers enregistrés avec succès ont des dimensions correctes. 

Si nous regardons la globale ^log, nous voyons combien de blocs le code a créé pour chaque fichier : 

Vous aimeriez probablement voir le corps des messages réels. Eduard Lebedyuk a suggéré dans son article Debugging Web qu'il est possible d'utiliser CSP Gateway Logging and Tracing. 

Si nous examinons le journal des événements pour le deuxième fichier découpé en blocs, nous constatons que la valeur de l'en-tête Transfer-Encoding est effectivement "découpé en blocs". Malheureusement, le serveur a déjà collé le message ensemble, donc nous ne voyons pas le découpage en blocks réel. 

L'utilisation de la fonction Trace ne montre pas beaucoup plus d'informations, mais elle permet de clarifier la présence d'un écart entre l'avant-dernière et la dernière demande. 

Pour voir les parties réelles des messages, nous copions le client sur un autre ordinateur pour utiliser un renifleur. Ici, nous avons choisi d'utiliser Wireshark car il est gratuit et possède les fonctions nécessaires. Pour mieux vous montrer comment le code divise le fichier en blocs, nous pouvons changer la valeur de MAXSIZEOFCHUNK à 100 et choisir d'envoyer un petit fichier. Ainsi, nous pouvons maintenant voir le résultat suivant : 

Nous constatons que la longueur de tous les blocs, sauf les deux derniers, est égale à 64 en HEX (100 en DEC), que le dernier bloc contenant des données est égal à 21 DEC (15 en HEX) et que la dimension du dernier bloc est égale à zéro. Tout semble correct et conforme à la spécification. La longueur totale du fichier est égale à 421 (4x100+1x21), ce que nous pouvons également voir dans les globales : 

Coonclusion 

Dans l'ensemble, nous pouvons constater que cette approche fonctionne et permet d'envoyer sans problème de gros fichiers au serveur. En outre, si vous envoyez de grandes quantités de données à un client, vous pouvez vous familiariser avec Fonctionnement et configuration de la passerelle Web, section Paramètres de configuration du chemin d'application, paramètre Notification de la dimension de la réponse. Celui-ci spécifie le comportement de la passerelle Web lors de l'envoi de grandes quantités de données en fonction de la version de HTTP utilisée. 

Le code de cette approche est ajouté à la version précédente de cet exemple sur GitHub et InterSystems Open Exchange

À propos de l'envoi de fichiers en blocs, il est également possible d'utiliser l'en-tête Content-Range avec ou sans l'en-tête Transfer-Encoding pour indiquer quelle partie exacte des données est transférée. En outre, vous pouvez utiliser un tout nouveau concept de flux disponible avec la spécification HTTP/2. 

Comme toujours, si vous avez des questions ou des suggestions, n'hésitez pas à les écrire dans la section des commentaires.

0
0 191
Article Iryna Mykhailova · Juin 29, 2022 8m read

Dans le premier article de cette série, nous avons vu comment lire un "gros" volume de données dans le corps brut d'une méthode HTTP POST et l'enregistrer dans une base de données en tant que propriété de flux d'une classe. Voyons maintenant comment enregistrer de telles données et métadonnées au format JSON.

Malheureusement, Client REST avancé (Advanced REST Client) ne permet pas de composer des objets JSON avec des données binaires comme valeur d'une clé (ou peut-être que je n'ai simplement pas trouvé comment le faire), j'ai donc décidé d'écrire un client simple en ObjectScript pour envoyer des données au serveur.

J'ai créé une nouvelle classe appelée RestTransfer.Client et je lui ai ajouté les paramètres Server = "localhost" and Port = 52773 pour décrire mon serveur web. Et j'ai créé une méthode de classe GetLink dans laquelle je crée une nouvelle instance de la classe %Net.HttpRequest et je configure ses propriétés avec les paramètres susmentionnés. 

Pour envoyer réellement la requête POST à un serveur, j'ai créé une méthode de classe SendFileDirect, qui lit le fichier que je souhaite envoyer au serveur et écrit son contenu dans la propriété EntityBody de ma requête. 

Ensuite, je lance la méthode Post("/RestTransfer/file") et, si elle se termine avec succès, la réponse se trouvera dans la propriété HttpResponse

Pour voir le résultat renvoyé par le serveur, je lance la méthode OutputToDevice de la réponse. 

Voici la méthode de classe :

Class RestTransfer.Client
{
  Parameter Server = "localhost";
  Parameter Port = 52773;
  Parameter Https = 0;

  ClassMethod GetLink() As %Net.HttpRequest
  {
      set request = ##class(%Net.HttpRequest).%New()       
      set request.Server = ..#Server
      set request.Port = ..#Port
      set request.ContentType = "application/octet-stream" 
      quit request
  }

  ClassMethod SendFileDirect(aFileName) As %Status
  {
    set sc = $$$OK 
    set request = ..GetLink() 
    set s = ##class(%Stream.FileBinary).%New()
    set s.Filename = aFileName
    While 's.AtEnd 
    {
      do request.EntityBody.Write(s.Read(.len, .sc))
      Quit:$System.Status.IsError(sc)
    }
    Quit:$System.Status.IsError(sc)

    set sc = request.Post("/RestTransfer/file")                            
    Quit:$System.Status.IsError(sc)
    set response=request.HttpResponse
    do response.OutputToDevice()
    Quit sc
  }
}

Nous pouvons lancer cette méthode pour transférer les mêmes fichiers que ceux utilisés dans l'article précédent :

 do ##class(RestTransfer.Client).SendFileDirect("D:\Downloads \2020_1012_114732_020.JPG")
 do ##class(RestTransfer.Client).SendFileDirect("D:\Downloads\Archive.xml")
 do ##class(RestTransfer.Client).SendFileDirect("D:\Downloads\arc-setup.exe")

Nous allons voir les réponses du serveur pour chaque fichier :

HTTP/1.1 200 OK
CACHE-CONTROL: no-cache
CONTENT-LENGTH: 15
CONTENT-TYPE: text/html; charset=utf-8
DATE: Thu, 05 Nov 2020 15:13:23 GMT
EXPIRES: Thu, 29 Oct 1998 17:04:19 GMT
PRAGMA: no-cache
SERVER: Apache
{"Status":"OK"}

Et nous aurons les mêmes données dans les globales que la fois précédente :

Cela signifie que notre client fonctionne et que nous pouvons maintenant l'étendre pour envoyer du JSON au serveur.

Présentation de JSON

JSON est un format léger de stockage et de transport de données, dans lequel les données se présentent sous la forme de paires nom/valeur séparées par des virgules. Dans ce cas, le JSON ressemblera à quelque chose comme ceci :

{
  "Name": "test.txt",
  "File": "Hello, world!"
}

IRIS dispose de plusieurs classes qui vous permettent de travailler avec JSON. Je vais utiliser les suivantes :

  • %JSON.Adaptor permet de sérialiser un objet compatible JSON en un document JSON et vice versa.
  • %Library.DynamicObject permet d'assembler dynamiquement des données qui peuvent être facilement transmises entre un client Web et un serveur.
  • Pour en savoir plus sur ces classes et d'autres qui vous permettent de travailler avec JSON, consultez la section Référence des classes de la documentation et des manuels Application Development Guides.

    Le principal avantage de ces classes est de permettre de créer facilement du JSON à partir d'un objet IRIS ou de créer un objet à partir de JSON. 

    Je vais montrer deux approches de la façon dont vous pouvez travailler avec JSON pour envoyer/recevoir des fichiers, à savoir une qui utilise %Library.DynamicObject pour créer un objet du côté client et le sérialiser en un document JSON et %JSON.Adaptor pour le désérialiser en RestTransfer.FileDesc du côté serveur, et une autre où je forme manuellement une chaîne à envoyer et à analyser du côté serveur. Pour rendre cela plus lisible, je vais créer deux méthodes différentes sur le serveur pour chaque approche.

    Pour l'instant, concentrons-nous sur la première méthode. 

    Création de JSON avec IRIS

    Pour commencer, héritons de la classe RestTransfer.FileDesc de %JSON.Adaptor. Cela nous permet d'utiliser la méthode d'instance %JSONImport() pour désérialiser un document JSON directement dans un objet. Maintenant, cette classe aura l'aspect suivant : 

    Class RestTransfer.FileDesc Extends (%Persistent, %JSON.Adaptor)
    {
      Property File As %Stream.GlobalBinary;
      Property Name As %String;
    }
    

    Ajoutons un nouvel itinéraire à UrlMap dans le service broker de la classe :

    <Route Url="/jsons" Method="POST" Call="InsertJSONSmall"/>
    

    Ceci spécifie que, lorsque le service reçoit une commande POST avec l'URL /RestTransfer/jsons, il doit appeler la méthode de classe InsertJSONSmall

    Cette méthode s'attend à recevoir un texte formaté en JSON avec deux paires clé-valeur où le contenu du fichier sera inférieur à la longueur maximale d'une chaîne. Je vais définir les propriétés de l'objet Name and File, le sauvegarder dans la base de données, et renvoyer l'état et un message formaté en JSON indiquant un succès ou une erreur.  

    Voici la méthode de classe.

    ClassMethod InsertJSONSmall() As %Status
    {
      Set result={}
      Set st=0 
    
      set f = ##class(RestTransfer.FileDesc).%New()  
      if (f = $$$NULLOREF) {    
       do result.%Set("Message", "Couldn't create an instance of class") 
      } else {  
         set st = f.%JSONImport(%request.Content) 
         If $$$ISOK(st) {
            set st = f.%Save()
            If $$$ISOK(st) {      
               do result.%Set("Status","OK")        
            } else {           
               do result.%Set("Message",$system.Status.GetOneErrorText(st)) 
            }
         } else {         
            do result.%Set("Message",$system.Status.GetOneErrorText(st)) 
         }
      } 
      write result.%ToJSON()
      Quit st
    }
    

    Que fait cette méthode ? Elle récupère la propriété Content de l'objet %request et, à l'aide de la méthode %JSONImport héritée de la classe %JSON.Adaptor, la convertit en un objet RestTransfer.FileDesc .

    S'il y a des problèmes pendant la conversion, nous formons un JSON avec la description de l'erreur :

    {"Message",$system.Status.GetOneErrorText(st)}
    

    Autrement, nous enregistrons l'objet. S'il est enregistré sans problème, nous créons un message JSON {"Status","OK"}. Sinon, un message JSON avec la description de l'erreur. 

    Enfin, nous écrivons le JSON dans une réponse et renvoyons l'état st.

    Du côté client, j'ai ajouté une nouvelle méthode de classe SendFile pour envoyer des fichiers au serveur. Le code est très similaire à celui de SendFileDirect mais, au lieu d'écrire le contenu du fichier directement dans la propriété EntityBody, je crée une nouvelle instance de la classe %Library.DynamicObject, je fixe sa propriété Name égale au nom du fichier, et je code et copie le contenu du fichier dans la propriété File

    Pour encoder le contenu du fichier, j'utilise la méthode Base64EncodeStream() proposée parVitaliy Serdtsev.

    Ensuite, en utilisant la méthode %ToJSON de la classe %Library.DynamicObject, je sérialise mon objet en un document JSON, je l'écris dans le corps de la requête et je lance la méthode Post("/RestTransfer/jsons")

    Voici la méthode de classe :

    ClassMethod SendFile(aFileName) As %Status
    {
      Set sc = $$$OK
      Set request = ..GetLink() 
      set s = ##class(%Stream.FileBinary).%New()
      set s.Filename = aFileName
      set p = {}
      set p.Name = s.Filename 
      set sc = ..Base64EncodeStream(s, .t)
      Quit:$System.Status.IsError(sc)
    
      While 't.AtEnd {
        set p.File = p.File_t.Read(.len, .sc)
        Quit:$System.Status.IsError(sc)
      }  
      do p.%ToJSON(request.EntityBody)
      Quit:$System.Status.IsError(sc)
    
      set sc = request.Post("/RestTransfer/jsons")                                    
      Quit:$System.Status.IsError(sc)
    
      Set response=request.HttpResponse
      do response.OutputToDevice()
      Quit sc
    }
    

    Nous lançons cette méthode et la méthode SendFileDirect pour transférer plusieurs petits fichiers :

     do ##class(RestTransfer.Client).SendFile("D:\Downloads\Outline Template.pdf")      
     do ##class(RestTransfer.Client).SendFileDirect("D:\Downloads\Outline Template.pdf")    
     do ##class(RestTransfer.Client).SendFile("D:\Downloads\pic3.png")         
     do ##class(RestTransfer.Client).SendFileDirect("D:\Downloads\pic3.png")         
     do ##class(RestTransfer.Client).SendFile("D:\Downloads\Archive (1).xml")      
     do ##class(RestTransfer.Client).SendFileDirect("D:\Downloads\Archive (1).xml")   
    

    Nous obtiendrons les résultats suivants :

    Comme vous pouvez le constater, les longueurs sont les mêmes et, si nous enregistrons ces fichiers sur un disque dur, nous verrons qu'ils sont toujours les mêmes.

    Formation manuelle de JSON

    Maintenant, nous allons nous concentrer sur la deuxième approche, qui consiste à former le JSON manuellement. Pour ce faire, ajoutons un nouveau itinéraire à UrlMap dans le service broker de classe :

    <Route Url="/json" Method="POST" Call="InsertJSON"/>
    

    Cela spécifie que lorsque le service reçoit une commande POST avec l'URL /RestTransfer/json, celui-ci doit lancer la méthode de classe InsertJSON. Dans cette méthode, je m'attends à recevoir le même JSON, mais je n'impose aucune limite à la longueur du fichier. Voici la méthode de classe :

    ClassMethod InsertJSON() As %Status
    {
      Set result={}
      Set st=0 
    
      set t = ##class(%Stream.TmpBinary).%New()
      While '%request.Content.AtEnd 
      {        
        set len = 32000
        set temp = %request.Content.Read(.len, .sc)
        set:len<32000 temp = $extract(temp,1,*-2)  
        set st = t.Write($ZCONVERT(temp, "I", "RAW"))                                                
      }
      do t.Rewind()
    
      set f = ##class(RestTransfer.FileDesc).%New()  
      if (f = $$$NULLOREF) 
      {     
        do result.%Set("Message", "Couldn't create an instance of class") 
      } else {
        set str = t.Read()
        set pos = $LOCATE(str,""",")
        set f.Name = $extract(str, 10, pos-1)
        do f.File.Write($extract(str, pos+11, *))
        While 't.AtEnd {       
          do f.File.Write(t.Read(.len, .sc))
        }
    
        If $$$ISOK(st) 
        {
          set st = f.%Save()
          If $$$ISOK(st) 
          {         
            do result.%Set("Status","OK")         
          } else {                      
            do result.%Set("Message",$system.Status.GetOneErrorText(st)) 
          }
        } else {         
           do result.%Set("Message",$system.Status.GetOneErrorText(st)) 
        }    
      }
      write result.%ToJSON()
      Quit st
    }
    

    Comment cette méthode fonctionne-t-elle ? Tout d'abord, je crée une nouvelle instance d'un flux temporaire %Stream.TmpBinary, et j'y copie le contenu de la requête. 

    Comme je vais l'utiliser comme une chaîne de caractères, je dois me débarrasser d'un guillemet de fin de chaîne (") et d'une accolade (}). Pour ce faire, dans le dernier segment du flux, je laisse de côté les deux derniers caractères : $extract(temp,1,*-2).

    En même temps, je convertis ma chaîne en utilisant le tableau de traduction "RAW" : $ZCONVERT(temp, "I", "RAW").

    Ensuite, je crée une nouvelle instance de ma classe RestTransfer.FileDesc et je fais les mêmes vérifications que dans les autres méthodes. Je connais la structure de ma chaîne de caractères, donc j'extrais le nom du fichier et le fichier lui-même et je les place dans les propriétés correspondantes.

    Du côté client, j'ai modifié la méthode SendFile de ma classe et, avant de former le JSON, je vérifie la longueur du fichier. S'il est inférieur à 2 000 000 d'octets (limite apparente pour la méthode %JSONImport), je lance Post("/RestTransfer/jsons"). Sinon, je lance Post("/RestTransfer/json")

    Now the method looks like this:

    ClassMethod SendFile(aFileName) As %Status
    {
      Set sc = $$$OK
      Set request = ..GetLink()
      set s = ##class(%Stream.FileBinary).%New()
      set s.Filename = aFileName
      if s.Size > 2000000 //3641144 max length of the string in IRIS
      {   
        do request.EntityBody.Write("{""Name"":"""_s.Filename_""", ""File"":""")      
        While 's.AtEnd 
        {       
          set temp = s.Read(.len, .sc)
          do request.EntityBody.Write($ZCONVERT(temp, "O", "RAW"))   
          Quit:$System.Status.IsError(sc)
        }
    
        do request.EntityBody.Write("""}")  
        set sc = request.Post("/RestTransfer/json")                           
      } else {
        set p = {}
        set p.Name = s.Filename
        set sc = ..Base64EncodeStream(s, .t)
        Quit:$System.Status.IsError(sc)
        While 't.AtEnd {
            set p.File = p.File_t.Read(.len, .sc)
            Quit:$System.Status.IsError(sc)
        } 
        do p.%ToJSON(request.EntityBody)
        Quit:$System.Status.IsError(sc)
        set sc = request.Post("/RestTransfer/jsons")                                      
      }
    
      Quit:$System.Status.IsError(sc)
      Set response=request.HttpResponse
      do response.OutputToDevice()
      Quit sc
    }
    

    Pour lancer la deuxième méthode, je crée manuellement la chaîne avec JSON. Et pour transférer le fichier lui-même, côté client, je convertis également le contenu en utilisant le tableau de conversion "RAW" : ($ZCONVERT(temp, "O", "RAW").

    Nous lançons maintenant cette méthode et la méthode SendFileDirect pour transférer plusieurs fichiers de dimensions différentes :

     do ##class(RestTransfer.Client).SendFile(“D:\Downloads\pic3.png”)         
     do ##class(RestTransfer.Client).SendFileDirect(“D:\Downloads\pic3.png”)         
     do ##class(RestTransfer.Client).SendFile(“D:\Downloads\Archive (1).xml”)      
     do ##class(RestTransfer.Client).SendFileDirect(“D:\Downloads\Archive (1).xml”)    
     do ##class(RestTransfer.Client).SendFile(“D:\Downloads\Imagine Dragons-Thunder.mp3”)         
     do ##class(RestTransfer.Client).SendFileDirect(“D:\Downloads\Imagine Dragons-Thunder.mp3”)
     do ##class(RestTransfer.Client).SendFile(“D:\Downloads\ffmpeg-win-2.2.2.exe”)   
     do ##class(RestTransfer.Client).SendFileDirect(“D:\Downloads\ffmpeg-win-2.2.2.exe”)
    

    Nous obtiendrons les résultats suivants :

    Nous pouvons voir que les longueurs sont les mêmes, donc tout fonctionne comme prévu.

    Conclusion

    Ici encore, vous pouvez en savoir plus sur la création de services dans la documentation. Le code exemplaire pour les deux approches se trouve sur GitHub et InterSystems Open Exchange

    Quant au transfert des résultats du module TWAIN dont nous avons parlé dans le premier article de cette série, il dépend de la taille des données que vous recevrez. Si la résolution maximale est limitée et que les fichiers sont de petite taille, vous pouvez utiliser les classes de système %JSON.Adaptor and %Library.DynamicObject. Mais pour plus de sécurité, il est préférable d'envoyer un fichier directement dans le corps d'une commande POST ou de former le JSON manuellement. Il peut également être judicieux d'utiliser des requêtes par plage et de diviser les gros fichiers en plusieurs parties, de les envoyer séparément et de les consolider en un seul fichier du côté serveur.

    En tout cas, si vous avez des questions ou des suggestions concernant l'une ou l'autre, n'hésitez pas à les écrire dans la section des commentaires.

    0
    1 112
    Article Iryna Mykhailova · Juin 27, 2022 7m read

    Une question a été posée dans la communauté des développeurs d'InterSystems concernant la possibilité de créer une interface TWAIN pour une application Caché. Il y a eu plusieurs suggestions intéressantes sur la façon d'obtenir des données d'un périphérique d'acquisition d'images sur un client Web vers un serveur, puis de stocker ces données dans une base de données 

    Toutefois, pour mettre en œuvre l'une de ces suggestions, vous devez être en mesure de transférer des données d'un client Web vers un serveur de base de données et de stocker les données reçues dans une propriété de classe (ou une cellule de tableau, comme c'était le cas dans la question). Cette technique peut être utile non seulement pour transférer des données d'images provenant d'un périphérique TWAIN, mais aussi pour d'autres tâches telles que l'organisation d'une archive de fichiers, d'un partage d'images, etc. 

    Ainsi, l'objectif principal de cet article est de montrer comment écrire un service RESTful pour obtenir des données du corps d'une commande HTTP POST, soit à l'état brut, soit enveloppées dans une structure JSON.

    Principes de base de REST

    Avant d'entrer dans les détails, permettez-moi de dire quelques mots sur REST en général et sur la façon dont les services RESTful sont créés dans IRIS.

    Le transfert d'état représentationnel (REST) est un style architectural pour les systèmes hypermédia distribués. L'abstraction clé de l'information dans REST est une ressource qui a son propre identifiant et qui peut être représentée dans un format JSON, XML ou un autre format connu à la fois du serveur et du client. 

    En général, HTTP est utilisé pour transférer des données entre le client et le serveur. Chaque opération CRUD (création, lecture, mise à jour, suppression) a sa propre méthode HTTP (POST, GET, PUT, DELETE), tandis que chaque ressource ou collection de ressources a son propre URI.

    Dans cet article, je n'utiliserai que la méthode POST pour insérer une nouvelle valeur dans la base de données, et j'ai donc besoin de connaître ses contraintes. 

    Selon la spécification IETF RFC7231 4.3.3 Post POST n'a pas de limite quant à la taille des données stockées dans son corps. Mais différents serveurs web et navigateurs imposent leurs propres limites, généralement de 1 Mo à 2 Go. Par exemple, Apache autorise un maximum de 2 Go. Dans tous les cas, si vous devez envoyer un fichier de 2 Go, vous devriez peut-être reconsidérer votre approche.

    Exigences pour un service RESTful dans IRS

    Dans IRIS, pour mettre en œuvre un service RESTful, vous devez :

  • Créer une classe broker qui étend la classe abstraite %CSP.REST. Celle-ci, à son tour, étend %CSP.Page, ce qui permet d'accéder à différentes méthodes, paramètres et objets utiles, en particulier %request).
  • Spécifier UrlMap pour définir les itinéraires.
  • En option, définir le paramètre UseSession pour préciser si chaque appel REST est exécuté dans sa propre session Web ou s'il partage une seule session avec d'autres appels REST.
  • Fournir des méthodes de classe pour effectuer les opérations définies dans les itinéraires
  • Définissez l'application web CSP et spécifiez sa sécurité sur la page de l'application web (Administration système > Sécurité > Applications > Applications web), où la classe Dispatch doit contenir le nom de la classe utilisateur et Name, la première partie de l'URL pour l'appel REST.
  • En général, il existe plusieurs façons d'envoyer de gros volumes de données (fichiers) et leurs métadonnées du client au serveur, par exemple :

  • Coder le fichier et les métadonnées en base64 et ajouter des frais de traitement au serveur et au client pour le codage/décodage.
  • Envoyer d'abord le fichier et renvoyez un ID au client, qui envoie ensuite les métadonnées contenant l'ID. Le serveur réassocie le fichier et les métadonnées.
  • Envoyer d'abord le fichier et renvoyez un ID au client, qui envoie ensuite le fichier contenant l'ID. Le serveur réassocie le fichier et les métadonnées.
  • Dans ce premier article, je vais essentiellement utiliser la deuxième approche, mais sans renvoyer l'ID au client et en ajoutant les métadonnées (le nom du fichier à stocker comme une autre propriété) car cette tâche n'a rien d'exceptionnel. Dans un deuxième article, j'utiliserai la première approche, mais j'empaqueterai mon fichier et les métadonnées (le nom du fichier) dans une structure JSON avant de l'envoyer au serveur.

    Mise en œuvre du service RESTFul

    Passons maintenant aux détails. Tout d'abord, définissons la classe, dont nous allons définir les propriétés :

    Class RestTransfer.FileDesc Extends %Persistent
    {
      Property File As %Stream.GlobalBinary;
      Property Name As %String;
    }
    

    Bien sûr, vous aurez généralement plus de métadonnées pour accompagner le fichier, mais cela devrait suffire pour nos besoins.

    Ensuite, nous devons créer un service broker de classe, que nous développerons plus tard avec des itinéraires et des méthodes :

    Class RestTransfer.Broker Extends %CSP.REST
    {
      XData UrlMap
      {
        <Routes>
        </Routes>
      }
    }
    

    Et enfin, pour la configuration préliminaire, nous devons spécifier cette application dans une liste d'applications web :

    Maintenant que la configuration préliminaire est faite, nous pouvons écrire des méthodes pour enregistrer un fichier reçu d'un client REST (j'utiliserai le client Advanced REST Client) dans une base de données en tant que propriété File d'une instance de la classe RestTransfer.FileDesc.

    Nous allons commencer par travailler avec des informations stockées sous forme de données brutes dans le corps de la méthode POST. Tout d'abord, ajoutons un nouvel itinéraire à UrlMap :

    <Route Url="/file" Method="POST" Call="InsertFileContents"/>
    

    Cet itinéraire spécifie que lorsque le service reçoit une commande POST avec l'URL /RestTransfer/file, il doit lancer la méthode de classe InsertFileContents. Pour faciliter les choses, à l'intérieur de cette méthode, je vais créer une nouvelle instance de la classe RestTransfer.FileDesc et définir sa propriété fichier File sur les données reçues. Cela renvoie le statut et un message formaté en JSON indiquant le succès ou l'erreur. Voici la méthode de classe :

    ClassMethod InsertFileContents() As %Status
    {
      Set result={}
      Set st=0    
      set f = ##class(RestTransfer.FileDesc).%New()
      if (f = $$$NULLOREF) {
        do result.%Set("Message","Couldn't create an instance of the class")
      } else {
        set st = f.File.CopyFrom(%request.Content)
        If $$$ISOK(st) {
          set st = f.%Save()
          If $$$ISOK(st) {
            do result.%Set("Status","OK")
          } else {
            do result.%Set("Message",$system.Status.GetOneErrorText(st))
          }
        } else {
          do result.%Set("Message",$system.Status.GetOneErrorText(st))
        }
      }
      write result.%ToJSON()
      Quit st
    }
    

    Tout d'abord, une nouvelle instance de la classe RestTransfer.FileDesc est créée et on vérifie qu'elle a été créée avec succès et que nous avons un OREF. Si l'objet n'a pas pu être créé, nous formons une structure JSON : 

    {"Message", "Couldn't create an instance of class"}

    Si l'objet a été créé, le contenu de la requête est copié dans la propriété fichier File. Si la copie n'a pas réussi, nous formons une structure JSON avec une description de l'erreur : 

    {"Message",$system.Status.GetOneErrorText(st)}

    Si le contenu a été copié, nous sauvegardons l'objet, et si la sauvegarde est réussie, nous formons un JSON {"Status","OK"}. Dans le cas contraire, le JSON renvoie une description de l'erreur. 

    Enfin, nous écrivons le JSON dans une réponse et retournons l'état st.

    Voici un exemple de transfert d'une image :

    La manière dont il est sauvegardé dans la base de données :

    Nous pouvons enregistrer ce flux dans un fichier et constater qu'il a été transféré sans être modifié :

    set f = ##class(RestTransfer.FileDesc).%OpenId(4)
    set s = ##class(%Stream.FileBinary).%New()
    set s.Filename = "D:\Downloads\test1.jpg"
    do s.CopyFromAndSave(f.File)
    

    La même chose peut être faite avec un fichier texte :

    Cela sera également visible dans les globales :

    Et nous pouvons stocker des fichiers exécutables ou tout autre type de données :

    Nous pouvons vérifier la dimension dans les globales, et c'est correct :

    Maintenant que cette partie fonctionne comme il se doit, examinons la deuxième approche - obtenir le contenu du fichier et ses métadonnées (le nom du fichier) stockés au format JSON et transférés dans le corps d'une méthode POST. 

    Vous pouvez en savoir plus sur la création de services REST dans la documentation InterSystems. 

    Le code exemplaire pour les deux approches se trouve sur GitHub et InterSystems Open Exchange. Si vous avez des questions ou des suggestions concernant l'une ou l'autre, n'hésitez pas à les écrire dans la section des commentaires.

    0
    1 743
    Article Guillaume Rongier · Juin 24, 2022 7m read

    Dans cette série d'articles en trois parties, il est montré comment vous pouvez utiliser IAM pour ajouter simplement de la sécurité, selon les normes OAuth 2.0, à un service précédemment non authentifié déployé dans IRIS.

    Dans la première partie, nous avons fourni des informations sur OAuth 2.0 ainsi que des définitions et des configurations initiales d'IRIS et d'IAM afin de faciliter la compréhension de l'ensemble du processus de sécurisation de vos services.

    La deuxième partie discute et montre en détail les étapes nécessaires pour configurer IAM pour valider le jeton d'accès présent dans la demande entrante et transmettre la demande au backend si la validation réussit.

    Cette dernière partie de cette série abordera et démontrera les configurations nécessaires pour que IAM génère un jeton d'accès (agissant comme un serveur d'autorisation) et le valide, ainsi que quelques considérations finales importantes.

    Si vous voulez essayer IAM, veuillez contacter votre représentant commercial d'InterSystems.

    Scénario 2 : IAM comme serveur d'autorisation et validateur de jetons d'accès

    Dans ce scénario, contrairement au premier scénario, nous allons utiliser un plugin appelé "OAuth 2.0 Authentication".

    Afin d'utiliser IAM comme serveur d'autorisation dans ce flux d'informations d'identification du propriétaire des ressources et du mot de passe, le nom d'utilisateur et le mot de passe doivent être authentifiés par l'application cliente. La demande d'obtention du jeton d'accès auprès d'IAM ne doit être effectuée que si l'authentification est réussie.

    Commençons par l'ajouter à notre "SampleIRISService". Comme vous pouvez le voir dans la capture d'écran ci-dessous, nous avons différents champs à remplir afin de configurer ce plugin.

    Tout d'abord, nous allons copier l'identifiant de notre service "SampleIRISService" dans le champ "service_id" pour activer ce plugin à notre service.

    Dans le champ "config.auth_header_name", nous allons spécifier le nom de l'en-tête qui portera le jeton d'autorisation. Dans ce cas, je vais laisser la valeur par défaut comme "authorization".

    Le plugin "OAuth 2.0 Authentication" prend en charge les flux OAuth 2.0 Authorization Code Grant, Client Credentials, Implicit Grant ou Resource Owner Password Credentials Grant (respectivement, Attribution de codes d'autorisation, Identifiants client, Attribution implicite et Attribution des informations d'identification du mot de passe du propriétaire de la ressource OAuth 2.0). Comme nous utilisons dans cet article le flux "Resource Owner Password Credentials" (Identifiants du mot de passe du propriétaire de la ressource), nous allons cocher la case "config.enable_password_grant"..

    Dans le champ "config.provision_key", saisissez une chaîne à utiliser comme clé de provision. Cette valeur sera utilisée pour demander un jeton d'accès à IAM.

    Dans ce cas, j'ai laissé tous les autres champs avec la valeur par défaut. Vous pouvez vérifier la référence complète de chaque champ dans la documentation du plugin disponible ici.

    Voici à quoi ressemble la configuration du plugin à la fin :

    Une fois le plugin créé, nous devons créer les informations d'identification de notre consommateur "ClientApp".

    Pour ce faire, allez dans "Consumers" dans le menu de gauche et cliquez sur "ClientApp". Ensuite, cliquez sur l'onglet "Credentials" et ensuite sur le bouton "New OAuth 2.0 Application".

    Sur la page suivante, entrez un nom quelconque pour identifier votre application dans le champ "name" (nom), définissez un identifiant et un secret de client, respectivement, dans les champs "client_id" et "client_secret" et enfin, entrez l'URL dans votre application où les utilisateurs seront envoyés après autorisation dans le champ "redirect_uri". Ensuite, cliquez sur "Create" (créer).

    Maintenant, vous êtes prêt à envoyer des demandes.

    La première requête que nous devons faire est d'obtenir le jeton d'accès depuis IAM. Le plugin "OAuth 2.0 Authentication" crée automatiquement un point de terminaison en ajoutant le chemin "/oauth2/token" à l'itinéraire déjà créé.

    Note: Assurez-vous que vous utilisez le protocole HTTPS et que le port proxy d'IAM prête attention aux requêtes TLS/SSL (le port par défaut est 8443). Il s'agit d'une exigence d'OAuth 2.0.

    Par conséquent, dans ce cas, nous devrions faire une demande POST à l'URL :

    https://iamhost:8443/event/oauth2/token

    Dans le corps de la requête, vous devez inclure le JSON suivant :

    {
       "client_id": "clientid",
       "client_secret": "clientsecret",
       "grant_type": "password",
       "provision_key": "provisionkey",
       "authenticated_userid": "1"
    }

    Comme vous pouvez le constater, ce JSON contient des valeurs définies à la fois lors de la création du plugin "OAuth 2.0 Authentication", telles que "grant_type" et "provision_key", et lors de la création des informations d'identification du Consommateur, telles que "client_id" et "client_secret".

    Le paramètre "authenticated_userid" doit également être ajouté par l'application client lorsque le nom d'utilisateur et le mot de passe fournis sont authentifiés avec succès. Sa valeur doit être utilisée pour identifier de manière unique l'utilisateur authentifié.

    La demande et sa réponse respective devraient ressembler à ceci :

    Avec cela, nous pouvons maintenant faire une requête pour obtenir les données de l'événement en incluant la valeur "access_token" de la réponse ci-dessus comme "Bearer Token" (jeton de porteur) dans une requête GET vers l'URL

    https://iamhost:8443/event/1

    Si votre jeton d'accès expire, vous pouvez générer un nouveau jeton d'accès en utilisant le jeton de renouvellement que vous avez reçu avec le jeton d'accès expiré en effectuant une requête POST vers le même point de terminaison que celui utilisé pour obtenir un jeton d'accès, avec un corps quelque peu différent :

    {
       "client_id": "clientid",
       "client_secret": "clientsecret",
       "grant_type": "refresh_token",
       "refresh_token": "E50m6Yd9xWy6lybgo3DOvu5ktZTjzkwF"
    }

    La demande et sa réponse respective devraient ressembler à ceci :

    Une fonctionnalité intéressante du plugin "OAuth 2.0 Authentication" est la possibilité d'afficher et d'invalider les jetons d'accès.

    Pour répertorier les tokens, envoyez une requête GET au point de terminaison suivant de l'API d'administration d'IAM :

    https://iamhost:8444/{workspace_name}/oauth2_tokens

    où {workspace_name} est le nom de l'espace de travail IAM utilisé. Veillez à saisir les informations d'identification nécessaires pour appeler l'API d'administration d'IAM si vous avez activé RBAC.

    Notez que "credential_id" est l'identifiant de l'application OAuth que nous avons créée dans le consommateur ClientApp (dans ce cas, elle s'appelle SampleApp), et "service_id" est l'identifiant de notre "SampleIRISService" auquel ce plugin est appliqué.

    Pour invalider un jeton, vous pouvez envoyer une demande DELETE au point de terminaison suivant

    https://iamhost:8444/Sample/oauth2_tokens/{token_id}

    où {token_id} est l'identifiant du jeton à invalider.

    Si nous essayons d'utiliser le jeton invalidé, nous obtenons un message indiquant que le jeton est invalide ou a expiré si nous envoyons une requête GET contenant ce jeton invalidé comme jeton porteur à l'URL :

    https://iamhost:8443/event/1

    Considérations finales

    Dans cet article, nous avons montré comment vous pouvez ajouter l'authentification OAuth 2.0 dans IAM à un service non authentifié déployé dans IRIS. Vous devez tenir compte du fait que le service lui-même continuera à être non authentifié dans IRIS. Par conséquent, si quelqu'un appelle directement le point de terminaison du service IRIS, en contournant la couche IAM, il pourra voir les informations sans aucune authentification. Pour cette raison, il est important d'avoir des règles de sécurité au niveau du réseau pour empêcher les demandes non désirées de contourner la couche IAM.

    Vous pouvez en savoir plus sur IAM ici.

    Si vous voulez essayer IAM, veuillez contacter votre représentant commercial d'InterSystems.

    0
    0 203
    Article Guillaume Rongier · Juin 22, 2022 4m read

    Dans cette série d'articles en trois parties, il est montré comment vous pouvez utiliser IAM pour ajouter simplement de la sécurité, selon les normes OAuth 2.0, à un service précédemment non authentifié déployé dans IRIS.

    Dans la première partie, nous avons fourni des informations sur OAuth 2.0 ainsi que des définitions et des configurations initiales d'IRIS et d'IAM afin de faciliter la compréhension de l'ensemble du processus de sécurisation de vos services.

    Cette partie va maintenant discuter et montrer en détail les étapes nécessaires pour configurer IAM pour valider le jeton d'accès présent dans la demande entrante et transmettre la demande au backend si la validation réussit.

    La dernière partie de cette série abordera et démontrera les configurations nécessaires pour que IAM génère un jeton d'accès (agissant comme un serveur d'autorisation) et le valide, ainsi que quelques considérations finales importantes.

    Si vous voulez essayer IAM, veuillez contacter votre représentant commercial d'InterSystems.

    Scénario 1 : IAM comme validateur de jetons d'accès

    Dans ce scénario, il sera utilisé un serveur d'autorisation externe qui génère un jeton d'accès au format JWT (JSON Web Token). Ce JWT est signé à l'aide de l'algorithme RS256 et d'une clé privée. Afin de vérifier la signature du JWT, l'autre partie (dans ce cas IAM) doit avoir la clé publique, fournie par le serveur d'autorisation.

    Ce JWT généré par le serveur d'autorisation externe comprend également, dans son corps, une déclaration appelée "exp" contenant l'horodatage de la date d'expiration de ce jeton, et une autre déclaration appelée "iss" contenant l'adresse du serveur d'autorisation.

    Par conséquent, IAM doit vérifier la signature du JWT avec la clé publique des serveurs d'autorisation et l'horodatage d'expiration contenu dans la déclaration "exp" à l'intérieur du JWT avant de transmettre la demande à IRIS.

    Afin de configurer cela dans IAM, commençons par ajouter un plugin appelé "JWT" à notre "SampleIRISService" dans IAM. Pour ce faire, allez sur la page Services dans IAM et copiez l'identifiant du "SampleIRISService", nous allons l'utiliser plus tard.

    Ensuite, allez dans Plugins, cliquez sur le bouton "New Plugin", localisez le plugin "JWT" et cliquez sur Enable.

    Dans la page suivante, copiez l'identifiant "SampleIRISService" dans le champ "service_id" et cochez la case "exp" dans le paramètre "config.claims_to_verify".


    Notez que la valeur du paramètre "config.key_claim_name" est "iss". Nous allons l'utiliser plus tard.

    Ensuite, appuyez sur le bouton "Create" (créer).

    Cela fait, allez dans la section "Consumers" dans le menu de gauche et cliquez sur notre "ClientApp" précédemment créée. Allez dans l'onglet "Credentials" (identifiants) et cliquez sur le bouton “New JWT Credential” (nouveau Identifiants JWT).

    Dans la page suivante, sélectionnez l'algorithme utilisé pour signer le JWT ( ici RS256) et copiez la clé publique dans le champ "rsa_public_key" (il s'agit de la clé publique qui vous a été fournie par le serveur d'autorisation au format PEM).

    Dans le champ "key", vous devez insérer le contenu du revendication JWT que vous avez entré dans le champ "config.key_claim_name" lors de l'ajout du plugin JWT. Donc, dans ce cas, je dois insérer le contenu de la revendication iss de mon JWT, qui, dans mon cas, est l'adresse du serveur d'autorisation.

    Après cela, cliquez sur le bouton "Créer".

    Hint: À des fins de débogage, il existe un outil en ligne permettant de décoder le JWT afin que vous puissiez vérifier les revendications et leurs valeurs et vérifier les signatures en insérant la clé publique. Voici le lien de cet outil en ligne : https://jwt.io/#debugger

    Maintenant, avec l'ajout du plugin JWT, il n'est plus possible d'envoyer la requête sans authentification. Comme vous pouvez le voir ci-dessous, une simple demande GET, sans authentification, renvoie à l'URL

    http://iamhost:8000/event/1

    un message non autorisé avec le code de statut "401 Unauthorized".

    Afin d'obtenir les résultats d'IRIS, nous devons ajouter le JWT à la requête.

    Par conséquent, nous devons d'abord demander le JWT au serveur d'autorisation. Le serveur d'autorisation personnalisé que nous utilisons ici renvoie un JWT si une demande POST est faite avec quelques paires clé-valeur dans le corps, y compris des informations sur l'utilisateur et le client, à l'URL suivante :

    https://authorizationserver:5001/auth

    Voici à quoi ressemblent cette requête et sa réponse :

    Ensuite, vous pouvez ajouter le JWT obtenu à partir de la réponse ci-dessous dans le header d'autorisation en tant que jeton de porteur Bearer Token et envoyer une requête GET à la même URL utilisée précédemment :

    http://iamhost:8000/event/1

    Vous pouvez également l'ajouter en tant que paramètre de chaîne de recherche, la clé de la chaîne de recherche étant la valeur spécifiée dans le champ "config.uri_param_names" lors de l'ajout du plugin JWT qui, dans ce cas, est "jwt" :

    Enfin, il y a aussi la possibilité d'inclure JWT dans la requête en tant que cookie, si un nom est saisi dans le champ "config.cookie_names".

    Passez à la troisième et dernière partie de cette série pour comprendre les configurations nécessaires pour générer un jeton d'accès IAM et le valider, ainsi que quelques considérations finales importantes.

    0
    0 80
    Article Guillaume Rongier · Juin 20, 2022 5m read

    Introduction

    Aujourd'hui, de nombreuses applications utilisent le cadre d'autorisation ouvert (OAuth) pour accéder aux ressources de toutes sortes de services de manière sûre, fiable et efficace. InterSystems IRIS est déjà compatible avec le cadre OAuth 2.0, en fait, il y a un excellent article dans la communauté concernant OAuth 2.0 et InterSystems IRIS dans le lien suivant ici.

    Toutefois, avec l'avènement des outils de gestion des API, certaines organisations l'utilisent comme point unique d'authentification, empêchant les demandes non autorisées d'arriver aux services descendants et découplant les complexités d'autorisation/authentification du service lui-même.

    Comme vous le savez peut-être, InterSystems a lancé son outil de gestion des API, appelé InterSystems API Management (IAM), qui est disponible avec la licence IRIS Enterprise (et non IRIS Community Edition). Vous trouverez ici un autre excellent article de la communauté présentant InterSystems API Management.

     Il s'agit de la première partie d'une série d'articles en trois parties qui montrent comment vous pouvez utiliser IAM pour ajouter simplement de la sécurité, selon les normes OAuth 2.0, à un service précédemment non authentifié déployé dans IRIS.

    Dans cette première partie, vous trouverez des informations sur OAuth 2.0 ainsi que des définitions et des configurations initiales d'IRIS et d'IAM afin de faciliter la compréhension de l'ensemble du processus de sécurisation de vos services.

    Suite à la première partie, cette série d'articles abordera deux scénarios possibles pour sécuriser vos services avec IAM. Dans le premier scénario, IAM validera uniquement le jeton d'accès présent dans la requête entrante et transmettra la requête au backend si la validation réussit. Dans le second scénario, IAM va à la fois générer un jeton d'accès (en agissant comme un serveur d'autorisation) et le valider.

    Par conséquent, la deuxième partie abordera et montrera en détail les étapes nécessaires pour configurer le scénario 1, et la troisième partie abordera et démontrera les configurations pour le scénario 2, ainsi que quelques considérations finales.

    Si vous voulez essayer IAM, veuillez contacter votre représentant commercial InterSystems.

    OAuth 2.0 : contexte

    Chaque flux d'autorisation OAuth 2.0 se compose essentiellement de 4 parties :

    1. Utilisateur
    2. Client
    3. Serveur d'autorisation
    4. Propriétaire de la ressource

    Pour des raisons de simplicité, cet article utilisera le flux OAuth "Resource Owner Password Credentials" (Identifiants du mot de passe du propriétaire de la ressource), mais vous pouvez utiliser n'importe quel flux OAuth dans IAM. De même, cet article ne spécifiera aucune portée.

    Note: Vous ne devez utiliser le flux d'informations d'identification du mot de passe du propriétaire des ressources que lorsque l'application cliente est hautement fiable, car elle traite directement les informations d'identification des utilisateurs. Dans la plupart des cas, le client doit être une application de première partie.

    En général, le flux "Resource Owner Password Credentials" (Identifiants du mot de passe du propriétaire de la ressource) suit les étapes suivantes :

    1. L'utilisateur saisit ses identifiants (par exemple le nom d'utilisateur et le mot de passe) dans l'application client.
    2. L'application client envoie les identifiants de l'utilisateur ainsi que sa propre identification (identifiant et secret du client, par exemple) au serveur d'autorisation. Le serveur d'autorisation valide les identifiants de l'utilisateur et l'identification du client et renvoie un jeton d'accès.
    3. Le client utilise le jeton pour accéder aux ressources du serveur de ressources.
    4. Le serveur de ressources valide le jeton d'accès reçu avant de renvoyer toute information au client.

    Dans cette optique, il existe deux scénarios dans lesquels vous pouvez utiliser IAM pour traiter OAuth 2.0 :

    1. IAM agit comme un validateur, vérifiant le jeton d'accès fourni par l'application cliente, transmettant la demande au serveur de ressources uniquement si le jeton d'accès est valide ; dans ce cas, le jeton d'accès serait généré par un serveur d'autorisation tiers.
    2. IAM agissant à la fois comme un serveur d'autorisation, fournissant un jeton d'accès au client, et comme un validateur de jeton d'accès, vérifiant le jeton d'accès avant de rediriger la demande vers le serveur de ressources.

    Définitions d'IRIS et d'IAM

    Dans ce post, il sera utilisé une application Web IRIS appelée "/SampleService". Comme vous pouvez le voir sur la capture d'écran ci-dessous, il s'agit d'un service REST non authentifié déployé dans IRIS :

    En outre, dans le côté IAM est configuré un service appelé "SampleIRISService" contenant un itinéraire, comme vous pouvez le voir dans la capture d'écran ci-dessous :

    En outre, dans IAM est configuré un consommateur appelé "ClientApp", initialement sans aucun justificatif d'identité, pour identifier celui qui appelle l'API dans IAM :

    Avec les configurations ci-dessus, IAM transmet chaque requête GET envoyée à l'URL suivante à IRIS :

    http://iamhost:8000/event

    À ce stade, aucune authentification n'est encore utilisée. Par conséquent, si nous envoyons une simple requête GET, sans authentification, à l'URL

    http://iamhost:8000/event/1

    nous obtenons la réponse recherchée.

    Dans cet article, nous allons utiliser une application appelée "PostMan" pour envoyer des requêtes et vérifier les réponses. Dans la capture d'écran de PostMan ci-dessous, vous pouvez voir une simple requête GET ainsi que sa réponse.

    Passez à la deuxième partie de cette série pour comprendre comment configurer IAM pour valider les jetons d'accès présents dans les demandes entrantes.

    0
    0 89
    Article Lorenzo Scalese · Juin 18, 2022 5m read

    InterSystems API Management (IAM) - c'est une nouvelle fonctionnalité de la plate-forme de données InterSystems IRIS, qui vous permet de surveiller, de contrôler et de gérer le trafic vers et à partir des API basées sur le Web au sein de votre infrastructure informatique. Au cas où vous l'auriez manqué, voici le lien vers l'annonce. Et voici un article expliquant comment commencer à travailler avec IAM.

    Dans cet article, nous allons utiliser InterSystems API Management pour assurer l'équilibrage de charge d'une API.

    Dans notre cas, nous avons 2 instances InterSystems IRIS avec /api/atelier REST API que nous voulons publier pour nos clients.

    Il y a de nombreuses raisons différentes pour lesquelles nous pourrions vouloir faire cela, par exemple :

    • Équilibrage de la charge pour répartir la charge de travail entre les serveurs.
    • Déploiement bleu-vert : nous avons deux serveurs, l'un "prod", l'autre "dev" et nous pouvons vouloir passer de l'un à l'autre.
    • Déploiement canary: nous pourrions publier la nouvelle version sur un seul serveur et y transférer 1% des clients.
    • Configuration de haute disponibilité
    • etc.

    Pourtant, les mesures que nous devons prendre sont assez similaires.

    Conditions préalables

    • 2 instances InterSystems IRIS
    • L'instance InterSystems API Managemen  

    Allons-y

    Voici ce que nous devons faire :

    1. Créer un flux ascendant.

    Un flux ascendant représente un nom d'hôte virtuel et peut être utilisé pour équilibrer la charge des demandes entrantes sur plusieurs services (cibles). Par exemple, un flux ascendant nommé service.v1.xyz recevrait des demandes pour un service dont l'hôte est service.v1.xyz. Les requêtes pour ce service seront envoyées par procuration aux cibles définies dans l'amont.

    Un flux ascendant comprend également un vérificateur de santé, qui peut activer et désactiver les cibles en fonction de leur capacité ou de leur incapacité à répondre aux demandes.

    Pour commencer :

    • Ouvrir le portail d'administration IAM
    • Allez dans Workspaces
    • Choisissez votre espace de travail
    • Ouvrez les flux ascendants (Upstreams)
    • Cliquez sur le bouton "New Upstream"

    Après avoir cliqué sur le bouton "New Upstream", vous verrez apparaître un formulaire où vous pourrez saisir quelques informations de base sur le flux ascendant (il y a beaucoup d'autres propriétés) :

    Saisissez nom - il s'agit d'un nom d'hôte virtuel que nos services utiliseraient. Il n'est pas lié aux enregistrements DNS. Je recommande de lui attribuer une valeur inexistante pour éviter toute confusion. Si vous voulez en savoir plus sur les autres propriétés, consultez la documentation. Sur la capture d'écran, vous pouvez voir que j'ai nommé de manière imaginative le nouveau flux ascendant comme myupstream.  

    2. Créer des cibles.

    Les cibles sont des serveurs backend qui exécutent les requêtes et renvoient les résultats au client. Allez dans Upstreams et cliquez sur le nom du flux ascendant que vous venez de créer (et NON sur le bouton de mise à jour) :

    Vous verrez toutes les cibles existantes (aucune jusqu'à présent) et le bouton "Nouvelle cible". Cliquez dessus :

    Ensuite, définissez une cible dans le nouveau formulaire. Seuls deux paramètres sont disponibles :

    • cible - hôte et port du serveur backend
    • Pondération - priorité relative donnée à ce serveur (plus de pondération - plus de demandes sont envoyées à cette cible)

    J'ai ajouté deux cibles :

     

    3. Créer un service

    A présent que nous avons notre flux ascendant, nous devons lui envoyer des requêtes.  Pour cela, nous utilisons Service.
    Les entités Service, comme leur nom l'indique, sont des abstractions de chacun de vos services en amont. Des exemples de services seraient un microservice de transformation des données, une API de facturation, etc. Nous allons créer un service ciblant notre instance IRIS, allez dans Services et cliquez sur le bouton "Nouveau service" :

    Définissez les valeurs suivantes :

    champvaleurdescription
    nommyservicenom logique de ce service
    hôtemyupstreamnom en amont
    chemin/api/atelierchemin d'accès associé à une racine que nous voulons servir
    protocolehttples protocoles que nous voulons supporter

    Conservez les valeurs par défaut pour tout le reste (y compris le port : 80).

    Après avoir créé le service, vous le verrez dans une liste de services. Copiez l'ID du service quelque part, nous en aurons besoin plus tard.

     

    4. Créer un itinéraire

    Les Itinéraires définissent les règles permettant de répondre aux demandes des clients. Chaque Itinéraire est associé à un Service, et un Service peut avoir plusieurs Itinéraires associés. Chaque demande correspondant à un Itinéraire donné sera transmise par proxy au Service qui lui est associé.

    La combinaison des Itinéraires et des Services (et la séparation des préoccupations entre eux) offre un mécanisme de routage puissant avec lequel il est possible de définir des points d'entrée très fins dans l'IAM menant à différents services en amont de votre infrastructure.

    Maintenant, créons un itinéraire. Allez dans le menu Routes et appuyez sur le bouton "New Route".

    Définissez les valeurs dans le formulaire de création de l'Itinéraire :

    champvaleurdescription
    lien/api/atelierlien racine que nous voulons servir
    protocolehttp les protocoles que nous voulons supporter
    service.idguid de 3valeur de l'identifiant du service (guide de l'étape précédente)
     

    Et c'est fini !

    Envoyez une requête à http://localhost:8000/api/atelier/ (notez la barre oblique à la fin) et elle sera servie par l'un de nos deux backends.  

    Conclusion

    IAM offre une infrastructure de gestion des API hautement personnalisable, permettant aux développeurs et aux administrateurs de prendre le contrôle de leurs API.  

    Liens

    Question

    Quelle fonctionnalité souhaitez-vous voir configurée avec IAM ?

    0
    0 87
    Article Guillaume Rongier · Juin 15, 2022 17m read

    Introduction

    Nous sommes à l'ère de l'économie multiplateforme et les API sont la "colle " de ce scénario numérique. Étant donné leur importance, les développeurs les considèrent comme un service ou un produit à consommer. Par conséquent, l'expérience d'utilisation est un facteur crucial de leur succès.

    Afin d'améliorer cette expérience, des normes de spécification telles que la spécification OpenAPI (OAS) sont de plus en plus adoptées dans le développement des API RESTFul.

    IRIS ApiPub - qu'est-ce que c'est ?

    IRIS ApiPub est un projet de type code source ouvert Open Source dont l'objectif principal est de publier automatiquement les API RESTful créées avec la technologie Intersystems IRIS, de la manière la plus simple et la plus rapide possible en utilisant la norme Open Specification API (OAS) standard, version 3.0.

    Il permet à l'utilisateur de se concentrer sur la mise en œuvre et les règles métier (méthodes Web) de l'API, en abstrayant et en automatisant les autres aspects liés à la documentation, l'exposition, l'exécution et la surveillance des services.

    Ce projet comprend également un exemple complet de mise en œuvre (apiPub.samples.api) de la Swagger Petstore, qui est l'échantillon officiel de swagger.

    Testez-le avec vos services SOAP actuels

    Si vous avez déjà publié des services SOAP, vous pouvez les tester en utilisant Rest/JSON avec OAS 3.0.

    Lors de la publication de méthodes avec des types complexes, la classe de l'objet doit être une sous-classe de %XML.Adapter. De cette manière, les services SOAP précédemment installés sont rendus automatiquement compatibles.

    Surveillez vos API avec IRIS Analytics

    Activez la surveillance des API pour gérer et suivre tous vos Appels Rest. Vous pouvez également configurer vos propres indicateurs.

    Installation

    1. Effectuez un clone/git pull depuis le dépôt dans le répertoire local.
    $ git clone https://github.com/devecchijr/apiPub.git
    
    1. Ouvrez le terminal dans ce répertoire et exécutez la commande suivante :
    $ docker-compose up -d
    
    1. Exécutez le conteneur IRIS avec le projet :
    $ docker-compose up -d
    

    Test de l'application

    Ouvrez l'URL http://localhost:52773/swagger-ui/index.html de swagger

    Essayez d'exécuter une opération à l'aide de l'API Petstore, par exemple en effectuant un postage d'un nouveau pet.

    Consultez la table de bord du moniteur apiPub. Essayez d'explorer le domaine petStore pour explorer et analyser les messages.

    Modifiez ou créez des méthodes dans la classe apiPub.samples.api et revenez à la documentation générée. Notez que toutes les modifications sont automatiquement reflétées dans la documentation OAS ou dans les schémas.

    Publiez votre API selon la norme OAS 3.0 en seulement 3 étapes :

    Étape 1

    Définissez la classe d'implémentation de votre API et balises les méthodes avec l'attribut [WebMethod]

    Cette étape n'est pas nécessaire si vous disposez déjà d'une mise en œuvre de WebServices.

    Étape 2

    Créez une sous-classe de apiPub.core.service et définissez sa propriété DispatchClass comme la classe Implementation créée précédemment. Incluez également le chemin de la documentation OAS 3.0. Si vous le souhaitez, pointez vers la classe apiPub.samples.api (PetStore).

    Étape 3

    Créez une application Web et définissez la classe de répartition comme la classe de service créée ci-dessus.

    Utilisation de Swagger

    Avec iris-web-swagger-ui vous pouvez exposer votre spécification de service. Il vous suffit de pointer vers le chemin de la documentation et... VOILÁ!!

    Définition de l'en-tête de la spécification OAS

    Il existe deux façons de définir l'en-tête OAS 3.0 :

    La première consiste à créer un bloc JSON XDATA nommé apiPub dans la classe d'implémentation. Cette méthode autorise plus d'une balise et la modélisation est compatible avec la norme OAS 3.0. Les propriétés qui peuvent être personnalisées sont info, tags et servers.

    XData apiPub [ MimeType = application/json ]
    {
        {
            "info" : {
                "description" : "Il s'agit d'un exemple de serveur Petstore.  Vous pouvez en savoir plus sur Swagger à l'adresse suivante\n[http://swagger.io](http://swagger.io) or on\n[irc.freenode.net, #swagger](http://swagger.io/irc/).\n",
                "version" : "1.0.0",
                "title" : "IRIS Petstore (Dev First)",
                "termsOfService" : "http://swagger.io/terms/",
                "contact" : {
                "email" : "apiteam@swagger.io"
                },
                "license" : {
                "name" : "Apache 2.0",
                "url" : "http://www.apache.org/licenses/LICENSE-2.0.html"
                }
            },
            "tags" : [ {
                "name" : "pet",
                "description" : "Tout sur vos Pets",
                "externalDocs" : {
                "description" : "Pour en savoir plus",
                "url" : "http://swagger.io"
                }
            }, {
                "name" : "store",
                "description" : "Accès aux commandes du Petstore"
            }, {
                "name" : "user",
                "description" : "Opérations sur l'utilisateur",
                "externalDocs" : {
                "description" : "En savoir plus sur notre magasin",
                "url" : "http://swagger.io"
                }
            } ]
        }
    }
    

    La seconde méthode consiste à définir des paramètres dans la classe d'implémentation, comme dans l'exemple suivant :

    Parameter SERVICENAME = "My Service";
    
    Parameter SERVICEURL = "http://localhost:52776/apipub";
    
    Parameter TITLE As %String = "REST aux API SOAP";
    
    Parameter DESCRIPTION As %String = "API pour le proxy des services Web SOAP via REST";
    
    Parameter TERMSOFSERVICE As %String = "http://www.intersystems.com/terms-of-service/";
    
    Parameter CONTACTNAME As %String = "John Doe";
    
    Parameter CONTACTURL As %String = "https://www.intersystems.com/who-we-are/contact-us/";
    
    Parameter CONTACTEMAIL As %String = "support@intersystems.com";
    
    Parameter LICENSENAME As %String = "Copyright InterSystems Corporation, tous droits réservés.";
    
    Parameter LICENSEURL As %String = "http://docs.intersystems.com/latest/csp/docbook/copyright.pdf";
    
    Parameter VERSION As %String = "1.0.0";
    
    Parameter TAGNAME As %String = "Services";
    
    Parameter TAGDESCRIPTION As %String = "Services d'héritage";
    
    Parameter TAGDOCSDESCRIPTION As %String = "Pour en savoir plus";
    
    Parameter TAGDOCSURL As %String = "http://intersystems.com";
    

    Personnalisez vos API

    Vous pouvez personnaliser plusieurs aspects de l'API, tels que les balises, les chemins et les verbes. Pour cela, vous devez utiliser une notation spéciale, déclarée dans le commentaire de la méthode personnalisée.

    Syntaxe:

    /// @apiPub[assignment clause]
    [Method/ClassMethod] methodName(params as type) As returnType {

    }

    Toutes les personnalisations présentées à titre d'exemple dans cette documentation se trouvent dans la classe apiPub.samples.api.

    Personnalisation des verbes

    Lorsqu'aucun type complexe n'est utilisé comme paramètre d'entrée, apiPub attribue automatiquement le verbe Get. Dans le cas contraire, le verbe Post sera attribué.

    Si vous souhaitez personnaliser la méthode, ajoutez la ligne suivante aux commentaires de la méthode.

    /// @apiPub[verb="verb"]

    verbe peut être get, post, put, delete ou patch.

    Exemple:

    /// @apiPub[verb="put"]

    Personnalisation des chemins

    Cet outil attribue automatiquement des chemins ou des routages aux Méthodes Web. Il utilise le nom de la méthode comme chemin, par défaut.

    Si vous souhaitez personnaliser le chemin, ajoutez la ligne suivante aux commentaires de la méthode.

    /// @apiPub[path="path"]

    chemin peut être n'importe quelle valeur précédée d'un slash, tant qu'il n'entre pas en conflit avec un autre chemin dans la même classe d'implémentation.

    Exemple:

    /// @apiPub[path="/pet"]

    Une autre utilisation très courante du chemin est de définir un ou plusieurs paramètres dans le chemin lui-même. Pour cela, le nom du paramètre défini dans la méthode doit être entouré d'accolades.

    Exemple:

    /// @apiPub[path="/pet/{petId}"]
    Method getPetById(petId As %Integer) As apiPub.samples.Pet [ WebMethod ]
    {
    }

    Lorsque le nom du paramètre interne diffère du nom du paramètre affiché, le nom peut être égalisé selon l'exemple suivant :

    /// @apiPub[path="/pet/{petId}"]
    /// @apiPub[params.pId.name="petId"]
    Method getPetById(pId As %Integer) As apiPub.samples.Pet [ WebMethod ]
    {
    }

    Dans l'exemple ci-dessus, le paramètre interne pId est affiché sous la forme petId.

    Personnalisation des balises

    Il est possible de définir le tag (regroupement) de la méthode lorsque plus d'un tag est défini dans l'en-tête.

    /// @apiPub[tag="value"]

    Exemple:

    /// @apiPub[tag="user"]

    Personnalisation du succès Code d'état

    Si vous souhaitez modifier le Code d'état de réussite de la méthode, qui est 200 par défaut, il convient d'utiliser la notation suivante.

    /// @apiPub[successfulCode="code"]

    Exemple:

    /// @apiPub[successfulCode="201"]

    Personnalisation de l'exception Code d'état

    Cet outil traite toutes les exceptions comme Code d'état 500 par défaut. Si vous souhaitez ajouter de nouveaux codes d'exception à la documentation, utilisez la notation suivante.

    /// @apiPub[statusCodes=[{code:"code",description:"description"}]]

    Où la propriété statusCodes est un tableau d'objets contenant le code et la description.

    Exemple:

    /// @apiPub[statusCodes=[
    /// {"code":"400","description":"Invalid ID supplied"}
    /// ,{"code":"404","description":"Pet not found"}]
    /// ]

    Lorsque vous soulevez l'exception, incluez Code d'état dans la description de l'exception entre les caractères "<" et ">".

    Exemple:

    Throw ##Class(%Exception.StatusException).CreateFromStatus($$$ERROR($$$GeneralError, "<400> Invalid ID supplied"))}

    Voir la méthode getPetById de la classe apiPub.samples.api

    Marquer l'API comme déconseillé

    Pour que l'API soit affichée comme déconseillée, la notation suivante doit être utilisée :

    /// @apiPub[deprecated="true"]

    Personnalisation de l'operationId

    Selon la spécification OAS, operationId est une chaîne unique utilisée pour identifier une API ou une opération. Dans cet outil, il est utilisé dans le même but lors des opérations de surveillance et suivi operations.

    Par défaut, elle porte le même nom que la méthode de la classe d'implémentation.

    Si vous souhaitez le modifier, utilisez la notation suivante

    /// @apiPub[operationId="updatePetWithForm"]

    Modification du jeu de caractères de la méthode

    Le jeu de caractères par défaut est généralement défini à l'aide du paramètre CHARSET de la classe de service, décrit dans étape 2. Si vous souhaitez personnaliser le jeu de caractères d'une méthode, vous devez utiliser la notation suivante ::

    /// @apiPub[charset="value"]

    Exemple:

    /// @apiPub[charset="UTF-8"]

    Personnalisation des noms et autres caractéristiques des paramètres

    Vous pouvez personnaliser plusieurs aspects des paramètres d'entrée et de sortie de chaque méthode, tels que les noms et les descriptions qui seront affichés pour chaque paramètre.

    Pour personnaliser un paramètre spécifique, utilisez la notation suivante

    /// @apiPub[params.paramId.property="value"]

    ou pour des réponses :

    /// @apiPub[response.property="value"]

    Exemple:

    /// @apiPub[params.pId.name="petId"]
    /// @apiPub[params.pId.description="ID of pet to return"]

    Dans ce cas, le nom petId et la description ID du pet à rendre sont attribués au paramètre défini comme pId

    Lorsque la personnalisation n'est pas spécifique à un paramètre donné, la notation suivante est utilisée

    /// @apiPub[params.property="value"]

    Dans l'exemple suivant, la description Ceci ne peut être fait que par l'utilisateur connecté est attribuée à l'ensemble de la demande, et pas seulement à un seul paramètre :

    /// @apiPub[params.description="Ceci ne peut être fait que par l'utilisateur connecté."]

    Autres propriétés qui peuvent être personnalisées pour des paramètres spécifiques

    Utilisez la notation suivante pour les paramètres d'entrée ou de sortie :

    /// @apiPub[params.paramId.property="value"]

    Pour les reponses:

    /// @apiPub[response.property="value"]

    Propriété
    required: "true" si le paramètre est obligatoire. Tous les paramètres de type path sont déjà automatiquement requis
    schema.items.enum: afficher les énumérateurs pour les types %String ou %Library.DynamicArray. Voir la méthode findByStatus de la classe apiPub.samples.api
    schema.default: Pointe vers une valeur par défaut pour les énumérateurs
    inputType: Pour les types simples, il s'agit par défaut d'un paramètre de requête. Pour les types complexes (corps), il s'agit par défaut de application/json. Dans le cas où vous souhaitez changer le type d'entrée, vous pouvez utiliser ce paramètre. Exemple d'utilisation : Téléchargement d'une image, qui n'est généralement pas de type JSON. Voir la méthode uploadImage de la classe apiPub.samples.api.
    outputType: Pour les types %Status, la valeur par défaut est header. Pour les autres types, la valeur par défaut est application/json. Si vous souhaitez modifier le type de sortie, vous pouvez utiliser ce paramètre. Exemple d'utilisation : Retourner un jeton ("text/plain"). Voir la méthode loginUser de la classe apiPub.samples.api

    Relier des schémas analysables à des types JSON dynamiques (%Library.DynamicObject)

    Vous pouvez relier les schémas OAS 3.0 aux types dynamiques internes

    L'avantage d'associer le schéma au paramètre, outre le fait d'informer l'utilisateur sur une spécification d'objet requise, est l'analyse automatique de la demande, qui est effectuée pendant l'appel API. Si l'utilisateur de l'API, par exemple, soumet une propriété qui ne figure pas dans le schéma, ou envoie une date dans un format non valide, ou n'inclut pas une propriété obligatoire, une ou plusieurs erreurs seront renvoyées à l'utilisateur contenant des informations sur ces problèmes.

    La première étape consiste à inclure le schéma souhaité dans le bloc XDATA, comme indiqué ci-dessous. Dans ce cas, le schéma appelé User peut être utilisé par n'importe quelle méthode. Il doit suivre les mêmes règles que celles utilisées dans la modélisation [OAS 3.0] (https://swagger.io/docs/specification/data-models/).

    XData apiPub [ MimeType = application/json ]
    {
        {
            "schemas": {
                "User": {
                    "type": "object",
                    "required": [
                        "id"
                    ],
                    "properties": {
                        "id": {
                            "type": "integer",
                            "format": "int64"
                        },
                        "username": {
                            "type": "string"
                        },
                        "firstName": {
                            "type": "string"
                        },
                        "lastName": {
                            "type": "string"
                        },
                        "email": {
                            "type": "string"
                        },
                        "password": {
                            "type": "string"
                        },
                        "phone": {
                            "type": "string"
                        },
                        "userStatus": {
                            "type": "integer",
                            "description": "(short) User Status"
                        }
                    }
                }            
            }
        }
    }
    

    La deuxième étape consiste à associer le nom du schéma renseigné à l'étape précédente au paramètre interne de type %Library.DynamicObject en utilisant la notation suivante :

    /// @apiPub[params.paramId.schema="schema name"]

    Exemple associant le paramètre user au schéma User :

    /// @apiPub[params.user.schema="User"]
    Method updateUserUsingOASSchema(username As %String, user As %Library.DynamicObject) As %Status [ WebMethod ]
    {
        code...
    }
    

    Exemple de soumission d'une requête avec une erreur. La propriété username2 n'existe pas dans le schéma User. La propriété id n'est pas non plus définie alors qu'elle est requise :

    {
      "username2": "devecchijr",
      "firstName": "claudio",
      "lastName": "devecchi junior",
      "email": "devecchijr@gmail.com",
      "password": "string",
      "phone": "string",
      "userStatus": 0
    }
    

    Exemple d'une réponse avec une erreur :

    {
      "statusCode": 0,
      "message": "ERROR #5001: <Bad Request> Path User.id is required; Invalid path: User.username2",
      "errorCode": 5001
    }
    

    Voir les méthodes updateUserUsingOASSchema et getInventory de la classe apiPub.samples.api. La méthode getInventory est un exemple de schéma associé à la sortie de la méthode (réponse), elle n'est donc pas analysable.

    Générer le schéma OAS 3.0 à partir d'un objet JSON

    Pour faciliter la génération du schéma OAS 3.0, vous pouvez utiliser les éléments suivants ::

    Définissez une variable avec un échantillon de l'objet JSON.

    set myObject = {"prop1":"2020-10-15","prop2":true, "prop3":555.55, "prop4":["banana","orange","apple"]}
    

    Utilisez la méthode utilitaire de la classe apiPub.core.publisher pour générer le schéma :

    do ##class(apiPub.core.publisher).TemplateToOpenApiSchema(myObject,"objectName",.schema)
    

    Copiez et collez le schéma renvoyé dans le bloc XDATA :

    Exemple:

    XData apiPub [ MimeType = application/json ]
    {
        {
            "schemas": {
                {
                    "objectName":   
                    {
                        "type":"object",
                        "properties":{
                            "prop1":{
                                "type":"string",
                                "format":"date",
                                "example":"2020-10-15"      
                            },
                            "prop2":{
                                "type":"boolean",
                                "example":true
                            },
                            "prop3":{
                                "type":"number",
                                "example":555.55
                            },
                            "prop4":{
                                "type":"array",
                                "items":{
                                    "type":"string",
                                    "example":"apple"
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    

    Activer la surveillance (facultatif)

    1 - Ajoutez et activez les composants suivants dans votre Production (IRIS Interopérabilité)

    ComposantType
    apiPub.tracer.bmService (BS)
    apiPub.tracer.bsService (BS)
    apiPub.tracer.boOpération (BO)

    2 - Activez la surveillance de la classe décrite dans [l'étape 2] (https://github.com/devecchijr/apiPub#passo-2)

    Le paramètre Traceable doit être activé.

    Parameter Traceable As %Boolean = 1;
    
    Parameter TracerBSName = "apiPub.tracer.bs";
    
    Parameter APIDomain = "samples";
    

    Le paramètre APIDomain est utilisé pour regrouper les API à surveiller.

    3 - Importez les tableaux de bord

    zn "IRISAPP"
    Set sc = ##class(%DeepSee.UserLibrary.Utils).%ProcessContainer("apiPub.tracer.dashboards",1)
    

    Vous pouvez également créer d'autres tableaux de bord, basés sur le cube apiPub Monitor.

    Utilisez cet outil conjointement avec Intersystems API Manager

    Acheminez vos API générées et bénéficiez de l'[Intersystems API Manager] (https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=AFL_IAM)

    Compatibilité

    ApiPub est compatible avec Intersystems IRIS ou Intersystems IRIS pour la santé, à partir de la version 2018.1.

    Dépôt

    Github: apiPub

    2
    0 116