[JAVA] Die CSV-Datei, die ich herunterladen konnte, wurde plötzlich auf der Seite angezeigt.

In der Eilarbeit haben wir eine Webanwendung veröffentlicht, die Daten herunterlädt, die von mehreren Personen im CSV-Format eingegeben wurden. Ich habe das folgende OSS verwendet.

Am Tag der Inbetriebnahme gab es kein besonderes Problem, und als ich das Büro verließ und mit meinen Freunden trank, war mein Smartphone laut. .. .. Als ich einen eingehenden Anruf erhielt, erhielt ich die Nachricht "Kann nicht heruntergeladen werden". Als ich die Details überprüfte, sagte er: "Bis vor 3 Stunden wurde beim Herunterladen eine CSV-Datei ausgegeben, aber plötzlich wurde die CSV im Browser angezeigt."

Ich machte einen Fehler mit einem Freund, hörte auf zu trinken und begann die Ursache zu untersuchen. Vorläufig wusste ich, dass es funktionieren würde, wenn ich es so reparierte, also gab ich es zuerst mit Servicepriorität frei. Das Prinzip bleibt jedoch unklar, weshalb wir eine kontinuierliche Untersuchung durchgeführt haben.

Details der Korrektur

Unten finden Sie den vereinfachten Code für den Downloadvorgang, der dieses Problem verursacht hat, sowie den geänderten Code.

  @PostMapping
  fun export(response: HttpServletResponse) {
    /**
     *CSV-Antwort.Schreiben Sie in outputStream.
     */

    response.contentType = "text/csv"
    response.setHeader("Content-Disposition", "attachment; filename=export.csv")
    response.outputStream.flush()
  }
  @PostMapping
  fun export(response: HttpServletResponse) {
    response.contentType = "text/csv"
    response.setHeader("Content-Disposition", "attachment; filename=export.csv")

    /**
     *CSV-Antwort.Schreiben Sie in outputStream.
     */

    response.outputStream.flush()
  }

Der Punkt ist, ob Content-Type und Content-Disposition nach oder vor dem Schreiben in den outputStream liegen. Nachfolgende Untersuchungen ergaben, dass die Datei heruntergeladen werden würde, wenn Content-Disposition im Vordergrund stünde.

Der Grund, warum ich Content-Disposition nach dem Schreiben in outputStream festgelegt habe, ist, dass der Artikel, auf den ich mich bezog, zufällig so war, und ich denke, dass das Setzen des Headers und das Schreiben in den Body (Stream) unabhängige Prozesse sind. Deshalb.

Ursachenforschung

Selbst wenn Content-Disposition nach dem Schreiben in outputStream festgelegt wird, kann die Datei für eine Weile normal heruntergeladen werden, sodass davon ausgegangen werden kann, dass sich die Größe der CSV-Ausgabe auswirkt. Also habe ich die Datengröße auf dem Bildschirm eingegeben und ein Reproduktionsprogramm erstellt, das CSV dieser Größe ausgibt.

  @PostMapping
  fun export(@RequestParam size: Int, response: HttpServletResponse) {
    (1..size).forEach {
      response.outputStream.print("a")
    }
    response.contentType = "text/csv"
    response.setHeader("Content-Disposition", "attachment; filename=export.csv")
    response.outputStream.flush()
  }

Anschließend bestätigte der Antwortheader von den Chrome-Entwicklertools, dass die Größe 8 KByte beträgt

Connection: keep-alive
Content-Disposition: attachment; filename=export.csv
Content-Type: text/csv
Date: Sat, 08 Feb 2020 10:26:07 GMT
Keep-Alive: timeout=60
Transfer-Encoding: chunked

Es war 9 KByte

Connection: keep-alive
Date: Sat, 08 Feb 2020 10:27:48 GMT
Keep-Alive: timeout=60
Transfer-Encoding: chunked

Verschwindet Content-Disposition nicht so? Am Ende sind 8191 Bytes die ersteren und 8192 Bytes die letzteren. Ich denke, das liegt daran, dass 8192, was eine Potenz von 2 ist, etwas anderes überschreitet.

Erwägung

Wahrscheinlich passiert in Tomcat Folgendes.

Ursachenuntersuchung 2

Während ich das Paket schnell mit tcpdump überwachte, versuchte ich, mit dem Debugger Schritt für Schritt von 8190 Bytes auf 1 Byte zu schreiben, und es wurde bestätigt, dass der HTTP-Header und der Text beim Schreiben von 8192 Bytes an den Browser gesendet wurden. ..

