Catch 304 – Der faule Trick

Beim Experimentieren mit der Spring Boot Rest-Schnittstelle fiel mir wieder einmal auf, dass ich beruflich nur wenige Rest-Services kennengelernt habe, die den HTTP Status Code 304 verwenden. Bei den Projekten, an denen ich teilgenommen habe, gab es tatsächlich nur eine einzige Rest-Schnittstelle, die von diesem Feature gebraucht machte.

Der HTTP Status Code 303 NOT Modified zeigt an, dass sich die Resource auf dem Server seit dem letzten Aufruf nicht verändert hat. Damit erspart sich ein Client eine Menge Arbeit, denn er braucht die Daten nicht vom Server holen und auch nicht verarbeiten. Stattdessen kann er die schon vorhandenen Informationen weiterverwenden.

Es gibt verschiedene Bedingungen, die der Server zur Prüfung verwenden kann. Normalerweise verwendet man die Header If-Modified-Since oder If-Non-Match.  Bei der Header If-Modified-Since <Zeitpunkt> prüft der Server, ob sich die Resource seit dem angegebenen Zeitpunkt verändert hat. Bei If-Non-Match <ETag> wird die Änderung der Resource über ein Tag geprüft. Dieses Tag ändert sich bei Änderungen an der Resource. Häufig wird dazu ein Hashwert verwendet, es könnte aber auch ein einfacher Zähler sein. Der Server liefert diesen Tag bei jedem Zugriff auf die Resource als ETag Header mit zurück und ein Client kann das Tag auf Änderung prüfen lassen.

axios.get("http://<SERVER>/ancestors", { 
  validateStatus: function (status) { return status >= 200 && status < 300 || status == 304; },
  headers: {'If-None-Match': this.state.etag }})
.then(response => {
  if (response.status == 200) {
    this.setState(Object.assign({}, this.state, { people: response.data, etag: response.headers.etag }));
  }
});

Das kurze axios Beispiel zeigt noch einmal die recht einfach Verwendung von If-Non-Match in einem Javascript Client. Der Client speichert den ETag aus dem Response Header in dem Attribute this.state.etag und sendet es mit jeder Anfrage im HTTP Header If-None-Match an den Server. Eine kleine doofe Eigenschaft der axios Bibliothek ist es, standardmäßig den Response Code 304 als Fehler anzusehen. Deshalb muss hier die validateStatus Funktion angepasst werden.

Auf der anderen Seite muss aber auch der Server dazu gebracht werden, dass ETag zu liefern und die Header If-Non-Match auszuwerten. Bei Spring Boot ist dies, wie nicht anders zu erwarten, sehr einfach. Der gesamte notwendige Code folgt in den nächsten vier Zeilen.

@Bean
public Filter filter() {
  return new ShallowEtagHeaderFilter();
}

Eine Kleinigkeit die im vorherigen Beitrag Neues Material unterschlagen wurde, ist das Cross-Origin Resource Sharing (CORS). Da unsere Web-Applikation zusätzlich Daten von einem Rest-Service bezieht, muss dem Web-Browser die Unbedenklichkeit dieser Verbindung mitgeteilt werden. Auch dies ist mit Spring Boot kein Hexenwerk.

@Bean
public CorsConfigurationSource corsConfigurationSource() {
  CorsConfiguration configuration = new CorsConfiguration();
  configuration.setAllowedOrigins(asList("<SERVER>"));
  configuration.setAllowedMethods(asList("HEAD", "GET"));
  configuration.setAllowCredentials(true);
  configuration.setAllowedHeaders(asList("Authorization", "Cache-Control", "Content-Type", "If-None-Match"));
  configuration.setExposedHeaders(asList("ETag"));
  UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
  source.registerCorsConfiguration("/**", configuration);
  return source;
}

@Override
protected void configure(HttpSecurity http) throws Exception {
  http.cors().and().<AUTHORIZATION>
}

Damit unser oben beschriebener Trick weiterhin funktioniert, müssen wir die notwendigen Header ETag und If-None-Match in der CORS Konfiguration angeben.

Intelligent eingesetzt kann der Status Code 304 Not Modified die Geschwindigkeit der Datenübertragung und die Reaktionszeit der Applikation drastisch erhöhen.