Browser-Caching bei GWT-Applikationen

Versionsunabhängige Information

Beschreibt, wie man über HTTP Header steuern kann, ob bzw. wie lange vom Webserver geladene Dateien im Browser gecacht werden. Dieser Aspekt dient dem Tuning des jadice web toolkit.

Browser-Caching und GWT

Zum besseren Verständnis wird zunächst ein Überblick über den Bootstrap-Prozess einer GWT-Applikation gegeben:

  1. Der Browser lädt und verarbeitet die Einstiegs-Hostseite der Web Application, z.B. index.html.
  2. Diese verweist auf <modulename>.nocache.js. Diese Datei wird heruntergeladen und der enthaltene JavaScript ausgeführt.
  3. Die <modulename>.nocache.js -Datei wiederum enthält JavaScript-Code, der die Deferred-Binding-Konfigurationen auflöst und auf eine der  *.cache.html Dateien verweist.
  4. Der JavaScript Code in <modulename>.nocache.js erzeugt einen versteckten  <iframe>, fügt dort den DOM der Hostseite ein, und lädt die *.cache.html -Datei in diesen iframe.
  5. Die *.cache.html -Datei enthält die tatsächliche Programmlogik der GWT-Applikation.

Damit kann nun das so genannte "Perfect Caching” von GWT zum Tragen kommen:

  • Einstiegs-Hostseite der Web Application: index.html
    • In manchen Anwendungsfällen kann es sinnvoll sein, diese Datei nicht zu cachen. Insbesondere in der Entwicklungsumgebung, wenn unterschiedliche Anwendungen zeitversetzt unter ein und derselben Hostadresse debuggt werden sollen, darf die Einstiegsdatei nicht im Browser gecacht werden. Da die Einstiegs-Hostseite üblicherweise sehr klein ist, ist das neuerliche Herunterladen vom Server nicht teuer und damit unkritisch.
    • In einem produktiven Szenario könnte diese Datei aber auch gecacht werden, da sie sich im Lebenszyklus einer deployten Anwendung so gut wie nie verändert.
  • Bootstrap Script: <modulename>.nocache.js
    • Diese Datei darf aus den oben genannten Gründen nicht gecacht werden.
  • GWT application file: *.cache.html / *.cache.js / *.cache.png
    • Diese Dateien dürfen und sollten gecacht werden. Sie enthalten die Programmlogik und sind vergleichsweise groß (je nach Anwendung ein bis mehrere MB).
      Die Dateien sind entsprechend des MD5-Hashes ihres Inhalts benannt. Wird die Anwendung ohne Codeänderungen rekompiliert, so ändert sich ihr Inhalt nicht und der MD5-Hash bleibt bestehen. Nur wenn der Code sich ändert, ändern sich auch der MD5-Hash und in Konsequenz der Dateiname, so dass ab sofort die "neue" Datei vom neuen (ungecachten) Bootstrap Script <modulename>.nocache.js angefordert wird.
  • _<alphanumeric>_.gwt.rpc

    • Die Serialization-Policy-Dateien enthalten seit GWT 1.4 diejenigen Datentypen, die java.io.Serializable implementieren und demzufolge für GWT RPC-Calls verwendet werden können. Die Dateien werden ebenfalls vom GWT Compiler erzeugt; der Dateiname ergibt sich als MD5-Hash des Inhalts..

    • Die Daten sind vergleichsweise klein (typischerweise zwischen 2 und 30 kB), so dass das für sie eingestellte Caching-Verhalten von untergeordneter Bedeutung ist. Entsprechend ihrer Namenskonvention ist ein Cachen der Dateien durchaus möglich, da sich bei Codeänderungen der Dateiname ändert und demzufolge eine neue Datei angefordert wird.

Caching über den HTTP-Header steuern

Das Caching-Verhalten des Browsers wird über den HTTP-Header der Server-Response gesteuert. Zum Unterbinden des Caching dienen beispielsweise die folgenden Einstellungen:

      httpServletResponse.setHeader("Cache-control", "no-cache, no-store, must-revalidate");
      httpServletResponse.setDateHeader("Expires", -1);
      httpServletResponse.setHeader("Pragma", "No-cache");

Die Literatur zu den einzelnen Einstellungen ist dabei nicht einheitlich. Einigkeit besteht darin, dass ein "Expires"-Date<=0 unabdingbar ist, um zuverlässig bei jedem Zugriff ein erneutes Anfordern der Seite am Server auszulösen. Die Header-Anweisungen "Cache-control: no-cache, no-store, must-revalidate" und "Pragma: no-cache" scheinen tendenziell optional bzw. browserabhängig.

Wichtig ist hierbei, dass diese Header in der HTTP Servlet-Response gesetzt werden müssen. Offensichtlich unterstützen die wenigsten Brower direkt in HTML-Dateien angegebene "meta http-equiv"-Tags. Jedenfalls nicht, wenn die Datei über einen Server angefordert wird. Nur beim Laden aus dem Filesystem werden diese Tags browserseitig respektiert. Deshalb reicht es nicht, direkt in der index.html die entsprechenden meta-Tags anzugeben. Zudem existieren Unterschiede zwischen den Browsern.

Programmatisch setzt man die Header am einfachsten über einen eigens implementierten javax.servlet.Filter, der über die web.xml geeignet einkonfiguriert wird.

Beispiele dazu sind in den Demos des jadice web toolkit enthalten und werden im Folgenden kurz beschrieben.

Browser-Caching im jadice web toolkit

Die jadice web toolkit Demos werden mit sinnvoll vorkonfigurierten Filtern ausgeliefert.

Filterklassen

In den jadice web toolkit Demos werden zum Einstellen des Browser-Cachings zwei Filterklassen eingesetzt:

  • com.levigo.jadice.web.demo.common.server.util.CacheFilter
  • com.levigo.jadice.web.demo.common.server.util.NoCacheFilter

Der CacheFilter setzt bei allen URIs der Form .*\\.cache\\.(js|jpg|png|gif|css|html)

      httpResponse.setHeader("Cache-Control", "public, max-age=" + seconds + "");
      httpResponse.setDateHeader("Expires", System.currentTimeMillis() + 1000L * seconds);
     // By default specified by some servlet containers (like tomcat with SSL)
      if (httpResponse.containsHeader("Pragma")) {
        httpResponse.setHeader("Pragma", null);
      }

Der NoCacheFilter setzt bei allen Dateien der Form .*\\.nocache\\.(js|jpg|png|gif|css|html) und index.* die folgenden Response Header:

      httpServletResponse.setHeader("Cache-control", "no-cache, no-store, must-revalidate");
      httpServletResponse.setDateHeader("Expires", -1);
      httpServletResponse.setHeader("Pragma", "No-cache");

Konfiguration in der web.xml

Das Filter-Mapping wird beispielsweise für den NoCacheFilter in der web.xml-Datei der Anwendung wie folgt konfiguriert:

    <filter>
        <filter-name>NoCacheFilter</filter-name>
        <filter-class>com.levigo.jadice.web.demo.common.server.util.NoCacheFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>NoCacheFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

Literaturhinweise

http://www.gwtproject.org/doc/latest/DevGuideCompilingAndDebugging.html#perfect_caching

http://www.gwtproject.org/doc/latest/FAQ_DebuggingAndCompiling.html

http://stackoverflow.com/questions/17824848/is-meta-http-equiv-value-cache-control-is-not-supported.