Serveur HTTP abstrait

Un plugin-outil pour aider les développeurs à implémenter des API orientées REST, basées sur les méthodes HTTP (get, post, put, delete).

Ce plugin a pour but premier de normaliser des URL que l’on pourra appeler pour manipuler les données du site. À chaque URL, il cherche une fonction plus précise, qui va implémenter une fonctionnalité. Et il ne fait quasiment que cela : c’est à vous, développeuses et développeurs, d’implémenter ces fonctionnalités suivant l’API que vous cherchez à fournir.

Pour cela, il fournit une unique action « http.api » qui va gérer trois paramètres :

  • un format : c’est le nom de l’API réellement implémentée
  • une collection : le type des données qu’on veut utiliser, par exemple le nom d’un objet éditorial de SPIP
  • une ressource : l’identifiant unique d’un contenu

Ces trois paramètres sont tout simplement ajoutés à la suite dans l’URL, séparés par des « / ». Seul le premier est toujours obligatoire, les autres sont ajoutés suivant ce que l’on veut manipuler. On se retrouve alors avec trois familles d’URL. Par exemple avec le format « atom » :

  • http://site.example/http.api/atom
    désigne l’index du format, et peut être utilisé pour lister les collections réellement disponibles (si ce format prévoit ce genre d’index !)
  • http://site.example/http.api/atom/articles
    désigne la collection des articles, et doit donc fournir une liste de plusieurs articles, à priori tous s’il n’y a pas de paramètres de filtrages supplémentaires
  • http://site.example/http.api/atom/articles/1234
    désigne l’URL d’un article précis dans ce format

Pour chacun de ces URLs, on cherche enfin la méthode HTTP utilisée pour l’appeler : get, post, put ou delete. Suivant la conjonction de tous ces paramètres, le plugin appelle la bonne fonction.

Bien entendu, chaque développeur doit adapter ce cadre à l’API qu’il désire réellement implémenter.

Utilisation de la librairie HTTPFoundation

Petite précision : afin de ne pas gérer nous-mêmes les affres des requêtes et des réponses en HTTP, ce plugin intègre et utilise la librairie HTTPFoundation fournie par Symfony.

Vous trouverez la documentation de ce module par ici :
http://symfony.com/fr/doc/current/components/http_foundation/introduction.html

Ce plugin crée donc dès le début un objet Request et un objet Response, qui seront ensuite trimbalés et modifiés au fil du temps dans les fonctions d’implémentation.

Nos trois paramètres essentiels sont déjà ajoutés à l’objet Request dans la propriété des attributs persos :

  • $requete->attributes->get('format')
  • $requete->attributes->get('collection')
  • $requete->attributes->get('ressource')

Implémentation d’une API

