Introduction

Cette activité reprend le développement effectué sur les îles et archipels de Polynésie.

L’objectif est d’adjoindre un service Web à l’application web afin de permettre à d’autres applications d’interagir les données. Cela consiste à mettre en place une API réseau.

Cette API sera développée selon le style d’architecture ReST, en utilisant le cadriciel TeachFrame. Ce framework est conçu pour concevoir simplement et avec un minimum de code des logiciels hybrides : à la fois application et service web.

C’est une bonne pratique de disposer de routes différentes pour l’application et le service web. De plus, les routes vers l’API devraient être versionnées (notamment si l’API est publique) afin d’assurer la compatibilité avec les applications tierces en cas de modification de la spécification. Exemple de routes (pour mise à jour d’une île) :

  • application web : POST /island/{id} ;
  • service web : PUT /api/v1/island/{id}.

Pour simplifier cette activité, application et service web utiliseront les même routes et partageront les mêmes contrôleurs. La solution alternative présente une solution respectueuses de ces bonnes pratiques.

Prérequis

La mise en place d’un service web nécessite d’autoriser les méthodes HTTP PUT et DELETE au niveau du serveur web. C’est souvent le cas par défaut (en particulier sur l’hébergement utilisé pour ces travaux pratiques), mais si besoin, ajouter les directives suivantes au fichier .htaccess de l’application :

<Limit PUT DELETE>
    Require all granted
</Limit>

Routes et spécification

Méthodes HTTP et CRUD

Selon le style d’architecture ReST, l’URL sert à désigner la ressource (/island/{id} par exemple) et la méthode HTTP l’action. GET pour obtenir, POST pour créer, PUT pour mettre à jour et DELETE pour supprimer. Ce sont les opérations CRUD.

En HTML, les formulaires ne supportant que les méthodes GET et POST, cette dernière méthode est utilisée pour la création, la mise à jour et la suppression.

Pour mettre en place le service web, il faut ajouter le support des méthodes PUT et DELETE au niveau des routes. Exemple :

island_update:
    comment: Update island
    path: /island/{id}
    method: PUT
    controller: controller\IslandController
    action: save

Spécification

Le développement d’un service web a souvent vocation à permettre une utilisation publique (ou tout du moins par d’autres équipes de développement) de l’API.

L’API doit donc être documentée, et son implémentation respecter la spécification :

  • URL des ressources ;
  • méthodes HTTP associées aux différentes actions ;
  • données (et format) en entrée (que le client envoie au service web) ;
  • données en sortie (réponse) du service web.

Par exemple, la réponse à la requête pour obtenir la liste des archipels est une collection (un tableau) d’objets ayant deux champs : id et name ; à titre de documentation, le minimum est d’ajouter dans le fichier des routes une indication de la forme : output: '[{id, name}, …]'.

De même, pour mettre à jour une île, la documentation indique quels données le client doit fournir en entrée (le serveur n’a rien à renvoyer en sortie) : input: '{name*, population, area, idArchipelago*}' ; ‘*’ permet d’identifier les champs obligatoires ; attention à adapter le code du contrôleur en conséquence, et à la cohérence avec le formulaire dans l’application (cf attribut required).

Développement

Il faut compléter le code de l’application pour implémenter les opérations CRUD sur les archipels et les îles, conformément à la spécification.

Dans cette situation (routes identiques et partage des contrôleur entre application et service web), les seuls modifications sont à apporter aux contrôleurs :

  • Pour les méthodes qui généraient une page web, il faut désormais vérifier si le client a sollicité (cf entête HTTP Accept) du HTML (text/html), du JSON (application/json) ou du XML (application/xml), dans le premier cas, le code existant peut être repris, dans les autres, il faut envoyer des données :
class ArchipelagoController {
  function getAll() {
    if (IO::htmlMode()) {#code actuel
    } else {
      $archipelagos = ORM::getAll(Archipelago::class); #extraction de données
      IO::send($archipelagos); #envoi de données au client
    }
  }
  • Pour les méthodes save qui effectuaient une redirection, il faut ici aussi conditionner cette dernière à l’application web, et dans le cas d’une création renvoyer l’identifiant de l’objet nouvellement créé :
function save(string $id) {#code actuel
    if (IO::htmlMode()) {
      header('Location: ' . Router::getURL('/archipelago'));
    } else if ($id == 0) { //un id a été généré par le SGBD
      IO::send([ "id" => $archipelago->getId() ]);
    }
  }

Remarque : TeachFrame supporte les formats de données JSON et XML (ainsi que les formulaires en entrée) de façon agnostique, c’est à dire sans avoir à s’y référer dans le code des contrôleurs (cf méthodes IO::getInput et IO::send).

Expérimentation / Tests

Pour effectuer des requêtes (HTTP) vers des API ReST, plusieurs solutions existent ; exemples :

  • extension de navigateur ReSTer ;
  • outil en ligne de commande CURL ;
  • application Insomnia.

Utiliser un client ReST pour effectuer des requêtes sur les différents points de terminaison (“endpoint”) de l’API. Essayer différents formats de données (cf entêtes Content-Type et Accept avec les valeurs application/xml ou application/json).

*Rigoureusement, ces tests devraient être enregistrés ou programmés (avec un client PHP par exemple), et le journal de leur exécution conservé.*

Attention, l’extension ReSTer doit être paramétrée pour faire des requêtes “propres” (cf “Clean Requests” à la place de “Browser Requests”).

CORS, ACAO et SOP

Le standard CORS permet à un serveur d’indiquer aux clients quels domaines (en dehors de lui-même) sont autorisés à accéder à ses ressources, notamment via l’entête ACAO.

Par défaut (en l’absence de l’entête ACAO), la SOP du navigateur interdit les requêtes inter-domaines en Javascript.

Cf article MDN sur CORS.

Pour expérimenter CORS, deux domaines sont nécessaires.