JSON-RPC et Node.js

Si vous avez bien suivi, j'ai commencé un projet utilisant expressjs coté serveur, et angular coté client. Les deux communiquent grâce à une API JSON-RPC. Pourquoi JSON-RPC et pas simplement REST ? Pour plusieurs raisons :

  • La licence de JSON interdit de faire le mal
  • JSON-RPC permet une grande liberté dans la structuration des paramètres envoyés à la requête. Contrairement à REST qui ne prendra qu'une liste de paramètres à plat, JSON-RPC envoie un objet formaté en JSON qui peut alors prendre la forme que vous voulez, contenant plusieurs niveaux de profondeurs, des tableaux, des entiers, des booléens…
  • JSON-RPC permet d'envoyer des batch de requêtes afin de demander plusieurs choses différentes en une seule requête HTTP
  • JSON-RPC est sécurisé contre les attaques XSS. En effet, il est impossible de forger une requête JSON-RPC depuis un formulaire qui pointerait vers un autre domaine. On ne peut que utiliser AJAX. L'attaquant devrait alors trouver un moyen d'éxécuter son script sur le même domaine que l'API. Évidemment, si on ouvre les portes de cette API avec CORS, cet argument tombe à l'eau. On passe alors à l'argument suivant :
  • JSON-RPC propose un système d'identifiant de requête qui peut être utilisé pour faire passer un token que le serveur aura préalablement envoyé au client avant chacune de ses requêtes.

Bref, l'article ne concerne pas la spécification de JSON-RPC mais son implémentation dans Node.js et Express.js.

Jayson

J'ai d'abord utilisé le module qui m'a semblé le moins buggué sur npm : jayson. Contrairement aux autres que j'ai rapidement essayé, il ne fait pas planter l'application si le client envoie un JSON mal formaté. Et en plus, il propose un middleware pour se rattacher à Express.js ou Connect.js. Mais il possède de nombreux inconvénients.

Tout d'abord, il ne fonctionne qu'avec un design pattern modulaire. Pour soit disant simplifier la tâche du développeur, il génère à la volée les paramètres passés à la fonction de callback de la méthode selon ce que contient l'objet de paramètres. Ainsi, il est impossible d'avoir une définition de paramètres figés. Par exemple, si on lui dit que la méthode user.get aura pour callback :

function(name, password, callback) {}

Si le client envoie la requête suivante :

{"jsonrpc":"2.0","method":"user.get","params":{"name":"kikoo"},"id":1}

Alors la fonction ne prendra que le paramètre name et le paramètre callback. Et dans votre fonction, le callback ne sera plus callback mais password.

En bref, ce qu'ils ont pris pour une simplification est en réalité une énorme contrainte qui ne colle pas du tout avec la philosophie du JSON-RPC.

Un autre point très agaçant que j'ai tenté de résoudre à l'aide d'un middleware dédié, c'est que le callback ne prend pas en paramètre l'objet Request, ni l'objet Response. Il ne prend que la liste des paramètres contenu dans les paramètres du JSON. Ce qui veut dire qu'on n'a aucune visibilité sur le reste de la requête. Impossible de lire les headers ou les cookies et donc, impossible de gérer une session. C'est un bug qui a été reporté mais jamais corrigé, parce que.

Alors je me suis débrouillé avec mon middleware dédié à la sécurité, mais c'était franchement pas pratique.

En plus, impossible de structurer son code autrement que via des fonctions anonymes. J'envisageais de structurer mes requêtes sous forme de contrôleurs par thématique : les méthodes liées au user regroupées dans un contrôleur nommé UserController qui proposerait des méthodes nommées nomdelamethodeMethod. Alors, j'ai fait des modules, mais ça reste un objet contenant une liste de fonctions statiques sans aucun lien entre elles.

Du coup, j'ai fini par écrire un truc from scratch. Et ça marche bien mieux pour mon projet.

JSON-RPC et Node.js

Jissonrpc

Voilà le nom de mon module node que je viens de publier à l'instant sur geekli.st (oui, c'est trop mainstream Github…).

Que fait-il de mieux que Jayson ?

  • Déjà, il ne fait pas crasher l'application. Tout est try catché et renvoit des erreurs intelligibles au client et à la console. Enfin, si ça crashe, il faudra me faire un rapport de bug, parce que ça ne devrait pas.
  • Il propose une architecture orientée objet où chaque requête est constituée de deux mots clés séparés par un point. ex : "user.get". Le premier mot clé étant le nom du contrôleur ou service où se trouve la méthode. Le second est le nom de la méthode. "user.get" ira donc appeller la méthode getMethod du service user.js.
  • Chaque méthode possède une liste de paramètres fixes : params, callback, req et res. params est l'objet params tel qu'il a été envoyé par le client. callback est la fonction a appeler à la fin du traitement en lui passant un objet d'erreur ou un objet de réponse. req et res sont les objects request et response envoyés par le serveur http qui sont purement optionnels et même déconseillés d'utiliser, MAIS ! qu'on peut quand même utiliser si necessaire. On peut alors accéder à req.session par exemple. On peut aussi court-circuiter le process normal de JSON-RPC pour renvoyer une erreur 401.

Bon, c'est tout, le reste existait aussi dans jayson :

  • Gestion des erreurs liées au protocole JSON-RPC
  • Gestion de requêtes multiple (batch)

Vous pouvez l'utiliser grâce à npm d'un coup de :

npm install jisson-rpc

Hadrien

Hi, I'm a french Javascript Lead Developer, Web Architect from Toulouse, France. I've worked for 12 years for many projects with YUI, AngularJS, Aurelia.io and now React and React native.

Toulouse, France https://hadrien.eu