Erste Rauchzeichen zu Apache Tomcat 8

Peter Roßbach
©Shutterstock/141crew

Nach zwei Jahren Entwicklung ist der erste Release Kandidat des Apache Tomcat 8 seit Anfang August 2013 verfügbar [1]. Es handelt sich um ein frühes Major-Release, das für mutige Entwickler gedacht ist. Auf keinen Fall ist der aktuelle Stand uneingeschränkt für die Produktion zu empfehlen. Es gibt noch jede Menge Aufgaben, die für einen echten Einsatz gelöst werden müssen. Mit einem ersten stabilen Release kann Anfang 2014 gerechnet werden.

Der Apache Tomcat 8 enthält den aktuellen Stand der Java EE 7 Spezifikation des Servlet API 3.1 [2], Java Server Pages 2.3 [3], Java Expression Language 3.0 [4] und eine Implementierung des WebSocket API 1.0 [5], die seit 22. Mai 2013 verfügbar sind. Noch gibt es in einigen Punkten Klärungsbedarf. Natürlich wurde die Chance genutzt den Tomcat Core weiter zu verbessern.

Im Servlet API 3.1 sind weitere Eigenschaften zur besseren Nutzung des Java NIO Stacks hinzugekommen. Mit dem ReadListener und WriteListener lassen sich nun weitere Verbesserungen des NIO-Durchsatzes umsetzen. Mit dem Listener können die Daten ohne Blockierung des Container Threads unabhängig gelesen und geschrieben werden. Der Entwickler bekommt eine elegante Kontrolle über die Verarbeitungen der Daten.

Als weitere Neuerung ist ein Upgrade des HTTP Protokolls hinzugekommen. Diese Eigenschaft ist nun die elegante Basis für die Integration des Websocket API in die Tomcat-Webcontainer-Welt. Mit der Verallgemeinerung ist der Spielraum für die Entwicklung eigener Protokolle auf der Basis einer HTTP Verbindung gegeben. Das proprietäre Websocket API des Tomcat 7 wird nun durch einen Java-Standard abgelöst. Erstmalig können nun portable Java-Webanwendungen mit Websockets geschrieben werden.

Definition eigener Daten-Protokolle

Als Basis für ein HTTP-Protokoll-Upgrade, wie es der WebSocket-Standard RFC 6455 vorschreibt, kann im Servlet API 3.1 ein spezieller Handler dafür registriert werden. Zur Registrierung der Upgrade-URL auf den Handler wird im Beispiel ein EchoServlet definiert (Listing 1). Dieses Servlet hat zur Aufgabe, den Upgrade-Handshake-Request zu beantwortet. Für die Verarbeitung ist es also notwendig, einen neuen EchoUpgradeHandler zu erzeugen (Listing 2). Jede Verbindung bekommt hierbei exklusiv eine Instanz des Handlers. Zur Unterscheidung verschiedener Protokolle oder Versionen können anfragespezifisch Protokoll-Handler im Servlet gesetzt werden. Jeder Handler wird zur Erzeugung der Datenbehandlung aufgefordert und erhält nach Abbau der Verbindung eine Benachrichtigung.

Listing 1: Http Upgrade-Protokoll-Handler mit einem Servlet registrieren

@WebServlet("/echo")
public class EchoServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse res)
            throws IOException, ServletException {

        if ("echo".equals(req.getHeader("Upgrade"))) {
            res.setStatus(101);
            res.setHeader("Upgrade", "echo");
            res.setHeader("Connection", "Upgrade");
            req.upgrade(EchoUpgradeHandler.class);
        } else {
            res.getWriter().println("Wrong upgrade: " + req.getHeader("Upgrade"));
        }
    }
}

Der Upgrade Handler sorgt dafür, dass nun ein ReadListener bzw. WriteListener an die WebConnection vermittelt wird. Mit diesem Listener wird der Datenaustausch realisiert. Im Falle des Echo Service werden die Daten sofort blockierend zurückgeschrieben und nicht asynchron mit einem separaten WriteListener. Sobald Daten angekommen sind oder geschrieben werden könnten, wird der Listener  mit einem der verfügbaren WebContainer Threads aufgerufen. Die Entwicklung eines bidirektionalen Full-Duplex-Protokolls, wie das Websocket-Protokoll, ist also umsetzbar. Im einfachen Echo-Beispiel wird die Kommunikation mit dem Client durch eine „exit“ Nachricht beendet. Eigene Datenprotokolle zu entwickeln und anzuwenden ist nicht trivial. Asynchrone Daten zu empfangen und zu senden ist hingegen schon eher praktikabel. Hier sind die neuen Read- und WriteListener eine wertvolle Hilfe (Listing 3).

