Mit Java auf dem HTTP/2-Zug

Seite 3: HTTP/2 und Java

Inhaltsverzeichnis

Um innerhalb einer Java-Applikation die Vorteile von HTTP/2 zu nutzen, ist es zunächst notwendig, entsprechende Bibliotheken oder Applikationsserver zu verwenden. Wenn das der Fall ist, bekommt man zwei der neuen Funktionen von HTTP/2 geschenkt, ohne dass dafür eine eigene Implementierung notwendig ist: Das Multiplexing und die Komprimierung mit HPACK erledigt der Java-HTTP/2-Client beziehungsweise -Server automatisch im Hintergrund, ohne dass Anwendungsentwickler es explizit anstoßen. Was dabei genau zu tun ist, erläutern die nachfolgenden Codebeispiele.

Der komplette und lauffähige Quellcode der Beispiele ist auf GitHub verfügbar. Er basiert auf den nachfolgend genannten Frameworks und dem Java Development Kit 9.

Glassfish 5.0 gehört zu den ersten Java-EE-Applikationsservern, die Servlet4 unterstützen. Die passende Dependency in Maven sieht folgendermaßen aus:

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.0</version>
<scope>provided</scope>
</dependency>

Damit steht HTTP/2 zur Verfügung und läuft automatisch im Hintergrund. Lediglich den Server Push müssen Entwickler eigenhändig implementieren. Der folgende Code erzeugt ein Servlet, das beim Aufruf der Seite https://localhost:8181/Servlet4Push/http2 HTML ausliefert:

import javax.servlet.http.HttpServlet;
import javax.servlet.http.PushBuilder;
import ...

@WebServlet(value = {"/http2"})
public class Http2Servlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {

PushBuilder pushBuilder = req.newPushBuilder();
if (pushBuilder != null) {
pushBuilder
.path("images/cat.png")
.addHeader("content-type", "image/jpeg").push();
pushBuilder.path("http2-json")
.addHeader("content-type", "application/json").push();
}
try (PrintWriter respWriter = resp.getWriter();) {
respWriter.write("<html>" +
"<img src='images/cat.jpg'>" +
"<p>Image by <a href=\"https://flic.kr/p/HPf9R1\">" +
"Andy Miccone</a></p>" +
"<p>License: <a href=\"https://creativecommons.org/" +
"publicdomain/zero/1.0/\">" +
"CC0 1.0 Universal (CC0 1.0) \n" +
"Public Domain Dedication</a></p>" +
"</html>");
}
}
}

Es sendet zwei Push Promises an den Client. Zuerst bietet es eine Bilddatei an und versendet anschließend ein Push Promise für ein kurzes Stück JSON-Code. Zum Implementieren lässt sich über die Methode newPushBuilder() der Klasse HttpServletRequest ein passender PushBuilder erzeugen. Dieser erhält einen Pfad sowie einen Content Type. Der Aufruf der Methode push() schickt ein Push Promise an den Client.

Das Java-Framework Spring Boot ist ab der aktuellen Version 2.0.0.M6 in Kombination mit Tomcat 9 ebenfalls in der Lage, Servlets der Version 4 bereitzustellen. Der folgende Code zeigt die Konfiguration des eingebetteten Tomcat für HTTP/2:

@Bean
public TomcatServletWebServerFactory tomcatCustomizer() {
TomcatServletWebServerFactory factory =
new TomcatServletWebServerFactory();

factory.addConnectorCustomizers((connector -> {
connector.addUpgradeProtocol(new Http2Protocol());
}));
return factory;
}

Anschließend lässt sich ein simpler RestController konfigurieren, der im folgenden Beispiel wiederum einen einfachen REST-Endpunkt anbietet. Beim Aufruf dieses Endpunktes sendet der Server ein Push Promise an den Client, in dem er ihm eine weitere JSON-Nachricht anbietet:

@RestController
public class GreetingController {
private static final String TEMPLATE = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();

@RequestMapping("/greeting")
public Greeting greeting(
HttpServletRequest request,
@RequestParam(value = "name",
defaultValue = "World") String name) {

PushBuilder pushBuilder = request.newPushBuilder();
pushBuilder.path("/push-greeting?name=push");
pushBuilder.push();

return new Greeting(counter.incrementAndGet(),
String.format(TEMPLATE, name));
}

@RequestMapping("/push-greeting")
public Greeting pushGreeting(
@RequestParam(value = "name",
defaultValue = "World") String name) {
return new Greeting(counter.incrementAndGet(),
String.format(TEMPLATE, name));
}
}

Die Implementierung eines HTTP/2-Clients verlangt noch weniger Codezeilen, wie im folgenden Listing zu sehen ist:

public void getRespResponse(){
RestTemplate okHttpRestTemplate =
new RestTemplate(new OkHttp3ClientHttpRequestFactory());
Greeting greeting = okHttpRestTemplate.getForObject(
"https://localhost:8443/greeting",
Greeting.class);
}

Entwickler müssen lediglich eine Instanz der Klasse RestTemplate erzeugen und verwenden dafür eine RequestFactory eines HTTP/2-fähigen Clients. Obiges Beispiel verwendet die OkHttp-Bibliothek.

Anschließend folgt der Aufruf der Instanz des RestTemplates mit der URL des Serverendpunkts. Das Ergebnis ist die handlich in einem Datenobjekt verpackte Antwort des Servers.

Im Zusammenhang mit Webservices, die HTTP/2 anbieten, ist immer daran zu denken HTTPS-verschlüsselte Dienste anzubieten. Somit ist es notwendig, ein TLS-Zertifikat zu erstellen und es dem Server zur Verfügung zu stellen. Für die lokale Entwicklungsumgebung lässt sich ein selbst signiertes Zertifikat erstellen und in einem Java Keystore speichern.

Folgende Properties-Datei konfiguriert ein TLS-Zertifikat für die Spring-Boot-Anwendung aus dem vorangegangenen Abschnitt:

server.port                   = 8443
server.port.http = 8080
server.ssl.key-store = classpath:sample.jks
server.ssl.key-store-password = secret
server.ssl.key-password = secret