<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[Category: java | Jetoile]]></title>
  <link href="https://blog.jetoile.fr/blog/categories/java/atom.xml" rel="self"/>
  <link href="https://blog.jetoile.fr/"/>
  <updated>2018-10-28T18:01:31+01:00</updated>
  <id>https://blog.jetoile.fr/</id>
  <author>
    <name><![CDATA[Khanh Maudoux]]></name>
    
  </author>
  <generator uri="http://octopress.org/">Octopress</generator>

  
  <entry>
    <title type="html"><![CDATA[Template de projets REST]]></title>
    <link href="https://blog.jetoile.fr/2015/10/template-de-projets-rest.html"/>
    <updated>2015-10-07T17:33:08+02:00</updated>
    <id>https://blog.jetoile.fr/2015/10/template-de-projets-rest</id>
    <content type="html"><![CDATA[<p><img src="/images/template_rest.png" alt="left-small" />
Il y a déjà un long moment, j'avais posté une série d'article expliquant comment il était possible de faire des web service de type REST de manière simple via <a href="http://blog.jetoile.fr/2014/03/jaxrs-netty-et-bien-plus-encore-mode.html">RestEasy-Netty</a> ou via <a href="http://blog.jetoile.fr/2015/06/undertow-pour-booster-vos-services-rest.html">Undertow</a>.</p>

<p>Dans la continuité de cette course au plus léger, je me suis dit que cela pouvait être intéressant de faire une petite étude un peu plus exhaustive des solutions légères qui existaient.</p>

<p>L'objectif étant extraire une sorte de bench un peu naïf et un peu <em>out of the box</em>. Parmi les solutions retenues, il y a :</p>

<ul>
<li>Resteasy-Netty</li>
<li>Resteasy-Undertow</li>
<li>Restlet</li>
<li>SpringBoot</li>
<li>Resteasy sur Tomcat en utilisant ses connecteurs NIO</li>
<li>Resteasy sur Jetty</li>
</ul>


<p>Cette article est là pour restituer mes résultats&hellip;</p>

<!-- more -->


<p>En fait, non&hellip; j'ai menti puisque je ne ferai aucun retour mais que je donnerai seulement le lien vers mon github où il est possible de trouver ces <em>bootstrap</em> de projets&hellip;</p>

<p>En effet, faire un <em>bench</em> est dangereux et complexe surtout quand toutes les implémentations ne sont pas maitrisées et qu'un <em>tuning</em> de ces dernières peut grandement modifier le résultat.</p>

<p>En outre, avoir un service exhaustif (autre que un simple <em>helloword</em>) qui est représentatif d'une vrai application et qui ne fait pas que taper dans le cache de la JVM ou de l'OS est plus complexe qu'écrire un simple <em>sample</em>.</p>

<p>Enfin, par manque de moyen (2 ordinateurs reliés par un wifi capricieux et par flemme de me monter des environnements plus représentatifs), je n'ai pu obtenir de résultats fiables&hellip;</p>

<p>Aussi, ci-joint les <em>repos</em> où il est possible de trouver le code (qui se veut ultra simple et qui a été fait sans chercher l'optimisation et sur un coin de table donc si des bourdes ont été faites, je m'en excuse&hellip;) :</p>

<ul>
<li><a href="https://github.com/jetoile/resteasy-netty-sample">Sample RestEasy-Netty</a></li>
<li><a href="https://github.com/jetoile/dropwizard-sample">Sample Dropwizard</a></li>
<li><a href="https://github.com/jetoile/restlet-sample">Sample Restlet</a></li>
<li><a href="https://github.com/jetoile/undertow-sample">Sample RestReasy-Undertow</a></li>
<li><a href="https://github.com/jetoile/tomcat-resteasy-sample">Sample Tomcat/Jetty</a> : simple webapp à déployer dans les conteneurs avec les bonnes options</li>
<li><a href="https://github.com/jetoile/springboot-sample">Sample SpringBoot</a></li>
<li><a href="https://github.com/jetoile/gatling-sample">Sample de projet Gatling pour le tir de performance</a></li>
</ul>


<p>Ainsi, si le coeur vous en dit, vous pourrez vous faire vous même une idée de qui est le plus fort&hellip; et même comparer avec vos solutions maisons&hellip; ;)</p>

<p>Allez, et parce que je suis sympa, je mets quand même le rapport Gatling obtenu suite à 1 seul tir en local. Je laisse le lecteur se faire une idée&hellip; ou pas&hellip;</p>

<ul>
<li><a href="/images/res_charge_gatling/dropwizard-1/index.html">Résultat Dropwizard</a></li>
<li><a href="/images/res_charge_gatling/jetty-1/index.html">Résultat Jetty</a></li>
<li><a href="/images/res_charge_gatling/netty-1/index.html">Résultat RestEasy-Netty</a></li>
<li><a href="/images/res_charge_gatling/restlet-1/index.html">Résultat Restlet</a></li>
<li><a href="/images/res_charge_gatling/springboot-1/index.html">Résultat SpringBoot</a></li>
<li><a href="/images/res_charge_gatling/tomcat-nio-1/index.html">Résultat Tomcat NIO</a></li>
<li><a href="/images/res_charge_gatling/undertow-1/index.html">Résultat RestEasy-Undertow</a></li>
</ul>


<p>Voilà un article un peu facile et qui n'apporte pas grand chose mais je trouvais qu'il était toujours intéressant pour les lecteurs curieux d'avoir la possibilité de voir différentes implementations&hellip;</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Undertow pour booster vos services REST]]></title>
    <link href="https://blog.jetoile.fr/2015/06/undertow-pour-booster-vos-services-rest.html"/>
    <updated>2015-06-23T14:51:56+02:00</updated>
    <id>https://blog.jetoile.fr/2015/06/undertow-pour-booster-vos-services-rest</id>
    <content type="html"><![CDATA[<p><img src="/images/undertow.png" alt="left-small" />
Il y a quelques temps, j'avais fait une série d'articles sur <a href="http://blog.jetoile.fr/2014/03/jaxrs-netty-et-bien-plus-encore-mode.html">resteasy-netty</a> et <a href="http://blog.jetoile.fr/2014/03/jaxrs-netty-4-jackon-2-les-memes-mais.html">resteasy-netty4</a>.</p>

<p>Cette article repart du même besoin, à savoir disposer d'une <em>stack</em> légère pour réaliser un service REST, mais en utilisant <a href="http://undertow.io/">Undertow</a> plutôt que Resteasy-Netty.</p>

<!-- more -->


<p>Au niveau des besoins, ils seront identiques ie. :</p>

<ul>
<li>utiliser JAX-RS,</li>
<li>intégrer Swagger,</li>
<li>intégrer Jolokia,</li>
<li>générer un livrable autoporteur.</li>
</ul>


<p>RestEasy-Netty, même s'il existe de nombreux points d'entrée, demande quelques phases de <em>hack</em> (gestion du <em>crossover domain</em> par exemple) et dispose d'un mécanisme un peu limité concernant la partie sécurité.</p>

<p>En outre, l'absence du mécanisme de Servlet reste un peu embêtant pour mettre en place certaines <em>features</em> comme le MDC ( <a href="http://logback.qos.ch/manual/mdc.html"><em>Mapped Diagnostic Context</em></a> ) bien pratique lorsque l'on est dans une architecture type microservice.</p>

<p>Le code complet est disponible <a href="https://github.com/jetoile/undertow-sample">ici</a>.</p>

<h1>Rappel du cahier des charges</h1>

<p>Comme je l'ai déjà indiqué dans les autres posts, l'objectif est seulement de montrer comme il peut être simple d'exposer un service REST à l'aide d'<a href="http://undertow.io/">Undertow</a>. Pour ce faire, un simple service sera exposé et il consistera à répèter ce qu’on lui demande…</p>

<p>Il répondra donc à une requête de type GET du type : <a href="http://localhost:8081/sample/say/">http://localhost:8081/sample/say/</a><message></p>

<p>Du coté de la réponse, elle aura la forme suivante :
```javascript
{</p>

<pre><code>"message": &lt;message&gt;,
"time":"2015-06-23T15:18:50.748"
</code></pre>

<p>}
```</p>

<h1>Mise en oeuvre</h1>

<p>A titre informatif, les versions des différentes librairies qui sont utilisés dans les exemples de code ci-dessous sont les suivantes (au format gradle pour gagner de la place) :
```text</p>

<pre><code>compile group: 'org.jboss.resteasy', name: 'jaxrs-api', version:'3.0.11.Final'
compile group: 'org.jolokia', name: 'jolokia-jvm', version:'1.3.1'
compile group: 'com.wordnik', name: 'swagger-jaxrs_2.10', version:'1.3.12'
compile group: 'com.wordnik', name: 'swagger-annotations_2.10', version:'1.3.0'
compile group: 'javax.servlet', name: 'javax.servlet-api', version:'3.1.0'
compile group: 'io.dropwizard.metrics', name: 'metrics-core', version:'3.1.2'
compile group: 'io.undertow', name: 'undertow-core', version:'1.2.8.Final'
compile group: 'io.undertow', name: 'undertow-servlet', version:'1.2.8.Final'
compile group: 'org.jboss.resteasy', name: 'resteasy-undertow', version:'3.0.11.Final'
compile group: 'org.jboss.resteasy', name: 'resteasy-jackson2-provider', version:'3.0.11.Final'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version:'2.5.4'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version:'2.5.4'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version:'2.5.4'
compile group: 'commons-configuration', name: 'commons-configuration', version:'1.10'
compile group: 'commons-collections', name: 'commons-collections', version:'3.2.1'
compile group: 'commons-io', name: 'commons-io', version:'2.4'
compile group: 'org.slf4j', name: 'slf4j-api', version:'1.7.12'
compile group: 'ch.qos.logback', name: 'logback-classic', version:'1.1.3'
</code></pre>

<p>```</p>

<p>Concernant la version des différentes dépendances, on constate que ce n'est pas swagger2 qui est utilisé en raison d'une incapacité de ma part à l'intégrer&hellip; :&lsquo;(</p>

<h1>Implémentation du service REST</h1>

<p>Le mise en place du service REST basé sur JAX-RS est on ne peut plus trivial… et la classe ci-dessous fait humblement l’affaire :
```java
@Api(value = &ldquo;/sample&rdquo;,</p>

<pre><code>    description = "the sample api")
</code></pre>

<p>@Path(&ldquo;/sample&rdquo;)
@RolesAllowed(&ldquo;admin&rdquo;)
public class SimpleService {</p>

<pre><code>private final static Logger log = LoggerFactory.getLogger(SimpleService.class);


@GET
@Path("/say/{msg}")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "repeat the word",
        notes = "response the word",
        response = DtoResponse.class)
@ApiResponses(value = {@ApiResponse(code = 500, message = "Internal server error")})
public Response sayHello(@PathParam("msg") String message) {

    log.info("sample log");

    final Timer timer = Main.metricRegistry.timer(name(SimpleService.class, "say-service"));
    final Timer.Context context = timer.time();
    try {

        DtoResponse response = new DtoResponse();
        try {
            response.setMessage(message);
            response.setTime(LocalDateTime.now());
        } catch (Exception e) {
            log.error("internal error: {}", e);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
        }
        return Response.ok(response).build();
    } finally {
        if (context != null) context.stop();
    }
}
</code></pre>

<p>}
<code>
Coté du DTO, il est le suivant :
</code>java
@XmlRootElement
public class DtoResponse {</p>

<pre><code>private String message;
private LocalDateTime time;

public DtoResponse() {
}

public String getMessage() {
    return message;
}

public void setMessage(String message) {
    this.message = message;
}

public LocalDateTime getTime() {
    return time;
}

public void setTime(LocalDateTime time) {
    this.time = time;
}
</code></pre>

<p>}
```</p>

<p>On remarquera l'utilisation de Java8 pour la gestion du temps plutôt que Joda-Time.</p>

<p>En outre, concernant les annotations Swagger et l'utilisation de metrics, nous y reviendrons plus tard.</p>

<p>Concernant le message de log, de même, nous y reviendrons plus tard avec l'intégration d'un MDC pour les logs.</p>

<h1>Mise en oeuvre avec Undertow</h1>

<p>Mettre en place Resteasy avec Undertow est très simple, d’après la documnentation, il suffit de faire :
```java
SimpleService simpleService = new SimpleService();
ResteasyDeployment deployment = new ResteasyDeployment();</p>

<p>deployment.setResources(Arrays.<Object>asList(simpleService));</p>

<p>int port = config.getInt(&ldquo;undertow.port&rdquo;, TestPortProvider.getPort());
String host = config.getString(&ldquo;undertow.host&rdquo;, String.valueOf(TestPortProvider.getHost()));
System.setProperty(&ldquo;org.jboss.resteasy.port&rdquo;, String.valueOf(TestPortProvider.getPort());
System.setProperty(&ldquo;org.jboss.resteasy.host&rdquo;, String.valueOf(TestPortProvider.getHost());</p>

<p>UndertowJaxrsServer server = new UndertowJaxrsServer();</p>

<p>DeploymentInfo deploymentInfo = server.undertowDeployment(deployment);
deploymentInfo.setDeploymentName(&ldquo;&rdquo;);
deploymentInfo.setContextPath(&ldquo;/&rdquo;);
deploymentInfo.setClassLoader(Main.class.getClassLoader());</p>

<p>deployment.setProviderFactory(new ResteasyProviderFactory());
server.deploy(deploymentInfo);
server.start(Undertow.builder().addHttpListener(port, host));
```</p>

<p>On y constate que pour ajouter un service, il suffit juste de déclarer la classe implémentant JAX-RS via la méthode <code>setResources()</code> sur l’instance de <em>ResteasyDeployment</em> fournit au serveur <em>UndertowJaxrsServer</em> :</p>

<p>Et voilà! On dispose désormais d’un programme exécutable qui démarre un serveur REST basé sur Undertow.</p>

<p>Par contre, il semble que le service ne rende pas vraiment ce que l'on voulait :
<code>bash
curl 'http://localhost:8081/sample/say/&lt;message&gt;'
</code></p>

<p>```json
{</p>

<pre><code>"message": "&lt;message&gt;",
"time": {
    "hour": 15,
    "minute": 55,
    "second": 51,
    "nano": 225000000,
    "year": 2015,
    "month": "JUNE",
    "dayOfMonth": 23,
    "dayOfWeek": "TUESDAY",
    "dayOfYear": 174,
    "monthValue": 6,
    "chronology": {
        "calendarType": "iso8601",
        "id": "ISO"
    }
}
</code></pre>

<p>}
```</p>

<p>Pas de souci, il suffit de préciser comment on souhaite que LocalDateTime soit sérialisé par Jackson :</p>

<p>Ainsi, notre DTO devient :
```java</p>

<p>@XmlRootElement
public class DtoResponse {</p>

<pre><code>private String message;
@JsonSerialize(using = LocalDateTimeToStringSerializer.class)
private LocalDateTime time;

public DtoResponse() {
}

public String getMessage() {
    return message;
}

public void setMessage(String message) {
    this.message = message;
}

public LocalDateTime getTime() {
    return time;
}

public void setTime(LocalDateTime time) {
    this.time = time;
}
</code></pre>

<p>}
```</p>

<p>où :</p>

<p>```java
public class LocalDateTimeToStringSerializer extends JsonSerializer<LocalDateTime> {</p>

<pre><code>@Override
public void serialize(LocalDateTime value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
    jgen.writeObject(value.format(DateTimeFormatter.ISO_DATE_TIME));
}
</code></pre>

<p>}
```</p>

<p>Après ces modifications, on obtient bien :</p>

<p><code>json
{"message":"&lt;message&gt;","time":"2015-06-23T16:04:01.419"}
</code></p>

<h1>Intégration de Metrics</h1>

<p>Concernant l'intégration de Metrics, pas grand chose de nouveau et donc pas grand chose à dire ;&ndash;)</p>

<p>Déclarer le registry :
<code>java
metricRegistry = new MetricRegistry();
final JmxReporter reporter = JmxReporter.forRegistry(metricRegistry).build();
reporter.start();
</code></p>

<p>Et utiliser le dans vos classes :
```java
final Timer timer = Main.metricRegistry.timer(name(SimpleService.class, &ldquo;say-service&rdquo;));
final Timer.Context context = timer.time();
try {</p>

<pre><code>...
</code></pre>

<p>} finally {</p>

<pre><code>if (context != null) context.stop();
</code></pre>

<p>}
```</p>

<h1>Intégration de la sécurité</h1>

<p>Undertow permet une bien meilleur intégration de la sécurité que RestEasy-Netty. En effet, grâce au mécanisme de Servlet, il est possible de bénéficier de toute la puissance des conteneurs de Servlets.</p>

<p>Du coté du serveur Undertow, il suffit donc de définir un <em>ServletIdentityManager</em> et de lui fournir un <em>LoginConfig</em> :
```java
deployment.setSecurityEnabled(true);</p>

<p>ServletIdentityManager identityManager = new ServletIdentityManager();
identityManager.addUser(&ldquo;khanh&rdquo;, &ldquo;khanh&rdquo;, &ldquo;admin&rdquo;);</p>

<p>deploymentInfo = deploymentInfo.setIdentityManager(identityManager).setLoginConfig(new LoginConfig(&ldquo;BASIC&rdquo;, &ldquo;Test Realm&rdquo;));
```</p>

<p>où :
```java
public class ServletIdentityManager implements IdentityManager {</p>

<pre><code>private static final Charset UTF_8 = Charset.forName("UTF-8");
private final Map&lt;String, UserAccount&gt; users = new HashMap&lt;&gt;();

public void addUser(final String name, final String password, final String... roles) {
    UserAccount user = new UserAccount();
    user.name = name;
    user.password = password.toCharArray();
    user.roles = new HashSet&lt;&gt;(Arrays.asList(roles));
    users.put(name, user);
}

@Override
public Account verify(Account account) {
    // Just re-use the existing account.
    return account;
}

@Override
public Account verify(String id, Credential credential) {
    Account account = users.get(id);
    if (account != null &amp;&amp; verifyCredential(account, credential)) {
        return account;
    }

    return null;
}

@Override
public Account verify(Credential credential) {
    return null;
}

private boolean verifyCredential(Account account, Credential credential) {
    // This approach should never be copied in a realm IdentityManager.
    if (account instanceof UserAccount) {
        if (credential instanceof PasswordCredential) {
            char[] expectedPassword = ((UserAccount) account).password;
            char[] suppliedPassword = ((PasswordCredential) credential).getPassword();

            return Arrays.equals(expectedPassword, suppliedPassword);
        } else if (credential instanceof DigestCredential) {
            DigestCredential digCred = (DigestCredential) credential;
            MessageDigest digest = null;
            try {
                digest = digCred.getAlgorithm().getMessageDigest();

                digest.update(account.getPrincipal().getName().getBytes(UTF_8));
                digest.update((byte) ':');
                digest.update(digCred.getRealm().getBytes(UTF_8));
                digest.update((byte) ':');
                char[] expectedPassword = ((UserAccount) account).password;
                digest.update(new String(expectedPassword).getBytes(UTF_8));

                return digCred.verifyHA1(HexConverter.convertToHexBytes(digest.digest()));
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalStateException("Unsupported Algorithm", e);
            } finally {
                digest.reset();
            }
        }
    }
    return false;
}

private static class UserAccount implements Account {
    // In no way whatsoever should a class like this be considered a good idea for a real IdentityManager implementation,
    // this is for testing only.

    String name;
    char[] password;
    Set&lt;String&gt; roles;

    private final Principal principal = new Principal() {
        @Override
        public String getName() {
            return name;
        }
    };

    @Override
    public Principal getPrincipal() {
        return principal;
    }

    @Override
    public Set&lt;String&gt; getRoles() {
        return roles;
    }
}
</code></pre>

<p>}
```</p>

<p>Il s'agit ici d'une Basic Authentification mais il est bien sûr possible d'en mettre en place d'autre.</p>

<p>Coté autorisation, il est alors possible de bénéficier de l'annotation <code>@RolesAllowed</code> de JAX-RS :
<code>java
@Path("/sample")
@RolesAllowed("admin")
public class SimpleService {
...
}
</code></p>

<h1>Intégration d'un MDC</h1>

<p>Concernant la mise en place d'un MDC (<em>Mapped Diagnostic Context</em>), le fait de bénéficier du mécanisme de <em>Filter</em> des Servlets rend la chose beaucoup plus simple.</p>

<p>En effet, une fois la couche sécurité branchée, il suffit de récupérer le <code>UserPrincipal</code> dans la requête et l'enregistrer dans le MDC.</p>

<p>La déclaration des Filters se fait de la manière suivante pour Undertow :
```java
FilterInfo mdcFilter = new FilterInfo(&ldquo;MDCFilter&rdquo;, MDCServletFilter.class);
deploymentInfo.addFilter(mdcFilter);
deploymentInfo.addFilterUrlMapping(&ldquo;MDCFilter&rdquo;, &ldquo;*&rdquo;, DispatcherType.REQUEST);</p>

<p>FilterInfo mdcInsertingFilter = new FilterInfo(&ldquo;MDCInsertingServletFilter&rdquo;, MDCInsertingServletFilter.class);
deploymentInfo.addFilter(mdcInsertingFilter);
deploymentInfo.addFilterUrlMapping(&ldquo;MDCInsertingServletFilter&rdquo;, &ldquo;*&rdquo;, DispatcherType.REQUEST);
```</p>

<p>Avec le filter ci-dessous :
```java
public class MDCServletFilter implements Filter {</p>

<pre><code>private final String USER_KEY = "username";

public void destroy() {
}

public void doFilter(ServletRequest request, ServletResponse response,
                     FilterChain chain) throws IOException, ServletException {

    boolean successfulRegistration = false;

    HttpServletRequest req = (HttpServletRequest) request;
    Principal principal = req.getUserPrincipal();
    // Please note that we could have also used a cookie to
    // retrieve the user name

    if (principal != null) {
        String username = principal.getName();
        successfulRegistration = registerUsername(username);
    }

    try {
        chain.doFilter(request, response);
    } finally {
        if (successfulRegistration) {
            MDC.remove(USER_KEY);
        }
    }
}

public void init(FilterConfig arg0) throws ServletException {
}


/**
 * Register the user in the MDC under USER_KEY.
 *
 * @param username
 * @return true id the user can be successfully registered
 */
private boolean registerUsername(String username) {
    if (username != null &amp;&amp; username.trim().length() &gt; 0) {
        MDC.put(USER_KEY, username);
        return true;
    }
    return false;
}
</code></pre>

<p>}
<code>
Ainsi, disposer d'un MDC permet d'ajouter automatiquement des informations dans les logs :
</code>xml
<configuration></p>

<pre><code>&lt;appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"&gt;
    &lt;encoder&gt;
        &lt;pattern&gt;%d{HH:mm:ss.SSS} %-5level %logger{36} %X{req.remoteHost} %X{req.requestURI} - C:%X{username} - %msg%n

        &lt;/pattern&gt;
    &lt;/encoder&gt;
&lt;/appender&gt;


&lt;root level="info"&gt;
    &lt;appender-ref ref="STDOUT" /&gt;
&lt;/root&gt;
</code></pre>

<p></configuration>
```</p>

<p>On obtient alors bien les logs voulues :
<code>text
17:15:11.466 INFO  f.j.sample.service.SimpleService 127.0.0.1 /sample/say/&lt;message&gt; - C:khanh - sample log
</code></p>

<h1>Intégration de Jolokia</h1>

<p>Coté Jolokia, pas grand chose à ajouter par rapport à ma série d'article précédent&hellip;
```java
try {</p>

<pre><code>        JolokiaServerConfig config = new JolokiaServerConfig(new HashMap&lt;String, String&gt;());

        JolokiaServer jolokiaServer = new JolokiaServer(config, true);
        jolokiaServer.start();
</code></pre>

<p>} catch (Exception e) {</p>

<pre><code>        LOGGER.error("unable to start jolokia server", e);
</code></pre>

<p>}
```</p>

<h1>Intégration de Swagger</h1>

<p>Concernant l'intégration de Swagger, le fait de disposer des <em>Filter</em> de Servlet permet de n'avoir pas à faire de <em>hack</em> immonde pour gérer le CORS (cf. <a href="http://blog.jetoile.fr/2014/03/jaxrs-netty-et-bien-plus-encore-mode.html">article précédent</a>) : il suffit de déclarer un <em>Filter</em> dans Undertow qui a, en outre, la chance d'exister :
```java
CorsFilter filter = new CorsFilter();
filter.setAllowedMethods(&ldquo;GET,POST,PUT,DELETE,OPTIONS&rdquo;);
filter.setAllowedHeaders(&ldquo;X-Requested-With, Content-Type, Content-Length, Authorization&rdquo;);
filter.getAllowedOrigins().add(&ldquo;*&rdquo;);</p>

<p>deployment.setProviderFactory(new ResteasyProviderFactory());
deployment.getProviderFactory().register(filter);
```</p>

<p>Concernant la déclaration dans Undertow, pas grand chose à ajouter :
```java</p>

<pre><code>private static void initSwagger(ResteasyDeployment deployment) {
    BeanConfig swaggerConfig = new BeanConfig();
    swaggerConfig.setVersion(config.getString("swagger.version", "1.0.0"));
    swaggerConfig.setBasePath("http://" + config.getString("swagger.host", "localhost") + ":" + config.getString("swagger.port", "8081"));
    swaggerConfig.setTitle(config.getString("swagger.title", "jetoile sample app"));
    swaggerConfig.setScan(true);
    swaggerConfig.setResourcePackage("fr.jetoile.sample.service");

    deployment.setProviderClasses(Lists.newArrayList(
            "com.wordnik.swagger.jaxrs.listing.ResourceListingProvider",
            "com.wordnik.swagger.jaxrs.listing.ApiDeclarationProvider"));
    deployment.setResourceClasses(Lists.newArrayList("com.wordnik.swagger.jaxrs.listing.ApiListingResourceJSON"));
    deployment.setSecurityEnabled(false);
}
</code></pre>

<p>```</p>

<h1>Branchement des plugins Maven Appassembler et Assembly</h1>

<p>Coté génération du livrable, encore une fois, pas grand chose à ajouter par rapport à mon précédent article : l'utilisation des plugins assembly et appassembler est identique.</p>

<h1>Conclusion</h1>

<p>On avait vu dans les articles précédents que RestEasy-Netty était une solution intéressante pour la simplicité de sa mise en oeuvre ainsi que pour le faible overhead.</p>

<p>Cependant, certaines intégrations ressemblaient plus à du <em>hack</em> qu'à une solution configurable.</p>

<p>Undertow (enfin pour être plus précis RestEasy-Undertow) pour sa part offre la même simplicité que RestEasy-Netty mais il permet en plus de s'intégrer avec beaucoup d'autres choses et le fait de retrouver le mécanisme de <em>Filter</em> facilite énormément les choses (par exemple, je ne suis pas sûr que bénéficier du MDC avec RestEasy-Netty ait été aussi simple).</p>

<p>Coté performance, je reviendrai dessus dans un autre article mais je peux déjà dire que la solution RestEasy-Undertow n'a rien à envier à RestEasy-Netty.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Breizhcamp 2014 - Présentations en ligne]]></title>
    <link href="https://blog.jetoile.fr/2014/06/breizhcamp-2014-presentation-en-ligne.html"/>
    <updated>2014-06-12T08:28:38+02:00</updated>
    <id>https://blog.jetoile.fr/2014/06/breizhcamp-2014-presentation-en-ligne</id>
    <content type="html"><![CDATA[<p><img src="/images/bzhcmp/Logo.png" alt="left-small" />
Pour ceux qui auraient manqué l'information, le <a href="http://www.breizhcamp.org/">BreizhCamp</a> s'est déroulé en mai dernier et j'y avais la chance d'y présenter un <em>talk</em> sur un retour d'expérience concernant le passage à l'échelle d'un SI afin de lui permettre de supporter 4 millions d'utilisateurs.</p>

<p>Le synopsis était le suivant :</p>

<blockquote><p><strong>De 20 000 à 4 millions d'utilisateurs</strong></p>

<p>Pour ce faire, il a été nécessaire de revoir certaines parties du SI afin de pouvoir stocker en masse les données des utilisateurs mais également afin d'être capable de les traiter.</p>

<p>Ce retour d'expérience montrera comment, avec une approche et des technologies simples, il a été possible de revoir la façon de faire et comment il a été proposé de traiter le sujet.</p>

<p>Il montrera également les pistes qui ont été étudiées et les solutions qui ont été retenues.</p>

<p>Les différents points qui seront abordés seront : Cassandra, REST, Netty, Spring Integration, Jolokia, Metrics saupoudré d'un peu de &ldquo;Big Data&rdquo;.</p></blockquote>

<p>Encore une fois, je remercie la <em>team</em> pour :</p>

<ul>
<li>l'organisation au top,</li>
<li>pour l'ambiance qu'ils ont su apporter à cette conférence,</li>
<li>mais également pour leur réactivité quant à mettre en ligne les vidéo des différents <em>talk</em> (sur <a href="https://www.youtube.com/playlist?list=PLHWl6dPnEb4l9S-nl4od10OkTOyZEn5Ef">YouTube</a> mais également sur <a href="http://parleys.com/channel/5148921d0364bc17fc56adbe/presentations?sort=date&amp;state=public">Parleys</a>)</li>
</ul>


<p>Du coup, vu que la vidéo de mon <em>talk</em> a été <em>processée</em>, j'en profite pour mettre le lien (désolé si je n'arrête pas de danser et désolé pour l'absence de démo dûe à mon ordinateur capricieux&hellip;) :</p>

<iframe type="text/html" width="420" height="290" mozallowfullscreen="true" webkitallowfullscreen="true" src="http://parleys.com/share.html#play/5391de0ee4b0359b3190f8f4" frameborder="0">&lt;br /&gt;</iframe>


<p>Et je vous invite à aller voir égalemnent les autres talk :
<a href="http://parleys.com/channel/5148921d0364bc17fc56adbe/presentations?sort=date&amp;state=public">http://parleys.com/channel/5148921d0364bc17fc56adbe/presentations?sort=date&amp;state=public</a></p>

<p>A noter également que les conf de Devoxx France ont aussi été publiée depuis un petit moment pour ceux qui seraient à la traine ;&ndash;) :
<a href="http://parleys.com/channel/5355419ce4b0524a2f28bca0/presentations?sort=date&amp;state=public">http://parleys.com/channel/5355419ce4b0524a2f28bca0/presentations?sort=date&amp;state=public</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Lecture et traitement de fichiers : comment faire simple?]]></title>
    <link href="https://blog.jetoile.fr/2014/04/lecture-et-traitement-de-fichiers-comment-faire-simple.html"/>
    <updated>2014-04-10T14:15:44+02:00</updated>
    <id>https://blog.jetoile.fr/2014/04/lecture-et-traitement-de-fichiers-comment-faire-simple</id>
    <content type="html"><![CDATA[<p><img src="/images/batch/logo.png" alt="left-small" /></p>

<p>De nombreuses applications ou systèmes d'informations nécessitent le chargement de données issues de fichiers.</p>

<p>Bien souvent, cet import est exécuté par <em>batch</em>, mais il peut aussi être intéressant de faire cet import au fil de l'eau.</p>

<p>En outre, bien souvent, les fichiers à importer sont, soient nombreux, soient volumineux. Du coup, écrire un code simple et fiable peut devenir plus ardu que ce qu'il n'y parait. Si, de plus, on veut ajouter des logs parlant (c'est à dire avec, au minimum, le temps de traitement d'un fichier et son nom), cela a tendance a rajouter du bruit au code. Sans oublier que lire un fichier est bien mais que, souvent, un traitement est effectué dessus&hellip;</p>

<p>Enfin, lors d'une forte volumétrie, une scalabilité horizontale peut être intéressante surtout dans le contexte actuel où la quantité d'information vient à exploser.</p>

<p>Cet article parlera donc de la problématique d'import de fichiers dans une application en s'appuyant sur des framework comme <a href="http://projects.spring.io/spring-batch/">Spring Batch</a> ou <a href="http://projects.spring.io/spring-integration/">Spring Integration</a>. Le mot d'ordre sera de le faire le plus simplement possible en s'appuyant au maximum sur ces framework.</p>

<!-- more -->


<h1>Solution à base de <em>batch</em></h1>

<p>Ecrire un batch permettant de traiter des fichiers peut sembler simple mais lorsque le nombre de ces derniers vient à augmenter ou lorsque la taille des fichiers est volumineux, il arrive souvent que des bugs apparaissent. En outre, il convient alors de gérer manuellement les logs ainsi que la partie supervision.</p>

<p>Pour répondre à ce besoin, il est peut être avantageux d'utiliser <a href="http://projects.spring.io/spring-batch/">Spring Batch</a> (ou une autre implémentation de la <a href="https://jcp.org/en/jsr/detail?id=352">JSR 352</a>).</p>

<p><em>ndlr</em> : je ne présenterai pas le fonctionnement de Spring Batch à base de <strong>Job</strong> et <strong>Step</strong> puisque cela se trouve très facilement dans les documents officiels, livres ou articles de blog et je mettrai plutôt l'accent sur la faisabilité de tel ou tel chose.</p>

<p><img src="/images/batch/spring-batch-reference-model.png" title="crédit photo : http://docs.spring.io/spring-batch/trunk/reference/html-single/index.html" alt="center" /></p>

<p>Spring Batch offre nativement la possibilité de traiter les fichiers par <em>chunk</em> via :</p>

<ul>
<li><code>FlatFileItemReader</code> qui permet de lire un fichier plat ligne par ligne et où chaque ligne dispose de la même information (il est également possible de traiter des types de lignes différentes issues du même fichier avec <code>PatternMatchingCompositeLineMapper</code>).</li>
<li><code>StaxEventItemReader</code> pour lire fichiers xml composés de format de <em>fragments</em> identiques :
<img src="/images/batch/xmlinput.png" title="crédit photo : http://docs.spring.io/spring-batch/trunk/reference/html-single/index.html" alt="center" /></li>
</ul>


<p>avec :</p>

<p>```xml
<bean id="itemReader" class="org.springframework.batch.item.xml.StaxEventItemReader"></p>

<pre><code>&lt;property name="fragmentRootElementName" value="trade" /&gt;
&lt;property name="resource" value="data/iosample/input/input.xml" /&gt;
&lt;property name="unmarshaller" ref="tradeMarshaller" /&gt;
</code></pre>

<p></bean></p>

<p>&lt;bean id=&ldquo;tradeMarshaller&rdquo;</p>

<pre><code>  class="org.springframework.oxm.xstream.XStreamMarshaller"&gt;
&lt;property name="aliases"&gt;
    &lt;util:map id="aliases"&gt;
        &lt;entry key="trade"
               value="org.springframework.batch.sample.domain.Trade" /&gt;
        &lt;entry key="price" value="java.math.BigDecimal" /&gt;
        &lt;entry key="name" value="java.lang.String" /&gt;
    &lt;/util:map&gt;
&lt;/property&gt;
</code></pre>

<p></bean>
```</p>

<p>Généralement, il est nécessaire de préciser le nom du fichier à traiter mais il est également possible d'en traiter plusieurs de même type dans la même <em>Step</em> via la classe <code>MultiResourceItemReader</code>.</p>

<p>```xml
<bean id="multiResourceReader" class="org.springframework.batch.item.file.MultiResourceItemReader"></p>

<pre><code>&lt;property name="resources" value="classpath:data/input/file-*.txt" /&gt;
&lt;property name="delegate" ref="flatFileItemReader" /&gt;
</code></pre>

<p></bean>
```</p>

<p>Spring Batch supporte également la scalabilité horizontale en permettant de préciser un <code>taskExecutor</code> au niveau de la <em>Step</em>.</p>

<p>```xml
<step id="loading"></p>

<pre><code>&lt;tasklet task-executor="taskExecutor"&gt;...&lt;/tasklet&gt;
</code></pre>

<p></step>
```</p>

<p>Ainsi, on constate que Spring Batch offre nativement la possibilité de traiter des fichiers volumineux en les découpant par <em>chunk</em>.</p>

<p>De même, il offre nativement la possibilité de passer sur du traitement parallèle.</p>

<p>Concernant la partie supervision, vu que l'on est dans un environnement Spring, on bénéficie, bien sûr, de toute la partie JMX.</p>

<p>Pour la partie gestion des erreurs, Spring Batch permet de les gérer de manière très simple.</p>

<p>Cependant, on perd un grand intérêt si, par fichier, il n'y a qu'une seule donnée. En effet, le mécanisme de <em>chunk</em> devient alors inutile. Il reste cependant possible d'utiliser la scalabilité horizontale.</p>

<p>Concernant la partie log, j'avoue ne pas avoir creuser, je ne dirai donc rien sur ce point&hellip;</p>

<h1>Solution à base d'EIP</h1>

<p>Dans le cas où la volonté serait de traiter les fichiers au fil de l'eau, Spring Batch n'est pas la solution la plus adaptée&hellip;</p>

<p>Cependant, Spring Integration répond à ce besoin de manière très simple.</p>

<p>En effet, en utilisant un simple <strong>Service Activator</strong> (au sens EIP) de type <code>inbound-channel-adapter</code>, il devient alors possible de <em>poller</em> un répertoire et d'envoyer le contenu du fichier vers un <em>filter</em> (au sens EIP).</p>

<p>```xml
&lt;file:inbound-channel-adapter id=&ldquo;fileAdapter&rdquo; auto-startup=&ldquo;true&rdquo; auto-create-directory=&ldquo;true&rdquo;</p>

<pre><code>                          filename-pattern="*.xml"
                          directory="file:/tmp"
                          scanner="recursiveScanner"
                          prevent-duplicates="true"
                          channel="inputChannel"&gt;
&lt;int:poller fixed-delay="30000" max-messages-per-poll="500"/&gt;
</code></pre>

<p>&lt;/file:inbound-channel-adapter></p>

<p>&lt;file:file-to-string-transformer charset=&ldquo;UTF-8&rdquo; delete-files=&ldquo;true&rdquo; input-channel=&ldquo;inputChannel&rdquo;</p>

<pre><code>                                 output-channel="toLogger"/&gt;
</code></pre>

<p>&lt;int:logging-channel-adapter auto-startup=&ldquo;true&rdquo; channel=&ldquo;toLogger&rdquo; level=&ldquo;DEBUG&rdquo; log-full-message=&ldquo;true&rdquo;/></p>

<p>&lt;int:channel id=&ldquo;inputChannel&rdquo;/>
&lt;int:channel id=&ldquo;toLogger&rdquo;/>
```</p>

<p>D'un point de vue scalabilité horizontale, il suffit de renseigner (tout comme pour Spring Batch) un <code>taskExecutor</code> au niveau du <em>Channel</em> et&hellip; c'est tout!</p>

<p>```xml
&lt;task:executor id=&ldquo;someExecutor&rdquo;</p>

<pre><code>               pool-size="20"
               keep-alive="2400"/&gt;
</code></pre>

<p>&lt;int:channel id=&ldquo;toLogger&rdquo;></p>

<pre><code>&lt;int:dispatcher task-executor="someExecutor"/&gt;
</code></pre>

<p>&lt;/int:channel>
```</p>

<p>Enfin, disposer d'une supervision est des plus aisé puisqu'il suffit de rajouter l'élément <code>message-history</code> :</p>

<p><code>xml
&lt;int:message-history/&gt;
</code></p>

<p>L'ajout de cet élément indique à Spring Integration qui doit ajouter automatiquement dans le <em>header</em> du message le temps d'exécution de chaque <em>Filter</em>. Concernant le nom du fichier et son chemin, il se trouve renseigner automatiquement dans le header par l'<em>adapter</em> <code>file:inbound-channel-adapter</code>.</p>

<p>Coté gestion des erreurs, Spring Integration permet de les gérer très simplement sur le principe du canal d'erreur qui peut récupérer tous les messages en erreur.</p>

<p>Cependant, avec Spring Integration, si le fichier est volumineux, il n'est plus possible de le traiter en <em>chunk</em> et un risque de contention mémoire existe.</p>

<h1>Solution à base d'EIP et de batch</h1>

<p>On a vu dans les deux paragraphe précédent que Spring Integration était une très bonne solution pour traiter des fichiers au fil de l'eau alors que Spring Batch était plutôt orienté traitement par batch.</p>

<p>Cependant, il est très facile de composer les 2 modes. Cela permet, par exemple, de déclencher un traitement d'un fichier volumineux dès sa réception (via Spring Integration) et de bénéficier du mode <em>chunk</em> de Spring Batch pour le traitement.</p>

<p>Dans ce cas, bien sûr, il n'est pas question de faire de traitement sur le contenu du fichier dans la partie Spring Integration (seul l'objet <code>File</code> est transmis dans le corps du message) et c'est le jobs Spring Batch qui s'occupera du traitement à proprement parler.</p>

<p>Cela engendre peut être un <em>overhead</em> conséquent mais on est, au moins sûr, d'éviter le <em>Out Of Memory</em> dans le cas de fichiers volumineux. En outre, cela permet de bénéficier de la puissance des EIP (routage ou filtrage sur le nom du fichier par exemple) tant que le fichier n'a pas à être chargé.</p>

<p><img src="/images/batch/archi_combo.png" alt="large" /></p>

<h1>Conclusion</h1>

<p>On a vu dans cet article comment il pouvait être trivial de traiter l'import de fichiers sans avoir à gérer manuellement des pools de thread ou des logs d'audit.</p>

<p>Je ne suis pas rentré dans les détails mais mon objectif était surtout de montrer qu'en utilisant les bons outils/framework, il était possible de produire du code minimaliste et donc moins propice aux erreurs.</p>

<p>Pour avoir mis en oeuvre ces solutions, je peux vous assurer que le code écrit (ainsi que le temps passé) était minimaliste sinon nul (si on considère qu'écrire du xml n'est pas du code&hellip;). Bien sûr, je ne parle pas du code de traitement qui doit être écrit quoiqu'il arrive mais, encore une fois, le fait d'expédier la partie plomberie a permis de se concentrer sur le réel besoin métier.</p>

<p>Enfin, il est important de préciser que dans certains cas, une telle approche ne fonctionnera pas (si un fichier contient, par exemple, des dépendances à des données issues d'autres fichiers) et qu'il peut même être dangereux de vouloir absoluement utiliser ce type de framework au risque de leur faire faire des choses pour lesquelles ils ne sont pas prévus&hellip; Par exemple, il ne faut pas oublier que dans <strong>EIP</strong>, le <strong>I</strong> signifie Intégration!! Si le besoin est autre, il est fortement recommandé d'utiliser autre chose ou de le faire manuellement mais, par pitié, ne tordez pas le coup aux outils&hellip;! (si si, je l'ai vu&hellip; d'où mon désarroi&hellip;).</p>

<p><em>ndlr</em> : bon, j'admets que la partie qui a dû être la plus longue a sûrement été le <em>tuning</em> du pool de thread afin de tirer le meilleur partie de la machine mais, même si cela avait été fait de manière programmatique, cela aurait été nécessaire&hellip;</p>

<p><em>ndlr</em> : j'ai parlé, dans cet article, de Spring Integration pour la partie EIP mais il est tout aussi simple d'utiliser Apache Camel.</p>

<h1>Pour aller plus loin&hellip;</h1>

<ul>
<li><strong>Spring Integration in Action</strong> de Mark Fisher, Jonas Partner, Marius Bogoevici et Iwein Fuld chez Manning</li>
<li><strong>Camel in Action</strong> de Claus Ibsen et Jonathan Anstey chez Manning</li>
<li><strong>Spring Batch in Action</strong> de Arnaud Cogoluegnes, Thierry Templier, Gary Gregory et Olivier Bazoud chez Manning</li>
<li><strong>Enterprise Integration Patterns</strong> de G. Hohpe et B. Woolf chez Addisson Wesley</li>
<li><a href="http://www.eaipatterns.com/">http://www.eaipatterns.com/</a></li>
<li><a href="http://projects.spring.io/spring-integration/">http://projects.spring.io/spring-integration/</a></li>
<li><a href="http://projects.spring.io/spring-batch">http://projects.spring.io/spring-batch</a></li>
<li><a href="http://www.technologies-ebusiness.com/langages/spring-batch-spring-integration-une-usine-de-batchs-a-moindre-cout">http://www.technologies-ebusiness.com/langages/spring-batch-spring-integration-une-usine-de-batchs-a-moindre-cout</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Logstash : tour d'horizon sur les stratégies de déploiement]]></title>
    <link href="https://blog.jetoile.fr/2014/04/logstash-petit-tour-dhorizon.html"/>
    <updated>2014-04-07T11:20:26+02:00</updated>
    <id>https://blog.jetoile.fr/2014/04/logstash-petit-tour-dhorizon</id>
    <content type="html"><![CDATA[<p><img src="/images/logstash/logstash.png" alt="left-small" />
Cet article fera un rapide tour d'horizon sur les différentes stratégies qui peuvent être utilisées pour <a href="http://logstash.net/">Logstash</a>.</p>

<p>Pour ce faire, je m'appuierai sur le très bon <a href="http://www.logstashbook.com/">livre officiel</a> que je me suis procuré (moyennant environ 10€) et qui fournit une très bonne vision sur ce qui est possible de faire ainsi que sur les différents concepts mais également sur les différentes stratégies de déploiement.</p>

<p>Même si je résumerai succinctement quelques-uns des concepts afin que cet article soit un minimum compréhensible, cet article traitera surtout sur la façon dont il est possible de déployer les agents Logstash.</p>

<p>[<em>ndlr</em> : par contre, je ne ferai, comme à mon habitude, que retranscrire ce qui est présent dans le livre&hellip;]</p>

<!-- more -->


<h1>Les concepts</h1>

<p>Logstash est écrit en JRuby et fonctionne dans une JVM. Son architecture est orientée messages et est très simple. Plutôt que de séparer le concepts d'agents et de serveurs, Logstash se présente comme  un simple agent qui est configuré pour combiner différentes fonctions avec d'autres composants open souce.</p>

<p>L'écosystème de Logstash est constitué de 4 composants :</p>

<ul>
<li><strong>Shipper</strong> qui envoie des événements à Logstash.</li>
<li><strong>Broker</strong> et <strong>Indexer</strong> qui reçoivent et indexent les événements.</li>
<li><strong>Search</strong> et <strong>Stockage</strong> qui permettent de rechercher et de stocker les événements.</li>
<li><strong>Web Interface</strong> qui est une interface web appelée <a href="http://www.elasticsearch.org/overview/kibana/"><strong>Kibana</strong></a>.</li>
</ul>


<p>Les serveurs Logstash sont constitués d'un ou de plusieurs de ces composants indépendamment, ce qui permet de les séparer offrant ainsi la possibilité de <em>scaler</em>  mais également de les combiner en fonction du besoin.</p>

<p>Dans le plupart des cas, Logstash sera déployé de la manière suivante :</p>

<ul>
<li>Les hôtes exécutant les agent Logstash comme des <strong>Shipper</strong> qui émettent, comme des événements, les logs des applications, services et hôte à un serveur central Logstash. Ces hôtes n'ont besoin de disposer que d'agents Logstash.</li>
<li>Le serveur central Logstash qui aura à sa charge l'exécution du <strong>Broker</strong>, <strong>Indexer</strong>, <strong>Search</strong>, <strong>Storage</strong> et <strong>Web Interface</strong> afin de recevoir, <em>processer</em> et stocker les logs.</li>
</ul>


<p><img src="/images/logstash/archi01.png" alt="center" /></p>

<p>En fait, une configuration typique de Logstash est la suivante :</p>

<p>```text
input {
  stdin { }
}</p>

<p>filter {
  grok {</p>

<pre><code>match =&gt; { "message" =&gt; "%{COMBINEDAPACHELOG}" }
</code></pre>

<p>  }
  date {</p>

<pre><code>match =&gt; [ "timestamp" , "dd/MMM/yyyy:HH:mm:ss Z" ]
</code></pre>

<p>  }
}</p>

<p>output {
  elasticsearch { host => localhost }
  stdout { codec => rubydebug }
}
```</p>

<p>où :</p>

<ul>
<li><code>input</code> peut prendre en valeur des <em>plugins</em> qui correspondent à ce que peut prendre en entrée l'agent (comme, par exemple, l'entrée standard ou le contenu d'un fichier).</li>
<li><code>filter</code> peut prendre en valeur des <em>plugins</em> qui permettent de manipuler l'événement en le <em>parsant</em>, filtrant ou en ajoutant des informations issues du parsing ou non.</li>
<li><code>output</code> peut prendre en valeur des <em>plugins</em> qui permettent de préciser où seront envoyés les événements (comme, par exemple, la sortie standard ou ElasticSearch).</li>
</ul>


<h1>Les différentes stratégies de déploiement possibles</h1>

<h2>Le mode de déploiement <em>classique</em></h2>

<p>Dans l'architecture de déploiement <em>classique</em>, on retrouve la <em>stack</em> préconisée qui est la suivante :</p>

<ul>
<li>Les agents Logstash se trouvant sur les machines hôtes collectent et émettent les logs (sous forme d'événements) au système central.</li>
<li>Une instance d'un système de bufferisation (comme <a href="http://redis.io/"><strong>Redis</strong></a> ou autre, comme une implémentation d'<a href="http://www.amqp.org/"><strong>AMQP</strong></a>) reçoit les événement sur le serveur central et joue le rôle de buffer.</li>
<li>Un agent Logstash extrait les événements de logs du buffer et les traite.</li>
<li>L'agent Logstash envoie les événements d'index dans ElasticSearch.</li>
<li>ElasticSearch stocke et rend les événements cherchable.</li>
<li>Kibana permet la recherche et le rendu des événements indexés dans ElasticSearch.</li>
</ul>


<p><img src="/images/logstash/archi02.png" alt="center" /></p>

<p>En fait, le <strong>broker</strong> permet de servir de buffer entre les agents et le serveur Logstash. Cela est essentiel pour les raisons suivantes :</p>

<ul>
<li>Cela permet d'améliorer les performances de l'environnement Logstash en fournissant une buffer de cache pour les événements de log.</li>
<li>Cele permet de fournir de la résiliance. Si l'indexation Logstash échoue, alors les événements sont mise en fils d'attente afin d'éviter la perte d'informations.</li>
</ul>


<p>On observe donc, dans cette configuration, que les agents Logstash présents sur les machines hôtes ne font que transmettre sans intelligence réelle au buffer les différents événements de log et qu'ils n'ont pas <em>vraiment</em> de logique (ie. ils n'ont pas de section <strong>filter</strong> mais juste les sections <strong>input</strong> et <strong>output</strong>).</p>

<h2>Le mode de déploiement sans agent</h2>

<h3>A la mode système</h3>

<p>Comme on a pu voir dans le paragraphe précédent, les machines hôtes disposent d'un agent Logstash complet. Cependant, ils n'ont pas vraiment de logique puisqu'ils ne font que transmettre les événements de logs au broker dont le rôle est de servir de buffer.</p>

<p>Cependant, parfois, il peut être intéressant de ne pas à avoir besoin d'installer un agent Logstash sur les machines hôtes :</p>

<ul>
<li>si la JVM déployé sur la machine hôte est limitée,</li>
<li>si la machine hôte est un périphérique qui dispose de peu de ressource et qu'il n'est pas possible d'y installer une JVM ou d'exécuter un agent,</li>
<li>s'il n'est pas possible d'installer n'importe quel logiciel sur la machine hôte.</li>
</ul>


<p>Pour répondre à cette problématique, il est possible d'utiliser des outils systèmes comme <strong>Syslog</strong>.</p>

<p>Dans ce cas, le serveur Logstash n'aura qu'à déclarer un <em>input</em> supplémentaire permettant d'écouter des événéments (dans notre cas, Syslog).</p>

<p>A titre informatif, il est possible d'utiliser un <em>Appender</em> syslog dans log4j ou logback (entre autre).</p>

<p><img src="/images/logstash/archi03.png" alt="center" /></p>

<h3>A la mode agent</h3>

<p>Dans le cas où ni un agent Logstash ni Syslog ne sont envisageables, il est possible d'utiliser <a href="https://github.com/elasticsearch/logstash-forwarder">Logstash Forwarder</a> (anciennement Lumberjack).</p>

<p>Il s'agit d'un client légé permettant d'envoyer des messages à Logstash en offrant un protocole maison intégrant de la sécurité (encryption SSL) ainsi que de la compression.</p>

<p>Il a été conçu pour être petit avec une faible emprunte mémoire tout en étant rapide. Il a été écrit en <a href="http://golang.org/">Go</a>.</p>

<p>Dans ce cas, il suffit d'exécuter logstash-forwarder avec les <em>bons</em> fichiers de configuration spécifiant l'adresse du serveur cible ainsi que l'emplacement du certificat et les fichiers à scruter.</p>

<p>Du coté serveur, il suffit, tout comme pour le mode sans agent à base de Syslog, de déclarer un <em>input</em> lumberjack.</p>

<p>A noter que d'autres <em>shipper</em> sont également disponibles tels que :</p>

<ul>
<li><a href="https://github.com/josegonzalez/beaver">Beaver</a></li>
<li><a href="https://github.com/danryan/woodchuck">Woodchuck</a></li>
</ul>


<h1>Les filtres</h1>

<p>Logstash vient avec un système de filtre qu'il est possible de configurer via la section <strong>filter</strong>.</p>

<p>Ces filtres permettent de filtrer mais également de modifier (via <strong>mutable</strong>) le contenu de l'événement. Ils permettent également de <em>parser</em> les événements (via <strong>grok</strong>) afin de les rajouter lors de la phase d'indexation (et donc de stockage). Cela permet ainsi de pouvoir rechercher des événements de manière plus ciblé.</p>

<p>Il existe plusieurs stratégies lors de l'utilisation de filtres :</p>

<ul>
<li>filtrer les événements sur l'agent,</li>
<li>filtrer les événements sur le serveur central,</li>
<li>émettre les événements au bon format.</li>
</ul>


<p>Le plus simple est encore d'émettre les logs au bon format, cependant, cela n'est pas toujours possible (trop de log différents, systèmes hétérogènes, code legacy, &hellip;).</p>

<p>Une autre manière de faire est d'exécuter le filtrage localement (ie. directement sur l'agent). Cela permet de réduire la charge de traitement du serveur central et d'être sûr que seuls les événements propres et structurés seront stockés. Cependant, cela oblige à maintenir une configuration plus complexe sur chaque agent.</p>

<p>A l'inverse, si le filtrage est effectué sur le serveur central, cela permet de centraliser les filtres et permet donc une administration plus simple. Cependant, cela demande des ressources supplémentaires pour effectuer le filtrage sur un plus grand nombre d'événements.</p>

<h1>La scalabilité et Logstash</h1>

<p>Une des grande force de Logstash est qu'il est possible de le composer avec différents composants : Logstash lui-même, Redis comme <em>broker</em>, ElasticSearch et bien d'autres éléments qu'il est possible de composer via la configuration de Logstash.</p>

<p>Ainsi, il est possible de jouer à plusieurs niveaux pour répondre à telles ou telles problématiques comme la perte de messages, le fait d'avoir un SPOF (<em>Single Point Of Failure</em>) ou d'avoir un point de contention dans le système.</p>

<p>Par exemple, si Redis est utilisé comme broker entre les agents Logstash et le serveur central, il peut être intéressant de passer Redis en mode <em>failover</em> afin d'éviter une perte d'événements lors de la transmission de ces derniers. Pour ce faire, il suffit de configurer le plugin <strong>redis</strong> de la section <strong>output</strong>  avec l'option <code>shuffle_hosts</code> pour indiquer à l'agent Logstash de n'utiliser qu'un seul noeud Redis lors de sa phase d'écriture. Du coté du serveur central, il suffit d'ajouter (et de configurer) autant de plugin <strong>redis</strong> de la section <strong>input</strong> que de noeud.</p>

<p><img src="/images/logstash/archi04.png" alt="center" /></p>

<p>Afin de permettre à la partie stockage/indexation d'être scalable, il suffit de configurer ElasticSearch en mode cluster, ce qui est natif chez lui.</p>

<p>Enfin, il est possible de rendre le serveur central Logstash robuste à la panne en en créant d'autres instances (mode <em>failover</em>) qui partageront la même configuration.</p>

<p><img src="/images/logstash/archi05.png" alt="center" /></p>

<h1>Conclusion</h1>

<p>En conclusion de cet article où je ne suis pas rentré dans les détails (mais ce n'est pas ce qui m'intéressait&hellip;), on peut constater qu'il existe moultes façons de configurer Logstash (et son écosystème) qui dépendent à chaque fois des besoins.</p>

<p>Cela est rendu possible par l'architecture et la conception modulaire de Logstash et le fait qu'il est très simple de le <em>plugger</em> à différentes solutions.</p>

<p>Même si cela est évident, je trouvais utile de le marquer noir sur blanc dans un court article&hellip; ;&ndash;)</p>

<h1>Pour aller plus loin&hellip;</h1>

<ul>
<li><a href="http://logstash.net/">http://logstash.net/</a></li>
<li><a href="http://www.logstashbook.com/">http://www.logstashbook.com/</a></li>
<li><a href="http://blog.xebia.fr/2013/12/12/logstash-elasticsearch-kibana-s01e02-analyse-orientee-business-de-vos-logs-applicatifs/">http://blog.xebia.fr/2013/12/12/logstash-elasticsearch-kibana-s01e02-analyse-orientee-business-de-vos-logs-applicatifs/</a></li>
</ul>

]]></content>
  </entry>
  
</feed>
