JavaScript côté Serveur

« Où est le temps pour la lecture et la recherche ? Où est le temps pour apprendre à se documenter ? Où est le temps pour la réflexion individuelle et collective ? ». Jack Lang.

Lorsqu'on m'a parlé de JavaScript côté serveur, j'ai cru qu'il s'agissait d'une nouveauté. Ce n'était pas le cas ; dans l'équipe R&D, certains utilisaient déjà du Javascript côté serveur dans les années 97-98 avec le serveur resin de Caucho.

Une introduction, pour ceux qui aiment les introductions

Qui dit JavaScript, dit navigateur !

Le JavaScript est l’un des langages les plus populaires, si ce n’est pas le plus populaire. Pratiquement, toute application web utilise JavaScript. Le plus utilisé, mais aussi le plus méprisé.

Jusqu’à un passé proche, qui dit JavaScript, dit navigateur. Avec la différence et les incompatibilités entre les moteurs JavaScript des navigateurs, le développement JavaScript demeure pénible! Interprété et exécuté côté client, plusieurs restrictions sont imposées à ce langage. Et si on passait au JavaScript côté Serveur ?

L’idée date de 1996 lorsque NetScape a implémenté le premier composant JavaScript coté serveur. Ce composant baptisé LiveWire a été inclus dans le serveur Enterprise Server 2.0. Puis, Netscape a planifié de développer un navigateur totalement en Java. Pour ce faire, elle a eu besoin d’une implémentation JavaScript en Java, une implémentation qui entrainera après la naissance de Rhino, un interpréteur JavaScript côté serveur.

Bien que l’idée a été abandonnée dans les années suivantes, on assiste aujourd’hui à des tentatives plus sérieuses pour utiliser JavaScript coté serveur, dans l’optique de ne plus avoir à se préoccuper de savoir quel navigateur exécutera le code, et d’autre part d’avoir moins de restrictions imposées au langage.

JavaScript côté serveur, pourquoi?

Aujourd'hui, des sérieuses tentatives visent à concevoir et implémenter des serveurs JavaScript. Ceci est du aux avantages qu'offre ce langage côté serveur.

  • Côté fonctionnel
    • Couche d'abstraction des moteurs JavaScript des navigateurs: certains serveurs JavaScript proposent une implémentation du DOM, ce qui offre une couche d'abstraction supplémentaire du navigateur et une souplesse de développement.
    • Optimisation du temps de développement: une des utilités de JavaScript est la vérification des données côté client. Or, en général, et pour des raisons de sécurité, de cohérence et d'intégrité des données, ces tests sont vérifiés une deuxième fois côté serveur. Les vérifications sont donc ré-implémentées en langage serveur (java, .net...). Avec JavaScript côté serveur, il n'est plus nécessaire de redéfinir ces tests, il suffit d'exécuter le même code JavaScript, ce qui permet d'augmenter la productivité.
    • Minimisation des coûts: JavaScript est omniprésent dans presque toutes les applications web. Ainsi, le développement d'une application web nécessite une double compétence: JavaScript/langage serveur. Cette double compétence a son coût (soit en termes de connaissances, soit en termes du nombre de développeurs).
  • Côté technique
    • Rapidité des tests: JavaScript côté serveur permet d'accélérer les phases de tests. D'une part, il est plus facile d'utiliser les objets mock en JavaScript. D'autre part, pour tester le fonctionnement d'une application on n'a pas besoin d'une phase de compilation et redéploiement, comme en java par exemple. Il suffit d'interpréter directement le code écrit, ce qui permet d'améliorer la productivité.
    • Moins de code à écrire: Avec son son paradigme de prototypage, JavaScript permet de développer les mêmes fonctionnalités que d'autres langages de développement mais avec moins de code (jusqu'à un dixième de code).
    • Plus d'API de développement: toutes les limites du langage JavaScript imposées par les restrictions de sécurité ne sont plus appliquées côté serveur.

Les principales alternatives et solutions JavaScript côté serveur sont:

RHINO

Introduction

Rhino est une implémentation open-source (MPL 1.1/GPL 2.0) de JavaScript écrit entièrement en Java. Cette implémentation est généralement utilisée coté serveur. A début Rhino a été conçu pour compiler le code JavaScript en code bytecode Java. Un mécanisme qui présente des performances qui rivalisent celles de Spider Monkey. Cependant, ce processus consomme beaucoup de temps pour la compilation du code, en plus le bytecode généré peut être cause de fuites mémoires. Un autre problème est dû au fait que la JVM limite la taille des méthodes à 64 KB, ce qui peut générer des exceptions si jamais la compilation du code JavaScript génère des méthodes dont les tailles dépassent cette limite. Ainsi, un nouveau mode a été développé : le mode interprété (script compilé en objets JavaScript). Avec ce mode, l’interprétation est plus rapide, mais l’exécution est plus lente. Le choix d’un mode ou de l’autre dépend des fonctionnalités du code.

Un peu de pratique

Rhino fait partie de la JDK 1.6 et ses successeurs. Pour la JDK 1.5 il faut télécharger et ajouter js.jar. Pour la JDK 1.4 il faut utiliser js-14.jar.

Rhino peut être utilisé en mode shell ou intégré dans une application. Le mode shell sert essentiellement à faire les tests (avec un déboggeur et un compilateur). Dans ce qui suit je présenterai comment intégrer Rhino dans une application java. Je rappelle que cette partie est à titre indicatif, d’autres possibilités et fonctionnalités restent à étudier.

La communication entre un objet java et l’interpréteur défini par l’implémentation Rhino se fait à travers un contexte d’exécution.

Contex context= new ContextFactory().enterContext() ;

Chaque interprétation a une porté. Le développeur doit définir la portée (scope) de l’interprétation.

Scriptable scope = context.initStandardObjects();

Scriptable scope = context.initStandardObjects(this); // ajoute l'objet lui-même au scope, l'objet doit étendre ScriptableObject.

Le scope permet de stocker les variables d’inter-échange entre la classe java et le code JavaScript:

<blockquote>

Scriptable jsArgs1 = context.toObject(request, scope);

scope.put("httpRequest", scope, jsArgs1);

On peut également définir des propriétés et des fonctions accessibles depuis le code JavaScript:

String[] names = { "readFile","loadSystem" };

defineFunctionProperties(names,  MyClass.class,  ScriptableObject.PERMANENT);

defineFunctionProperties introspecte la classe MyClass pour chercher les méthodes dont le nom est dans le tableau names et les ajouter comme propriétés. Dans JavaScript, il suffit d'appeler directement la méthode avec la liste des paramètres.

On peut aussi accéder a des variables et fonctions définies dans le code JavaScript.

Object fObj = scope.get("f", scope);

if (!(fObj instanceof Function)) {

System.out.println("f is undefined or not a function.");

} else {

Object functionArgs[] = { "my arg" };

Function f = (Function)fObj;

Object result = f.call(context, scope, scope, functionArgs);

String report = "f('my args') = " + context.toString(result);

System.out.println(report);

}

A noter

  • Savoir utiliser le mode compilé et le mode interprété (paramétrage de l’optimiseur, par défaut le mode est interprété).
  • Pas de fonctionnalités de manipulation de DOM (quelques implémentation et tentatives env-js).
  • Supporte JSON, E4X.

Un exemple plus détaillé

Dans cet exemple, on met l'accent sur les principaux aspects et fonctionnalités de RHINO. Il résume les principales étapes pour créer un projet RHINO et le faire tourner dans une application web.

L'exemple est consultable ici.