Pour implémenter une API (la votre ou un standard reconnu du monde entier), vous devez tout d’abord créer un fichier /http/*format*.php où « *format* » est le nom donné à votre API.
Par exemple /http/atom.php (nous utiliserons cet exemple pour la suite).

Ensuite, vous devez créer une fonction pour chaque cas d’utilisation que vous souhaitez pouvoir gérer. Chacune de ces fonctions DOIT renvoyer l’objet Response valide, en l’ayant à priori modifié au passage, par exemple pour changer le statut de la réponse ($reponse->setStatusCode()), ou encore son contenu ($reponse->setContent()).

http_atom_get_index($requete, $reponse)

Vue générale d’un format, par exemple pour afficher la liste des collections disponibles.

Pour le format « atom » par exemple, il existe déjà une norme pour décrire cela. On trouve un exemple dans la documentation du standard « APP » (Atom Publishing Protocol) :
http://tools.ietf.org/html/rfc5023#section-8.2

http_atom_get_collection($requete, $reponse)

Liste le contenu de la collection dont le nom est dans la variable $requete->attributes->get('collection').

Pour le format « atom », ça serait un flux atom des articles (ou des résumés), par exemple.

Bien entendu, votre implémentation peut parfaitement accepter ou demander des paramètres supplémentaires, pour filtrer le contenu final. Tel que ?recherche=truc&id_rubrique=321.

http_atom_get_ressource($requete, $reponse)

La vue d’une ressource précise (un article par exemple), dans le format attendu. L’identifiant de la ressource (l’id_article si c’en est un) est dans la variable $requete->attributes->get('ressource').

http_atom_post_collection($requete, $reponse)

Créer une nouvelle ressource dans la collection (créer un nouvel article). Normalement, l’utilisateur devrait avoir envoyé une représentation de la ressource dans le format géré par votre API. Par exemple un article au format atom.

Vous devez donc savoir lire ce format, et et le traiter pour créer la ressource correspondante (ajouter un article dans la base).

http_atom_put_ressource($requete, $reponse)

Ce cas est sensiblement le même que le précédent, à ceci près que l’on travaille sur la modification d’une ressource précise. Tout comme les formulaires CVT « editer_truc » savent gérer à la fois la création et la modification, il y a de grandes chances pour que ces deux dernières fonctions soient souvent similaires.

http_atom_delete_ressource($requete, $reponse)

Supprimer définitivement une ressource. Attention ce n’est pas pareil que modifier un statut (qui se ferait avec PUT à priori). Cela dépend donc des usages et des objets. Par exemple par défaut dans SPIP, on ne supprime pas un article mais on change le statut à « poubelle », et quelques temps plus tard, une tâche de fond le supprimera réellement.

Gestion générique des erreurs

Lors de l’implémentation, une fonction supplémentaire est obligatoire, pour que le plugin HTTP sache quoi renvoyer s’il trouve une erreur avant vous (avant vos fonctions précises) :
http_atom_erreur_dist($code, $requete, $reponse)

Cette fonction prend en paramètre supplémentaire un code d’erreur HTTP (401, 404, etc) et doit savoir renvoyer une réponse, si possible dans le format en rapport avec votre API.

Pour le format « atom », il n’y a malheureusement pas de syntaxe pour décrire les erreurs. Mais dans d’autres format, en JSON ou autre, il existe des manières pour lister la ou les erreurs.

Lors que le plugin HTTP rencontre une erreur d’autorisations (401) ou qu’il ne trouve pas d’implémentation pour une URL (404), il utilise cette fonction générique pour renvoyer les erreurs plus proprement.

Cela ne vous empêche pas de générer d’autres erreurs plus complexes par la suite, dans vos fonctions d’implémentation.

Gestion des autorisations

Pour chaque action demandée, le plugin HTTP teste déjà une autorisation, avec comme paramètres :

  • le verbe HTTP accolé au type d’URL : « get_collection », « put_ressource », etc
  • le nom de la collection si présent : « articles », « patates »
  • l’identifiant de la ressource si présent : 1234

Comme on le voit, le format n’est pas présent dans le calcul de ces autorisations. En effet, on considère que si on peut faire une action (créer un article) en « atom », on peut le faire en « json » aussi, bref, quelque soit le format.

Par ailleurs, par défaut, le plugin défini déjà ces autorisations quelque soit le type de collection, en les calquant sur les fonctions déjà utilisées dans SPIP : « modifier », « creer », etc.

Dans la grande majorité des cas, vous n’aurez donc même pas à définir ces autorisations, car les utilisateurs de votre API auront tout simplement les mêmes droits que dans les interfaces de SPIP (ie : s’ils ne peuvent pas modifier tel article dans l’admin, ils ne pourront pas non plus par l’API).

Si vous devez malgré tout les préciser, l’une de vos autorisations pourrait donc ressembler à :
autoriser_article_put_ressource($faire, $quoi, $id, $qui, $opt)

Exemples

Un exemple d’implémentation presque complet est le plugin « Collection+JSON » qui propose de manipuler les objets du site dans ce format (JSON n’étant qu’un conteneur, la grammaire interne peut être n’importe quoi, ce format est une des manières de faire).
http://zone.spip.org/trac/spip-zone/browser/_plugins_/collection_json/trunk

Un autre exemple non complet, qui ne gère pour l’instant que les « GET », est le plugin « APP ». Celui ci propose d’aller chercher des squelettes /http/app/patates.html et /http/app/patates-ressource.html correspondant respectivement au flux de la collection « patates », et à la vue d’une seule patate, dans le format Atom.
http://zone.spip.org/trac/spip-zone/browser/_plugins_/app/trunk

Discussion

4 discussions

  • Répondre à ce message

  • 2
    titou from mars

    Bonjour,
    J’utilise ce plugin avec le plugin Collection+JSON. Pour les requêtes GET, pas de problème, ça marche nickel. Par contre pour les PUT/POST, je ne voie pas. C’est bloqué, ce qui normal vu que je ne suis pas authentifié, mais comment fait-on pour s’authentifier ? Je n’ai trouvé aucune doc la dessus, et l’inspection du code ne m’a rien révélé. J’ai bien vu un objet credentials.php dans collection+json, mais il a l’air de se contenter de vérifier qu’une session existe, ce qui en limiterai l’usage à quelqu’un déjà connecté à spip par les moyens "classiques.
    Une idée de comment faire ?

    • Bé par défaut avec l’authentification HTTP que SPIP sait déjà gérer tout seul.
      https://fr.wikipedia.org/wiki/Authentification_HTTP#M.C3.A9thode_.C2.AB_Basic_.C2.BB

      Authorization : Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

      Après on s’est déjà amusé à faire de l’auth en envoyant un token Facebook aussi, mais comme SPIP n’a pas de pipeline à cet endroit du code très en amont où SPIP gère l’auth, on a dû gérer avec le pipeline qui est au début du plugin HTTP justement, et donc c’est moyen générique car ça ne marche du coup que pour les API (ce qui est déjà pas mal ok). Du coup c’est juste dans le code du projet où on l’a utilisé pour l’instant.

    • titou from mars

      L’authentification http devrai suffire. Le site est en https, ça sera « acceptable » en terme de sécurité . Merci pour la réponse rapide (et le plugin !)

    Répondre à ce message

  • 2

    Hola Rasta ;

    J’avais besoin de faire remonter une variable d’un squelette inclus jusqu’au parent,
    pour qu’il puisse ensuite répercuter cette variable à un troisième squelette inclus.
    Globalement c’est ça…

                 __  Enfant A
                |      ↓
                |     /
                |    /
    Parent   ___| ←
                |  →
                |    \
                |     \
                |      ↓
                |__  Enfant B

    J’ai passé un certain temps à vouloir implémenter cette élégante solution
    https://contrib.spip.net/Astuces-longues-pour-SPIP#a1
    mais sans succès aucun. Pommé comme un pingouin chez le Minotaure.

    Donc, est-ce que je pourrais émettre un flux provenant de Enfant A que Enfant B pourrait lire ?
    Par exemple…

        <BOUCLE_ENFANT_A(ARTICLES){O,2}{doublons enfant_a}>
            Comment écrire (ou encore créer ou supprimer)
            dans le flux le résultat de cette boucle?        
        </BOUCLE_ENFANT_A>


    FLUX

        <BOUCLE_ENFANT_B(ARTICLES){doublons enfant_a}>
            Et comment le lire ensuite ?
        </BOUCLE_ENFANT_B>

    J’ai vu que en allant à
    #URL_SITE_SPIP/collectionjson/articles/1
    J’ai

    {
    "collection": {
      "version": "1.0",
      "href": "http:\/\/site\/http.api\/collectionjson\/articles\/1",
      "items": [{
        "href": "http:\/\/site\/http.api\/collectionjson\/articles\/1",
        "links": [{
          "rel": "edit",
          "href": "http:\/\/site\/http.api\/collectionjson\/articles\/1"
        }, {
          "rel": "alternate",
          "type": "text\/html",
          "href": "http:\/\/site\/spip.php?article1"
        }],
        "data": [{
          "name": "surtitre",
          "value": "Surtitre de l'article"
        }, {
          "name": "titre",
          "value": "Titre de l'article"
          }]
      }]
    
    }
    }

    Où il fait la différence entre edit (l’URI)

          "rel": "edit",
          "href": "http:\/\/site\/http.api\/collectionjson\/articles\/1"
    

    et alternate (l’URL)

          "rel": "alternate",
          "type": "text\/html",
          "href": "http:\/\/site\/spip.php?article1"

    Et là je coince. Il me faudrait un exemple concret

    • Bah elle est très bien l’astuce du premier lien. Faire un squelette dédié qui génère un tableau (serialize ou JSON peu importe) et l’appeler avec #INCLURE pour pouvoir le mettre dans un #SET.

      Je comprends le besoin expliqué au départ (et ça correspond bien à l’astuce en question), mais je ne vois pas en quoi le plugin HTTP ou le plugin Collection-JSON pourrait faire quelque chose de plus.

    • Pour faire quelque chose de plus, il faudrait que au moins ça arrive à marcher mal, parce que là, ça ne marche pas.
      J’ai un squelette parent et deux enfants : un qui a la première boucle (qui devrait émettre) et l’autre ayant la boucle qui devrait recevoir.
      Comme ça fait un moment que je bataille avec, je me suis dit que le REST était peut-être une piste. Et en plus, le plaisir d’apprendre un truc nouveau.
      Je me disais qu’il me fallait un exemple de boucles pour lire,créer, modifier et détruire (et une manière de sécuriser). Mais il se peut que ce soit trop compliqué pour moi. En tout cas, ce sûr que là je ne pige pas.

    Répondre à ce message

  • Très intéressant.
    Cela ouvre des perspectives pour la mobilité...

    Répondre à ce message

Ajouter un commentaire

Avant de faire part d’un problème sur un plugin X, merci de lire ce qui suit :

  • Désactiver tous les plugins que vous ne voulez pas tester afin de vous assurer que le bug vient bien du plugin X. Cela vous évitera d’écrire sur le forum d’une contribution qui n’est finalement pas en cause.
  • Cherchez et notez les numéros de version de tout ce qui est en place au moment du test :
    • version de SPIP, en bas de la partie privée
    • version du plugin testé et des éventuels plugins nécessités
    • version de PHP (exec=info en partie privée)
    • version de MySQL / SQLite
  • Si votre problème concerne la partie publique de votre site, donnez une URL où le bug est visible, pour que les gens puissent voir par eux-mêmes.
  • En cas de page blanche, merci d’activer l’affichage des erreurs, et d’indiquer ensuite l’erreur qui apparaît.

Merci d’avance pour les personnes qui vous aideront !

Par ailleurs, n’oubliez pas que les contributeurs et contributrices ont une vie en dehors de SPIP.

Qui êtes-vous ?
[Se connecter]

Pour afficher votre trombine avec votre message, enregistrez-la d’abord sur gravatar.com (gratuit et indolore) et n’oubliez pas d’indiquer votre adresse e-mail ici.

Ajoutez votre commentaire ici

Ce champ accepte les raccourcis SPIP {{gras}} {italique} -*liste [texte->url] <quote> <code> et le code HTML <q> <del> <ins>. Pour créer des paragraphes, laissez simplement des lignes vides.

Ajouter un document

Suivre les commentaires : RSS 2.0 | Atom