2015-12-15 19 views
13

Mein ursprünglicher Zweck war die HTTP Chunked Transfer zu verifizieren. Aber zufällig fand diese Inkonsistenz.Warum gibt Tomcat unterschiedliche Header für HEAD- und GET-Anfragen an meine RESTful-API zurück?

Die API wurde entwickelt, um eine Datei an den Client zurückzugeben. Ich benutze HEAD und GET Methoden dagegen. Verschiedene Header werden zurückgegeben.

Für GET, erhalte ich diese Header: (Dies ist, was ich erwartet hatte.)

Transfer-Encoding: chunked, no Content-Length.

Für HEAD ich diese Header erhalten:

Content-Length: 1017118720, no Transfer-Encoding.

Nach this thread , HEAD und GETSOLLTE geben identische Header zurück, aber nicht notwendigerweise.

Meine Frage ist:

Wenn Transfer-Encoding: chunked verwendet wird weil die Datei dynamisch an den Client und Tomcat-Server zugeführt wird, kann nicht seine Größe vorher wissen, wie könnte Tomcat kennen die Content-Length wenn HEAD Methode ist benutzt? Ist Tomcat nur Dry-run der Handler und zählen alle Datei-Bytes? Warum gibt es nicht einfach den gleichen Transfer-Encoding: chunked Header zurück?

Unten ist mein RESTful API implementiert mit Spring Web MVC:

@RestController 
public class ChunkedTransferAPI { 

    @Autowired 
    ServletContext servletContext; 

    @RequestMapping(value = "bootfile.efi", method = { RequestMethod.GET, RequestMethod.HEAD }) 
    public void doHttpBoot(HttpServletResponse response) { 

     String filename = "/bootfile.efi"; 
     try { 
      ServletOutputStream output = response.getOutputStream(); 
      InputStream input = servletContext.getResourceAsStream(filename); 
      BufferedInputStream bufferedInput = new BufferedInputStream(input); 
      int datum = bufferedInput.read(); 
      while (datum != -1) { 
       output.write(datum); 
       datum = bufferedInput.read(); 
      } 
      output.flush(); 
      output.close(); 

     } catch (IOException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } 

    } 

} 

ADD 1

In meinem Code habe ausdrücklich füge ich keine Header, muss es Tomcat dann sein Fügen Sie die Header Content-Length und Transfer-Encoding hinzu, wie es für richtig hält.

Also, was sind die Regeln für Tomcat zu entscheiden, welche Header zu senden?

ADD 2

Vielleicht ist es damit zusammen, wie Tomcat funktioniert. Ich hoffe, hier kann jemand etwas Licht werfen. Ansonsten werde ich in die Quelle von Tomcat 8 debuggen und das Ergebnis teilen. Aber das kann eine Weile dauern.

Verwandte:

Antwort

2

Does Tomcat den Handler nur trocken laufen und alle Datei Bytes zählen?

Ja, die Standardimplementierung von javax.servlet.http.HttpServlet.doHead() tut das.

Sie an Hilfsklassen aussehen kann NoBodyResponse, NoBodyOutputStream in HttpServlet.java

Die DefaultServlet Klasse (die Tomcat Servlet, das statische Dateien zu dienen verwendet wird) ist weise. Es kann den richtigen Content-Length-Wert senden und GET-Anforderungen für eine Teilmenge der Datei (Range) liefern. Sie können Ihre Anfrage zu diesem Servlet, mit

ServletContext.getNamedDispatcher("default").forward(request, response); 
2

weiterleiten Obwohl es seltsam scheint, könnte es sinnvoll sein, die Größe nur in Reaktion auf eine HEAD Anfrage zu senden und gestückelt in Reaktion auf eine GET Anfrage, je nach Art von Daten, die vom Server zurückgegeben werden müssen.

Während Ihre API scheint eine statische Datei zu liefern, sprechen Sie auch über dynamisch erstellte Dateien oder Daten, so dass ich im Allgemeinen hier sprechen werde (auch für Webserver im Allgemeinen).

