Accueil > Code > Bases de la réécriture d’URL en Java

Bases de la réécriture d’URL en Java

mardi 20 décembre 2011, par Nicolas

Introduction

Ayant expérimenté avec Java sur le web, je me suis logiquement intéressé à la réécriture d’URL.

La réécriture d’URL consiste à transformer des liens sur les pages web, afin par exemple qu’un lien apparaisse sous la forme http://nicolas.weeger.org/-Code-.html au lieu de http://nicolas.weeger.org?rubrique2 (sur ce site, la rubrique « Code » a pour identifiant interne 2). Ceci est plus « agréable » à lire, et permet de mieux référencer la page dans les moteurs de recherche notamment.

Pour cela il y a deux aspects symétriques, l’écriture d’URLs « propres » dans les pages HTML (« URLs sortantes ») d’un côté, la gestion par le serveur de la correspondance entre l’URL propre et les données internes de l’autre (« URLs entrantes »). Soit d’un côté déterminer que « un lien vers la rubrique 2 s’écrit -Code-.html », de l’autre déterminer que « la page -Code-.html correspond à la rubrique 2 ».

Dans le monde Apache + PHP, ces URLs sont traitées de façon différente.

Apache via mod_rewrite pré-traite les URLs et transmet les informations à PHP, qui fait ce qu’il faut pour déterminer le résultat final. Ainsi sur ce site « -Code-.html » appelle un script PHP en lui transmettant les informations « page=rubrique » (grâce aux tirets autour du nom), ce script faisant à partir de « Code » la correspondance avec l’identifiant interne 2.

Il ne me semble pas exister de librairie pour les URLs sortantes, laissant à chaque langage ou framework la responsabilité d’implémenter quelque chose. Ainsi SPIP, pour ce site, gère différents types d’URL selon la configuration (simple, arborescence, propre).

Dans le monde Java, le standard semble être UrlRewriteFilter, une librairie qui permet de spécifier des règles pour les URLs à la fois entrantes et sortantes, et de réécrire automatiquement celles-ci sans travail supplémentaire, pourvu que les règles habituelles Java aient été appliquées.

En fouillant dans le code de cette librairie, voici en résumé comment fonctionne la réécriture.

Note : le code Java qui suit n’est pas complet (il manque les import et de nombreuses choses). Il n’est donné qu’à titre indicatif, la meilleure lecture étant de fouiller dans le code de UrlRewriteFilter.

Traitement des URLs entrantes

L’interception des URLs se fait via une classe implémentant Filter.

La méthode doFilter permet d’intercepter l’URL demandée, de la modifier, puis de recommencer le traitement sur cette URL.

Supposons que le but soit de faire correspondre fiche/n à la page fiche ?id=n. Le code est assez simple :

package org.weeger.Servlet;
public class TestFilter implements Filter {
  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
  }
  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    final HttpServletRequest r = (HttpServletRequest)request;
    final Pattern p = Pattern.compile(r.getContextPath() + "/fiche/(\\d+)");
    final Matcher m = p.matcher(r.getRequestURI());
    if (m.matches()) {
      request.getRequestDispatcher("/fiche?id=" + m.group(1)).forward(request, response);
      return;
    }
    chain.doFilter(request, response);
  }
  @Override
  public void destroy() {
  }
 
}

L’URL contenant le contexte de l’application, il faut en tenir compte pour l’expression régulière.

Il faut ensuite configurer le serveur Java pour utiliser ce filtre, ce qui donne un fragment XML à insérer dans web.xml :

    <filter>
        <filter-name>Test</filter-name>
        <filter-class>org.weeger.Servlet.TestFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>Test</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>

Bien sûr un bon IDE vous permettra un paramétrage plus simple qu’une modification manuelle.

Génération d’URLs réécrites

La façon naïve de générer des URLs « propres » consiste à manuellement concaténer « fiche/ » et l’identifiant correspondant.

Cependant, la méthode JSP de génération d’URL, si elle est utilisée sous la forme

<c:url value="/fiche" var="url">
<c:param name="id" value="${fiche.idFiche}" />
</c:url>

peut être automatiquement convertie.

Cela se fait via une surcharge des différentes méthodes encodeURL de la classe HttpServletResponseWrapper, de la forme :

  private class WrappedResponse extends HttpServletResponseWrapper {
    final private String _context;
    public WrappedResponse(HttpServletResponse response, final String context) {
      super(response);
      _context = context;
    }
    @Override
    public String encodeURL(String url) {
      Pattern p = Pattern.compile(_context + "/fiche\\?id=(\\d+)");
      Matcher m = p.matcher(url);
      if (m.matches()) {
        return _context + "/fiche/" + m.group(1);
      }
      return super.encodeURL(url);
    }
  }

Note : il faudrait surcharger les variantes et les méthodes obsolètes, ceci est laissé comme exercice au lecteur.

Et pour utiliser cette nouvelle class WrappedResponse, il suffit de faire un filtre de même façon que précédemment :

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    final HttpServletRequest r = (HttpServletRequest)request;
    final HttpServletResponse hsResponse = (HttpServletResponse)response;
    final WrappedResponse wr = new WrappedResponse(hsResponse, r.getContextPath());
    chain.doFilter(request, wr);
  }

Ceci convertira les liens sans modification du .jsp, à condition bien sûr que les expressions régulières correspondent aux URLs.

Conclusion

Je dois admettre aimer l’idée adoptée par Java d’unifier le traitement de toutes les URLs. Cela simplifie nettement la vie sur la configuration, embarquée dans le .war de déploiement, contrairement à une solution Apache + PHP qui nécessite des règles soit sur la configuration du site soit dans les fichiers .htaccess, plus l’écriture de scripts PHP.

L’inconvénient bien sûr de réécrire à la volée les URLs sortantes est qu’il faut être cohérent sur l’ordre des paramètres pour que le traitement se fasse proprement. Mais il me semble que c’est un prix à payer suffisamment faible pour être ignoré.