Objectif : mettre en ligne une application très simple en utilisant GAE (Google App Engine) et Clojure.
Spécifications :
- afficher "HelloWorld" sur la page d'accès;
- réécrire les fichiers Java en Clojure.
- avoir un compte google actif;
- sdk Google Appengine installé. cf partie 01;
- ant installé. cf partie 01.
1- Téléchargement / installation de ClojureBox (Clojure version 1.2)
Clojure Box permet d'installer très facilement sous Windows : Clojure, Emacs et swank. Swank permet la communication entre Emacs et Clojure. Emacs sera plus qu'un éditeur de texte, il sera notre IDE.
2- Code source
L'objectif est de traduire le code java que nous avons utilisé la dernière fois en code Clojure. Code source de la Servlet (src/test/java/helloworld/MyServlet.java) en Java :
package test.java.helloworld;
import java.io.IOException;
import javax.servlet.http.*;
public class MyServlet extends HttpServlet
{
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException
{
resp.setContentType("text/plain");
resp.getWriter().println("My Hello, world !");
}
}
Voici la traduction en Clojure (src/test/clojure/helloworld/MyServlet.clj) :
(ns test.clojure.helloworld.MyServlet
(:gen-class :extends javax.servlet.http.HttpServlet
:main false ))
(defn -doGet [_ req resp]
(.setContentType resp "text/plain")
(.println (.getWriter resp) "My Clojure/GAE Hello, world !") )
a- La directive ':gen-class' indique au compilateur de générer du code JVM. Ce code correspond à une classe java 'test.clojure.helloworld.MyServlet'. Celle-ci n'aura pas de méthode 'main' (':main false') et aura une méthode doGet à deux arguments.
b- La méthode 'doGet' sera déduite de la fonction à trois arguments '-doGet'. Le '-' signifie au compilateur d'en faire une méthode. Le premier argument représente l'objet appliqué à la méthode : un objet de type MyServlet. Ici, comme il n'est pas utilisé dans le corps de la fonction, il est dénommé '_'.
A noter que le type des arguments n'est pas spécifié ici, même si Clojure le permet (notation méta). Le type d'exception n'est pas spécifié non plus. Concernant la transformation java => clojure, je n'ai pas encore regardé ce qui peut/doit être spécifié ni comment. En effet, seuls les points d'entrée devront être codés en pseudo-java, tout le reste sera en Clojure.
3- Mise à jour du fichier web.xml et du fichier index.html
Nous voulons que ce soit le test2 de notre application et que l'utilisateur puisse choisir le test à lancer à partir de la page d'accueil.
Ajouts au fichier "WEB-INF/web.xml" :
<web-app>
...
<servlet>
<servlet-name>helloworld-clojure</servlet-name>
<servlet-class>test.clojure.helloworld.MyServlet</servlet-class>
</servlet>
...
<servlet-mapping>
<servlet-name>helloworld-clojure</servlet-name>
<url-pattern>/test2</url-pattern>
</servlet-mapping>
...
</web-app>
Modifications de la page d'accueil (html/index.html) :
<html>
<body>
<h1>Accès aux applications de tests !</h1>
<ul>
<li><a href="test1">Test Hello world en Java !</a>
<li><a href="test2">Test Hello world en Clojure !</a>
</ul>
</body>
</html>
4- Compilation, fichier build.xml et fichiers .jar
Voici la liste des commandes "ant" que nous souhaitons utiliser :
- ant copyjars : recopie les librairies java nécessaires (GAE + clojure).
- ant compile1 : compile les fichiers java
- ant compile2 : compile les servlets clojure. recopie les fichiers .clj dans l'arborescence war.
- ant static : copie les fichiers statiques (web-inf, html, css, xml, js, ...) dans le répertoire war.
- ant build1 : met à jour de façon incrémentale l'application test1 dans le dossier "war". static,compile1.
- ant build2 : met à jour de façon incrémentale l'application test2 dans le dossier "war". static,compile2.
- ant launch : lance le serveur de développement appengine.
- ant run1 : build1, launch.
- ant run2 : build2, launch.
- ant run : build1, build2, launch.
- ant clean : supprime tous les fichiers / répertoires de war.
- ant deploy : déploie l'application sur le cloud.
<project>
<property name="sdk.dir" location="D:/outils/appengine-java-sdk-1.4.0" />
<property name="clojure.dir" location="C:/Program Files (x86)/Clojure Box" />
<import file="${sdk.dir}/config/user/ant-macros.xml" />
<path id="project.classpath">
<pathelement path="war/WEB-INF/classes" />
<fileset dir="war/WEB-INF/lib">
<include name="**/*.jar" />
</fileset>
<fileset dir="${sdk.dir}/lib">
<include name="shared/**/*.jar" />
</fileset>
</path>
<!-- ********************** targets **************************** -->
<target name="copyjars"
description="Copies the JARs to the WAR.">
<mkdir dir="war/WEB-INF/lib" />
<copy todir="war/WEB-INF/lib"
flatten="true">
<fileset dir="${sdk.dir}/lib/user">
<include name="**/*.jar" />
</fileset>
</copy>
<copy todir="war/WEB-INF/lib"
flatten="true">
<fileset dir="${clojure.dir}/lib">
<include name="*.jar" />
</fileset>
</copy>
</target>
<target name="compile1" depends="copyjars"
description="Compiles Java source to the WAR.">
<mkdir dir="war/WEB-INF/classes" />
<javac
srcdir="src/test/java/helloworld"
destdir="war/WEB-INF/classes"
classpathref="project.classpath"
includeantruntime="false"
debug="on" />
</target>
<target name="compile2" depends="copyjars"
description="Compiles Clojure Servlet and copies .clj source files to the WAR.">
<mkdir dir="war/WEB-INF/classes/test/clojure" />
<copy todir="war/WEB-INF/classes/test/clojure">
<fileset dir="src/test/clojure">
<include name="**/*.clj" />
</fileset>
</copy>
<java classname = "clojure.lang.Compile"
classpathref = "project.classpath"
failonerror = "true" >
<classpath path="src" />
<sysproperty key="clojure.compile.path" value="war/WEB-INF/classes"/>
<arg value="test.clojure.helloworld.MyServlet" />
</java>
</target>
<target name="static"
description="Copies the static files to the WAR (deployement, html).">
<mkdir dir="war/WEB-INF" />
<copy todir="war/WEB-INF">
<fileset dir="WEB-INF"/>
</copy>
<copy todir="war">
<fileset dir="html"/>
</copy>
</target>
<target name="build1" depends="static,compile1"
description="test1 application update.">
</target>
<target name="build2" depends="static,compile2"
description="test2 application update.">
</target>
<target name="launch"
description="Starts the development server">
<!-- dev_appserver2 = fixed dev_appserver (bug 2093: ctrl-c doesn't stop server)
cf appengine...config/user/ant-macros.xml -->
<dev_appserver2 war="war" address="0.0.0.0"/>
</target>
<target name="run1" depends="build1,launch"
description="compile and start test1 application">
</target>
<target name="run2" depends="build2,launch"
description="compile and start test2 application">
</target>
<target name="run" depends="build1,build2,launch"
description="build and start test1/test2 application">
</target>
<target name="clean"
description="delete all war files and directories" >
<delete dir="war"/>
</target>
<target name="deploy"
description="Deploy this application on the cloud." >
<appcfg action="update" war="war"/>
</target>
</project>
A noter la compilation Clojure de la servlet :
<target name="compile2" depends="copyjars"
description="Compiles Clojure Servlet and copies .clj source files to the WAR.">
...
<java classname = "clojure.lang.Compile"
classpathref = "project.classpath"
failonerror = "true" >
<classpath path="src" />
<sysproperty key="clojure.compile.path" value="war/WEB-INF/classes"/>
<arg value="test.clojure.helloworld.MyServlet" />
</java>
</target>
L'inner tag "classpath" se concatène avec le classpathref. Vu que les fichiers .clj sont recopiés sous "war/web-inf/classes", je pense que cet ajout est superflu, à tester ... La propriété système "clojure.compile.path" indique à Clojure où celui-ci doit créer les fichiers compilés. La commande "ant run" doit maintenant compiler test1 et test2 puis lancer le serveur de test local.
La prochaine fois on essaiera de réaliser l'ensemble du tutoriel GAE en Clojure.


Aucun commentaire:
Enregistrer un commentaire