Listing 2: Echo Upgrade Handler

public class EchoUpgradeHandler implements HttpUpgradeHandler {
    public EchoUpgradeHandler() {
    }

    @Override
    public void init(WebConnection wc) {
        try {
            ServletInputStream input = wc.getInputStream();
            EchoReadListenerImpl readListener =
                    new EchoReadListenerImpl(input, wc);
            input.setReadListener(readListener);
       } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    @Override
    public void destroy() {
    }
}

Listing 3: EchoReadListener

public class EchoReadListenerImpl implements ReadListener {
    private static final String CRLF = "rn";

    private ServletInputStream input = null;
    private WebConnection wc = null;

    EchoReadListenerImpl(ServletInputStream in,
                         WebConnection c) throws IOException {
        input = in;
        wc = c;
    }

    @Override
    public void onDataAvailable() throws IOException {
        StringBuilder sb = new StringBuilder();

        int len;
        byte b[] = new byte[1024];
        while (input.isReady()
                && (len = input.read(b)) != -1) {
            String data = new String(b, 0, len);
            sb.append(data);
        }

        OutputStream output = wc.getOutputStream();
        output.write((sb.toString() + CRLF).getBytes());
        output.flush();
        if(sb.toString().startsWith("exit")) {
            try {
                wc.close();
            } catch (Exception ex) {
                // ignore
            }
        }
    }

    @Override
    public void onAllDataRead() throws IOException {
    }

    @Override
    public void onError(final Throwable t) {
    }
}

Aufmacherbild: An old painted cigar store indian wood carving von Shutterstock / Urheberrecht: 141crew

[ header = Erste Rauchzeichen zu Apache Tomcat 8 – Teil 2 ]

Echtzeitkommunikation mit WebSockets

Wer aber trotzdem die Aufgabe einer bidirektionalen Echtzeitkommunikation bekommt, z.B. mit einer Browseranwendung, ist sicherlich gut beraten, diese auf der Basis von WebSockets umzusetzen. Die Java-Websocket-Client- und Server-Spezifikation ist übersichtliche 33 Seiten lang. Die WebSocket-Handler bzw. Endpoints können mit einem API oder mit Hilfe von Annotationen definiert werden. Jeder Endpoint repräsentiert genau eine Verbindung zu seinem Client und wird also exklusiv genutzt. Der Zustand eines Clients kann in der Instanz des Endpoints gespeichert werden. Allerdings muss die Verbindung zum Client dafür stabil sein. Besser ist es sicherlich, die Informationen in einer eigenen Zustandsverwaltung zu speichern. Das simple Echo-Beispiel merkt sich die Verbindung für die Ausgabe. Jede eingehende Nachricht wird direkt synchrone zu ihrem Client zurückgeschickt. Der Tomcat unterstützt hierbei direkt die ServerEndpoint-Annotation und sorgt für die Integration in den WebSocket-Container (Listing 4). In einem Webcontainer ist der Zugriff auf die Autorisierung und Http-Session bei der Verbindungsannahme möglich. Natürlich kann der Tomcat via WebSocket Client API auf andere Websocket-Services zugreifen. Sogar die Transformation von WebSocket-Nachrichten mit eigenen Encodern und Decodern wird durch den Standard unterstützt. Natürlich können neben einem String, Objekten, auch Byte-WebSocket-Nachrichten verarbeitet werden. Damit ist es elegant und einfach eigene Anwendungsprotokolle auf der Basis des Java-WebSockets-Standards im Tomcat bereitzustellen. Weitere kleine Beispiel sind in der Tomcat Distribution enthalten. Ein Chat-Service und eine einfache Variante des bekannten Snake-Spiels zeigen, wie man mit der Entwicklung eines Websocket-Service startet.

Listing 4: Echo mit dem Websocket API

@ServerEndpoint("/echo")
public class Echo {

    Session session ;

    @OnOpen
    public void open(Session session) {
        this.session = session;
    }

    @OnMessage
    public void echo(String msg) {
        try {
            if (session.isOpen()) {
                session.getBasicRemote().sendText(msg);
            }
        } catch (IOException e) {
            try {
                session.close();
            } catch (IOException e1) {
                // Ignore
            }
        }
    }
}

Einfacher ausdrücken mit EL 3.0

Die Neuerungen in der JSP-Spezifikation sind übersichtlich, sie beziehen sich auf die erweiterte Expression Language (EL) und Klärungen aus der Verwendung des bestehenden API. Mit der EL 3.0 ist eine Vielzahl von neuen Eigenschaften verfügbar:

