L’API Servlet de Java EE 8 propose un support d’HTTP/2 et en particulier du Server Push.
Comme indiqué dans le précédent article de la série, le Server Push permet au serveur d’envoyer de manière pro-active des ressources au client. Les ressources ainsi envoyées peuvent alors être stockées par le client et ainsi éviter l’envoi de requêtes.
Evidemment cela requiert de la part du serveur de connaître quelles sont les ressources dont le client va avoir besoin pour lui envoyer par anticipation.
Cet article fait partie de la série HTTP/2, contenant les articles suivants :
- HTTP/2 : introduction
- HTTP/2 : les détails
- HTTP/2 : les anciennes pratiques à éviter maintenant
- HTTP/2 : l’API HTTP Client de Java 11
- HTTP/2 : Push serveur
- HTTP/2 : Push serveur avec Java EE 8
Le support du Server Push dans HTTP/2
L’API Servlet 4.0 de Java EE 8, définie dans la JSR 369, propose un support de HTTP/2. La majorité du support de HTTP/2 est pris en charge par l’implémentation de l’API Servlet utilisée. La partie la plus visible de ce support dans l’API est la possibilité d’utiliser le Server Push.
Le serveur peut proactivement envoyer des ressources liées à celle demandée en utilisant la fonctionnalité Server Push d’HTTP/2.
Cela permet à un navigateur de mettre ses ressources en cache et ainsi éviter des requêtes supplémentaires. Ces ressources peuvent être de différentes natures mais par exemple pour une page HTML, on pourrait retrouver des feuilles de style CSS, des images, des fichiers JavaScript, … Le temps de téléchargement des ressources est alors réduit, ce qui améliore l’expérience utilisateur.
L’utilisation du Server Push implique donc que le serveur connaisse les ressources associées à la requête initiale pour lui permettre de les envoyer de manière proactive si le protocole est supporté.
La mise en œuvre requiert l’obtention d’une instance de type PushBuilder à partir de l’HttpServletRequest.
Exemple : invocation d’une servlet qui renvoie une page HTML ayant besoin d’une feuille de styles CSS et d’une image
L’interface javax.servlet.http.PushBuilder
Pour la mise en œuvre du Server Push, l’API Servlet 4.0 propose l’interface javax.servlet.http.PushBuilder. Cette interface met en œuvre le design pattern builder.
Une instance de type PushBuilder est obtenue en invoquant la fabrique getPushBuilder() de la classe HttpServletRequest.
L’instance obtenue possède une configuration par défaut :
- le verbe HTTP est GET
- quelques headers sont initialisés
Pour chaque ressource à envoyer, il faut configurer le PushBuilder. La seule configuration obligatoire est l’URI de la ressource en utilisant la méthode path().
Il est aussi possible de configurer plusieurs informations sur la ressource :
- des headers avec les méthodes addHeader(), setHeader() et removeHeader()
- le verbe à utiliser avec la méthode method()
- les paramètres de la requête avec la méthode queryString()
Les invocations de chacune de ces méthodes peuvent être chaînées.
Une fois la configuration terminée, l’invocation de la méthode push() permet d’envoyer la ressource.
L’instance de type PushBuilder peut être réutilisée : sa configuration est conservée sauf l’URI et les headers conditional.
Attention : comme indiqué dans le premier article de cette série, les principaux navigateurs ne mettent en oeuvre HTTP/2 qu’au travers d’une connexion sécurisée.
Si la connexion n’est pas sécurisée ou si le client a désactivé le serveur push dans la requête alors la méthode newPushBuilder() renvoie null. Il faut donc vérifier si l‘instance obtenue n’est pas null avant son utilisation.
Un exemple de mise en œuvre
La servlet ci-dessous renvoie une page HTML avec éventuellement la feuille de style CSS et l’image qu’elle requière si le mode Push est utilisable.
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.PushBuilder;
import javax.ws.rs.core.MediaType;
@WebServlet(urlPatterns = "/testpush")
public class PushServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException,
ServletException {
PushBuilder pushBuilder = req.newPushBuilder();
if (pushBuilder != null) {
System.out.println("Utilisation du push server : "+req.getProtocol());
pushBuilder.path("main.css").push();
pushBuilder.addHeader("content-type", "image/jpeg").path("images/logo.jpg").push();
}
res.setContentType(MediaType.TEXT_HTML_TYPE.withCharset(UTF_8.name()).toString())
res.setStatus(200);
try(PrintWriter resWriter = res.getWriter();){
resWriter.println("<html><head><title>HTTP2 Push</title><link rel=\"stylesheet\" href=\"main.css\"></head>");
resWriter.println("<body>Servlet Push</body><img src=\"images/logo.jpg\" alt=\"logo\" /></html>");
}
}
}
Il est possible d’utiliser les outils de développement des navigateurs pour voir les échanges réseaux
Exemple avec Chrome Dev tools :
L’utilisation du Push Server dans un filtre
Il est aussi possible d’utiliser le Push Server dans un filtre de Servlet. Cela permet d’appliquer le Push Server sur différentes ressources.
Il suffit d’utiliser l’interface PushBuilder dans la méthode doFilter() du filtre pour envoyer les ressources selon l’URI de la requête.
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.PushBuilder;
@WebFilter("/*")
public class PushFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String uri = httpRequest.getRequestURI();
System.out.println("Filtre pour uri " + uri + " : " + request.getProtocol());
switch (uri) {
case "/serverpush/maservlet":
PushBuilder pushBuilder = httpRequest.newPushBuilder();
if (pushBuilder != null) {
System.out.println("Utilisation du push server pour uri " + uri + " : " + request.getProtocol());
pushBuilder.path("main.css").push();
pushBuilder.addHeader("content-type", "image/jpeg").path("images/logo.jpg").push();
}
break;
default:
break;
}
chain.doFilter(request, response);
}
}
Le support de PushBuilder dans Spring MVC
Spring MVC propose un support de l’API Servlet 4.0.
Pour obtenir une instance de type PushBuilder, il suffit de demander son injection en définissant un paramètre de type PushBuilder à une méthode annotée avec @RequestMapping.
Conclusion
L’API Servlet 4.0 propose un support d’HTTP/2 côté serveur avec notamment une certaine facilité pour mettre en œuvre le Server Push.