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.

APTANA JAXER

Introduction

Aptana Jaxer est un serveur JavaScript open source sous licence GPL et qui fournit des fonctionnalités avancées en JavaScript. Jaxer est basé sur le moteur Spider Monkey de Mozilla, ainsi, il a la possibilité de manipuler du DOM sans retourner un rendu graphique. La manipulation des HTML, CSS, JavaScript, XmlHttpRequest, est définie nativement dans Jaxer. De plus, Jaxer possède une API riche qui définit des fonctionnalités d'accès aux bases de données, au fichiers systèmes, RESTful, sécurité...

L'avantage de Jaxer est qu'il se base sur le même modèle de développement des clients classiques (JavaScript côté Client). La grande nouveauté au niveau du codage, c'est que l'utilisateur peut choisir d'exécuter son code coté client ou côté serveur.

<script runat="server">

 var resultset=Jaxer.DB.execute("Select * from myTable");

</script>

Architecture de Jaxer

Jaxer est un Serveur JavaScript qui doit être couplé à un serveur web ou applicatif. Par défaut, la version est couplée à un serveur Apache.

Jaxer est composé essentiellement de deux modules :

  • un pont (bridge) qui permet d'assurer la communication avec le serveur avec lequel il est couplé, et
  • une API qui définit un ensemble riche de fonctionnalités.

Le cycle de vie d'une page est décrit par la figure suivante (source: site officiel de Jaxer : http://www.jaxer.org/):

Le Serveur web reçoit la requête client et choisit le document HTML approprié et le transmet au serveur Jaxer.

Jaxer parcourt le document et construit son propre DOM (noté DOM1). Jaxer interprète tout script JavaScript dont l'attribut runat est identifié à "server" ou "both" ou "server-proxy". Ainsi, certaines parties du code HTML rendu au navigateur peuvent être générés par le serveur (Sérialisation du DOM1 en HTML).

Étudions l'exemple suivant:

....

....

<script runat="server">

document.getElementById("tableau").innerHTML="<p>Bienvenue chez IdeoTechnologies.</p>"

</script>

....

....

Cette portion du code sera interprétée par le serveur seulement (puisque runat=server) . Le code reçu par le navigateur sera le résultat de l'interprétation du code. Les blocs qui doivent être exécutés coté Serveur uniquement sont supprimés du code HTML généré pour le poste client.

Jaxer retourne le document HTML généré au navigateur qui construit son propre DOM (DOM2). Une fois, le document HTML est envoyé, le DOM1 est supprimé.

Lorsque le client appelle des fonctions définies en tant que Server-Proxy, Jaxer construit un troisième DOM (DOM3), pour manipuler les requêtes reçues.

Noter que le DOM3 n'a rien avoir avec DOM2 et DOM1. Seul le DOM2 reste persistant pendant le cycle de vie d'une page. Le serveur Jaxer n'a aucune action sur le DOM généré par le navigateur.

Cette manipulation du DOM propre à Jaxer peut entrainer une dégradation de performances.

Jaxer est téléchargeable sur l'adresse : http://jaxer.org/download

Comme je l'ai mentionné, le point le plus intéressant de Jaxer est qu'il permet de développer des page web suivant le même modèle classique de développement mais en spécifiant où le code JavaScript doit être exécuté. Ceci revient à définir la valeur de l'attribut runat de la balise script.

Cet attribut peut prendre les valeurs suivantes :

Option

Description

Server

Script interprété et exécuté uniquement côté serveur.

Client

Script interprété et exécuté uniquement côté client.

Both

Script interprété et exécuté côté serveur et client.

server-proxy

Script interprété et exécuté uniquement côté serveur. Le client peut à travers ce proxy lancer l'exécution de ce script.

both-proxy

Script interprété et exécuté côté serveur et client. Possibilité d'appeler le code côté serveur par le client.

server-nocache

Script présent uniquement à la phase de construction du DOM1

both-nocache

Script interprété et exécuté côté serveur et client. Possibilité d'appeler le code côté serveur par le client uniquement dans la phase de construction du DOM1.

Avantages

  • Facilité d'utilisation.
  • Accès aux bases de données, accès aux fichiers systèmes, Sécurité, Sandboxing, RESTful, Cloud...
  • Résolution de certains problèmes de compatibilités entre les moteurs JavaScript des navigateurs.
  • Supporte JSON, JavaScript 1.5, 1.7 et 1.8.
  • OpenSource, extensible.

Inconvénient

  • Une éventuelle dégradation des performance (que nous n'avons pas mesuré)
  • Absence de multithreading (Web Workers côté Navigateurs) bien que L'API définit la notion de thread mais avec une seule méthode wait().

Pour plus d'information : http://jaxer.org/