  • Lambda-Ausdrücke, ähnlich denen in Java 8, werden unterstützt.
  • Das Erzeugen von Sets, Lists und Maps wird unterstützt.
  • Es können mehrere Ausdrücke mit dem „;“ Operator getrennt formuliert werden.
  • Es kann auf statische Felder und Methoden beliebiger Klassen zugegriffen werden.
  • Mit dem EL Stream API können Filter, wie z.B. Sum, Map, Reduce, oder ForEach zur Verarbeitung von Elementen einer Collection bereitgestellt werden. Es können so direkt Aggregationen von Listen oder die Auswahl von Elementen in einer JSP/JSF Seite programmiert werden.
  • Erstmalig kann das EL API unabhängig von JSP oder JSF-Container eingesetzt werden.

Nun ist es also in einem Servlet oder Anwendungsservice problemlos möglich, einen ELProcessor zu erzeugen und einen Ausdruck auf die übergebenen Daten auszuführen. Das SumEL Servlet summiert die übergebenen Parameter (v) mit der vorhandenen sum-Funktion und einer ForEach-Schleife (Listing 5).

Listing 5: Verwendung eines unabhängigen ELProcessors

@WebServlet("/sum")
public class SumEL extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse res)
            throws ServletException, IOException {
        List list = new ArrayList() ; String params[] = req.getParameterValues("v"); for(String param: params) { list.add(Double.valueOf(param)); } ELProcessor elp = new ELProcessor(); elp.defineBean("values", list); Object message = elp.eval( "values.stream().sum()"); res.getWriter().println(message); message = elp.eval( "sum = 0; " + "values.stream().forEach( " + "d -> (sum = sum + d)); " + "sum"); res.getWriter().println(message); } }

Erweiterung des Tomcats

An der Apache Tomcat Code-Basis wird weiter optimiert. Durch die Erweiterungen der Spezifikation im Bereich der asynchronen nicht blockierenden Kommunikation, sind nun die Java–NIO-Konnektoren die Voreinstellung in der Serverkonfiguration. Natürlich sind die bewährten BIO-Konnektoren weiterhin vorhanden, aber erste Überlegungen existieren, diese für das nächste Major Release zu eliminieren. Eine rudimentäre SPDY-Implementierung auf der Basis des APR Connectors liegt ebenfalls vor. Das Mapping der URL wird nun im Tomcat Service für alle Konnektoren und Anwendungen zentral bereitgestellt. Dies spart etwas Speicher und bringt Konsistenz. Völlig überarbeitet wurde die Implementierung der Ressourcen-Behandlung. Das Laden von Dateiressourcen via Alias, War, Jar oder das Laden der Klassen wurden nun mit einer erweiterbaren Strategie umgesetzt. Damit besitzt der Tomcat 8 schon die Basis für die Überlagerung von Webanwendungen, die im Java EE 8 Standard möglicherweise enthalten sind.

Als Betriebsbasis wird nun Java 7 vorausgesetzt. Dazu wurde die im aktuelle Release-Kandidat RC 1 noch nicht fertiggestellte DBCP 2.0 Implementierung des JDBC 4.1 Standard integriert. Aus dem Apache Tomcat 5.5 wurde das Module StoreConfig zur Speicherung von Laufzeitkonfigurationen portiert. Ein Vielzahl von kleineren Änderungen und Erweiterungen sind im aktuellen Changelog des Projekts zu finden.

Ausblick

Ein weiterer notwendiger Schritt zur komfortablen Java-Programmierung auf der Basis von HTTP ist mit der Java EE 7 Spezifikation vollbracht. Am aktuellen Stand des Apache Tomcats in der Version 8 wird sich bestimmt noch einiges ändern. Die Neuerungen sind erstaunlich leistungsfähig und der Entwicklung in einem nicht zeitkritischen Projekt steht nichts mehr im Wege. Eure Mitwirkung ist nun gefragt, die vorhandenen Eigenschaften der Java EE 7 Spezifikation und des Apache Tomcats zu testen und auf weitere Ergänzungen aufmerksam zu machen oder sie vielleicht gleich zu implementieren.

Geschrieben von
Peter Roßbach
Peter Roßbach
Peter Roßbach ist ein Infracoder, Systemarchitekt und Berater vieler Websysteme. Sein besonderes Interesse gilt dem Design und der Entwicklung von komplexen Infrastrukturen. Er ist Apache Tomcat Committer und Apache Member. Mit der bee42 solutions gmbh realisiert er Infrastrukturprodukte und bietet Schulungen auf der Grundlage des Docker-Ökosystems, aktuellen Webtechnologien, NoSQL-Datenbanken und Cloud-Plattformen an.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.