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.
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.
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.
Wahrscheinlich passiert in Tomcat Folgendes.
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.
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