Übrigens, als ich beim Schreiben von 8192 Bytes einsprang, fand ich den folgenden Code.

java:org.apache.catalina.connector.OutputBuffer


    public void append(byte src[], int off, int len) throws IOException {
        if (bb.remaining() == 0) {
            appendByteArray(src, off, len);
        } else {
            int n = transfer(src, off, len, bb);
            len = len - n;
            off = off + n;
            if (isFull(bb)) {
                flushByteBuffer();
                appendByteArray(src, off, len);
            }
        }
    }

bb ist eine Mitgliedsvariable vom Typ ByteBuffer, und die Übertragungsdaten werden in bb in der appendByteArray- und Übertragungsmethode gespeichert. Mit der isFull-Methode wird es wahr, wenn die in bb gespeicherte Größe die zulässige Menge erreicht, und sie wird mit der flushByteBuffer-Methode an den Browser gesendet.

Wenn Sie die flushByteBuffer-Methode weiter befolgen,

java:org.apache.coyote.http11.Http11OutputBuffer


    @Override
    public int doWrite(ByteBuffer chunk) throws IOException {

        if (!response.isCommitted()) {
            // Send the connector a request for commit. The connector should
            // then validate the headers, send them (using sendHeaders) and
            // set the filters accordingly.
            response.action(ActionCode.COMMIT, null);
        }

        if (lastActiveFilter == -1) {
            return outputStreamOutputBuffer.doWrite(chunk);
        } else {
            return activeFilters[lastActiveFilter].doWrite(chunk);
        }
    }

Wenn nicht festgeschrieben, senden Sie während des Festschreibungsprozesses einen HTTP-Header, um das festgeschriebene Flag zu setzen. Danach wird der HTTP-Body geschrieben.

Überlegung 2

Aus diesem Grund wurde der Code, den ich einfach erstellt habe, veröffentlicht, ohne es zu bemerken, da er sich wie beabsichtigt verhalten hat. Angesichts der Schwierigkeit auf der Tomcat-Seite, verschiedene Größen zu verarbeiten, ist dies jedoch eine natürliche Implementierung Es war eine Geschichte, die ich den ganzen Weg ging, um süchtig zu werden.


(2020/2/8) Gepostet. (2020/2/9) Die fehlerhafte Beschreibung der Größe, in der das Ereignis bei der Ursachenuntersuchung hin und her wechselt, wurde korrigiert. Ursachenuntersuchung 2 und Berücksichtigung hinzugefügt.

Recommended Posts

Die CSV-Datei, die ich herunterladen konnte, wurde plötzlich auf der Seite angezeigt.
Ich war süchtig danach, die Update-Anweisung in MyBatis zu wiederholen
Über die Sache, dass ich süchtig danach war, wie man Hashmap benutzt
Ich habe ein Tool erstellt, um den Unterschied zwischen CSV-Dateien auszugeben
Ich konnte die Docker + Laravel + MySQL App für Heroku bereitstellen!
Durch die Überprüfung der Funktionsweise von Java unter Linux konnte ich die Kompilierung und Hierarchie verstehen.
Eine Geschichte, der ich mit der automatischen Starteinstellung von Tomcat 8 unter CentOS 8 zweimal verfallen war
Ich möchte eine Datei mit Ruby im Internet herunterladen und lokal speichern (mit Vorsicht).
[Ruby] Ich habe versucht, die häufigen Methoden in Paiza zusammenzufassen
[Ruby] Ich habe versucht, die häufigen Methoden mit paiza ② zusammenzufassen
Ich war süchtig nach der Rollmethode
Ich war süchtig nach dem Spring-Batch-Test
Ich war süchtig nach RXTX mit Sierra
Doppelklicken Sie, um die JAR-Datei unter Windows zu öffnen
Eine Seite, die leicht zu verstehen war, als ich anfing, Spring Boot zu lernen
Eine Geschichte, nach der ich süchtig war, als ich einen Schlüssel bekam, der automatisch auf MyBatis ausprobiert wurde
Ich war süchtig nach NoSuchMethodError in Cloud-Endpunkten
[Ruby] Missverständnis, dass ich das Modul verwendet habe [Anfänger]
Löschen Sie die Migrationsdatei, die Sie nicht löschen konnten, vollständig
Ich möchte die Protokollausgabe unter Android vereinfachen
Ich musste herausfinden, wo sich der Ordner mit den Eclipse-Plugins auf meinem Mac befand. (Memo)
[Java] Ich habe versucht, ein Janken-Spiel zu erstellen, das Anfänger auf der Konsole ausführen können