Pages

mercredi 13 février 2013

Serveur d'application allégé : pourquoi faire compliqué quand on peut faire simple

Je voulais mettre l'accent sur une réflexion que j'ai eue dernièrement, lorsque j'ai voulu mettre en place un POC sur une fonctionnalité web. Inconsciemment les réflexes du quotidien m'ont fait créer une webapp avec Spring.... Mon but initial n'était que de lancer une page pour valider un concept, et je me retrouve à devoir déployer un war dans un serveur d'application avec une multitude de librairies pour au final faire un traitement très simple.

Ce réflexe est malheureusement partagé par un grand nombre de développeurs ou d'architectes et les habitudes sont parfois la cause de la complexité inutile de certaines applications. Nous avons la chance en tant que développeur dans l'écosystème Java de disposer d'une multitude de librairies pour ne pas réinventer la poudre à chaque projet, mais cette richesse nous fait parfois oublié l'essentiel et les bases.

Revenons sur mon besoin : disposer d'un serveur web permettant de lancer une page web contenant un formulaire et de récupérer le résultat saisi par l'utilisateur pour lui afficher un résultat. Je vous propose ci dessous la solution que j'ai appliquée pour mon besoin basée sur la classe du JDK com.sun.net.httpserver.HttpServer.

Au niveau rendu visuel la première page permet de saisir un nom 


et un clic sur le bouton « Envoyer » affiche une page saluant le nom saisi.


Au niveau du code j'utilise une classe permettant de lancer le serveur

public class MyHttpServer {

    /**
     * Cette méthode permet de lancer notre serveur
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        //Creation d'un serveur HTTP
        HttpServer server = HttpServer.create(new InetSocketAddress(9998),0);
        server.createContext("/", new HttpHandler() {
            @Override
            public void handle(HttpExchange httpExchange) throws IOException {
                byte[] response = null;
                int codeRetourHttp = HttpURLConnection.HTTP_OK;

                //Dans le cas d'un appel simple j'affiche mon formulaire
                try {
                    if("/hello".equals(httpExchange.getRequestURI().getPath())){
                        response = Strings.nullToEmpty(
                                TemplateLoader.getPageFromVelocityTemplate("/hello.html",
                                HttpParams.getParameters(httpExchange.getRequestURI().getQuery())))
                                    .getBytes();
                    }
                    else{
                        response = Files.readAllBytes(Paths.get(this.getClass().getResource("/index.html").toURI()));
                    }
                }
                catch (Exception e) {
                    response = e.getMessage().getBytes();
                    codeRetourHttp = HttpURLConnection.HTTP_INTERNAL_ERROR;
                    return;
                }

                httpExchange.sendResponseHeaders(codeRetourHttp, response.length);
                httpExchange.getResponseBody().write(response);
                httpExchange.close();
            }


        });

        //Demarrage du serveur
        server.start();
    }
}



Cette classe utilise deux classes utilitaires. La première permet de placer les paramètres passés dans la query dans une Map.

public class HttpParams {
    /**
     * Lecture des paramètres qui sont placés dans une map
     * @param query
     * @return
     */
    protected static Map<String,String> getParameters(String query){
        Map<String,String> parameters = new HashMap<>();
        if(query!=null){
            Iterable<String> couples = Splitter.on("&").split(query);
            for(String param : couples){
                if(!Strings.isNullOrEmpty(param)){
                    Iterable<String> element = Splitter.on("=").split(param);
                    Iterator<String> iterator = element.iterator();
                    parameters.put(iterator.next(), iterator.next());
                }
            }
        }

        return parameters;
    }
}

La seconde utilise Velocity pour charger un template de page auquel on passe des paramètres

public class TemplateLoader {
    private static VelocityEngine velocityEngine;

    /**
     * Chargement du template Velocity
     * @param nomTemplate
     * @param parameters
     * @return
     */
    protected static String getPageFromVelocityTemplate(String nomTemplate, Map<String, ?> parameters) throws Exception{
        Preconditions.checkNotNull(nomTemplate);
        if(velocityEngine==null){
            initTemplateEngine();
        }
        Template t = velocityEngine.getTemplate(nomTemplate);
        try(StringWriter writer = new StringWriter()){
            t.merge(new VelocityContext(parameters), writer);
            return writer.toString();
        }
    }

    /**
     * Initialisation du moteur de template velocity
     * @throws Exception
     */
    private static void initTemplateEngine() throws Exception {
        Properties props = new Properties();
        props.put("resource.loader", "class");
        props.put("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        velocityEngine = new VelocityEngine();
        velocityEngine.init(props);
    }
}

La page HTML d'entrée est celle ci

<!DOCTYPE html>
<html lang="en">
<head>
    <link href="http://twitter.github.com/bootstrap/1.4.0/bootstrap.min.css" rel="stylesheet"  media="screen"/>
    <script src="http://code.jquery.com/jquery-1.9.1.min.js" language="JavaScript"></script>
</head>
<body>
<div class="container">
    <h1>Javamind : exemple serveur application</h1>
    <p>Cette page statique fait partie de l'article permettant de montrer la mise en place
    d'un serveur d'application simplifi&eacute;</p>
   <fieldset>
    <div class="control-group">
        <label class="control-label" for="inputNom">Votre nom &nbsp;&nbsp;&nbsp;</label>
        <div class="controls">
            <input type="text" id="inputNom" placeholder="Nom" >
        </div>
    </div>
        <br>
    <div class="control-group">
        <label class="control-label" for="btnSubmit">&nbsp;</label>
        <div class="controls">
            <button class="btn btn-info" id="btnSubmit" onclick="location.href='/hello?name='+$('#inputNom').val()">Envoyer</button>
        </div>
    </div>
    </fieldset>
</div>
</body>
</html>

et le template Velocity est le suivant

<!DOCTYPE html>
<html lang="en">
<head>
    <link href="http://twitter.github.com/bootstrap/1.4.0/bootstrap.min.css" rel="stylesheet"  media="screen"/>
    <script src="http://code.jquery.com/jquery-1.9.1.min.js" language="JavaScript"></script>
</head>
<body>
<div class="container">
    <h1>Javamind : exemple serveur application</h1>
    <p>Cette page statique fait partie de l'article permettant de montrer la mise en place
    d'un serveur d'application simplifi&eacute;</p>
   <p>
    <h3>Bonjour $name</h3>
    </p>
</div>
</body>
</html>

Cet exemple est vraiment simple mais il a l'avantage de lancer un serveur d'application en moins de 5 secondes et de n'utiliser aucune autre dépendance que le JDK. En conclusion je pense qu'il faut retenir que pour des besoins simples il faut veiller à ne pas créer des solutions trop complexes. Cet exemple pourrait être adapté par exemple pour démarrer un serveur Web exposant des services rest. Pour des appels REST Stateless on a pas forcément besoin de déployer une machine de guerre.

Aucun commentaire:

Enregistrer un commentaire

Remarque : Seul un membre de ce blog est autorisé à enregistrer un commentaire.