Zuerst schauen wir uns die verschiedenen Verwendungen einen Blick für GET und HEAD:

  • Mit GET der Client die gesamte Datei oder Daten (oder einen Bereich der Daten) anfordert, und will es so schnell wie möglich. Es gibt also keinen bestimmten Grund dafür, dass der Server zuerst die Größe der Daten sendet, insbesondere wenn er im Chunked-Modus schneller/früher senden könnte. Daher wird hier der schnellstmögliche Weg bevorzugt (der Client wird nach dem Download sowieso die Größe haben).

  • Mit HEAD auf der anderen Seite, der Client möchte in der Regel einige spezifische Informationen. Dies könnte nur eine Prüfung der Existenz oder "zuletzt geändert" sein, aber es könnte auch verwendet werden, wenn der Client einen bestimmten Teil der Daten wünscht (mit einer Bereichsanfrage, einschließlich einer Überprüfung, ob Bereichsanforderungen für diese Anfrage unterstützt werden)), oder muss nur aus irgendeinem Grund die Größe der Daten im Voraus wissen.

Aus Furcht, dass der Blick auf einige mögliche Szenarien:

Statische Datei:

HEAD: Es gibt keinen Grund, nicht die Größe in der Antwort-Header enthalten, da diese Informationen zur Verfügung steht.

GET: die meiste Zeit wird die Größe in der Kopfzeile enthalten sein und die Daten werden auf einmal gesendet, es sei denn, es gibt bestimmte Leistungsgründe, sie in Blöcken zu senden. Auf der anderen Seite scheint es, dass Sie eine Chunked-Übertragung für Ihre Datei erwarten, daher könnte dies hier sinnvoll sein.

Live-Logfile:

Ok, etwas seltsam, aber möglich: Herunterladen einer Datei, wo die Größe ändern könnte beim Herunterladen.

HEAD: Auch hier möchte der Client wahrscheinlich die Größe, und der Server kann einfach die Größe der Datei zu dieser bestimmten Zeit in der Kopfzeile bereitstellen.

GET: Da während des Herunterladens Loglines hinzugefügt werden konnten, ist die Größe im Voraus unbekannt. Die einzige Option ist chunked zu senden.

Tabelle mit fester Größe Aufzeichnungen:

sie sich vor, ein Server eine Tabelle zurück zu senden mit fester Länge Aufzeichnungen kommen aus verschiedenen Quellen/Datenbanken benötigt:

HEAD: Größe wird wahrscheinlich wollen von der Kunde. Der Server könnte schnell eine Abfrage für Count in jeder Datenbank durchführen und die berechnete Größe zurück an den Client senden.

GET: Anstatt eine Abfrage für Count in jeder Datenbank zuerst zu starten, beginnt der Server besser, die resultierenden Datensätze aus jeder Datenbank in Chunks zu senden.

Dynamisch generierte Zip-Dateien:

Vielleicht nicht üblich, aber ein interessantes Beispiel.

Stellen Sie sich vor, Sie möchten dem Benutzer basierend auf einigen Parametern dynamisch generierte Zip-Dateien bereitstellen.

Lassen Sie sich zunächst auf dem Aufbau einer Zip-Datei einen Blick:

Es gibt zwei Teile: zunächst ein Block für jede Datei gibt es: einen kleinen Header durch die komprimierten Daten für diese Datei gefolgt. Dann gibt es eine Liste aller Dateien in der Zip-Datei (einschließlich Größen/Positionen).

So die vorbereiteten Blöcke für jede Datei im Voraus erzeugt auf Festplatte (und die Namen/Größen in einiger Datenstruktur gespeichert werden könnten

HEAD. Der Kunde will wahrscheinlich die Größe hier wissen, kann der Server leicht. Berechnen Sie die Größe aller benötigten Blöcke + die Größe des zweiten Teils mit der Liste der darin enthaltenen Dateien

Wenn der Client eine einzelne Datei extrahieren möchte, könnte er direkt nach dem letzten Teil der Datei fragen (mit eine Bereichsanfrage), um die Liste zu haben, und dann mit einer zweiten Anfrage nach dieser einzelnen Datei fragen.Obwohl die Größe nicht notwendigerweise benötigt wird, um die letzten n Bytes zu erhalten, könnte es praktisch sein, wenn zum Beispiel y Sie möchten die verschiedenen Teile in einer Sparse-Datei mit der gleichen Größe der vollständigen Zip-Datei speichern.

GET: keine Notwendigkeit, die Berechnungen zuerst zu tun (einschließlich das Erzeugen des zweiten Teils, um seine Größe zu wissen). Es wäre besser und schneller, einfach jeden Block in Blöcken zu senden.

Voll dynamisch generierte Datei:

In diesem Fall wäre es nicht sehr effizient sein, um die Größe zu einem HEAD Anfrage natürlich zurück, da die gesamte Datei erzeugt werden müssten nur ihre Größe wissen .