Wie funktioniert das Hochladen von HTTP-Dateien?

528

Wenn ich ein einfaches Formular wie dieses mit einer angehängten Datei abschicke:

<form enctype="multipart/form-data" action="http://localhost:3000/upload?upload_progress_id=12344" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="100000" />
Choose a file to upload: <input name="uploadedfile" type="file" /><br />
<input type="submit" value="Upload File" />
</form>

Wie wird die Datei intern gesendet? Wird die Datei als Teil des HTTP-Körpers als Daten gesendet? In den Kopfzeilen dieser Anfrage wird nichts im Zusammenhang mit dem Namen der Datei angezeigt.

Ich möchte nur die internen Funktionen des HTTP beim Senden einer Datei kennen.

0xSina
quelle
Ich habe seit einiger Zeit keinen Sniffer mehr verwendet, aber wenn Sie sehen möchten, was in Ihrer Anfrage gesendet wird (da es sich um eine Anfrage an den Server handelt), schnüffeln Sie daran. Diese Frage ist zu weit gefasst. SO ist eher für spezifische Programmierfragen.
Paparazzo
... während Schnüffler gehen, ist Geiger meine Waffe der Wahl. Sie können sogar Ihre eigenen Testanforderungen erstellen, um zu sehen, wie sie veröffentlicht werden.
Phil Cooper
Für Interessierte siehe auch " MAX_FILE_SIZEin PHP - was ist der Sinn" auf stackoverflow.com/q/1381364/632951
Pacerier
Ich finde MAX_FILE_SIZE komisch. Ich kann mein HTML in Chrome auf 100000000 ändern, bevor ich es veröffentliche, damit es einen besseren Wert bietet. Entweder 1. haben Sie es in einem Cookie mit einem sicheren Hash über Salt, damit Cookie, wenn es geändert wird, der Server eine Ausnahme validieren und auslösen kann (wie es Webpieces oder Playframework beide tun) oder eine Art Formularvalidierung, bei der sich nichts geändert hat. @ 0xSina
Dean Hiller

Antworten:

320

Lassen Sie uns einen Blick darauf werfen, was passiert, wenn Sie eine Datei auswählen und Ihr Formular senden (der Kürze halber habe ich die Überschriften abgeschnitten):

POST /upload?upload_progress_id=12344 HTTP/1.1
Host: localhost:3000
Content-Length: 1325
Origin: http://localhost:3000
... other headers ...
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryePkpFF7tjBAqx29L

------WebKitFormBoundaryePkpFF7tjBAqx29L
Content-Disposition: form-data; name="MAX_FILE_SIZE"

100000
------WebKitFormBoundaryePkpFF7tjBAqx29L
Content-Disposition: form-data; name="uploadedfile"; filename="hello.o"
Content-Type: application/x-object

... contents of file goes here ...
------WebKitFormBoundaryePkpFF7tjBAqx29L--

HINWEIS: Jeder Begrenzungszeichenfolge muss ein zusätzliches Präfix vorangestellt werden --, genau wie am Ende der letzten Begrenzungszeichenfolge. Das obige Beispiel enthält dies bereits, kann jedoch leicht übersehen werden. Siehe Kommentar von @Andreas unten.

Anstelle einer URL, die die Formularparameter codiert, werden die Formularparameter (einschließlich der Dateidaten) als Abschnitte in einem mehrteiligen Dokument im Hauptteil der Anforderung gesendet.

Im obigen Beispiel sehen Sie die Eingabe MAX_FILE_SIZEmit dem im Formular festgelegten Wert sowie einen Abschnitt mit den Dateidaten. Der Dateiname ist Teil des Content-DispositionHeaders.

Die vollständigen Details finden Sie hier .

toddsundsted
quelle
7
@ source.rar: Nein. Webserver haben (fast?) immer einen Thread, damit sie gleichzeitige Verbindungen verarbeiten können. Im Wesentlichen übergibt der Daemon-Prozess, der Port 80 überwacht, sofort die Aufgabe, einem anderen Thread / Prozess zu dienen, damit er wieder auf eine andere Verbindung warten kann. Selbst wenn zwei eingehende Verbindungen genau zum gleichen Zeitpunkt eintreffen, bleiben sie nur im Netzwerkpuffer, bis der Dämon bereit ist, sie zu lesen.
Eggyal
10
Die Erklärung zum Threading ist etwas falsch, da es Hochleistungsserver gibt, die als Single-Threaded-Server konzipiert sind und eine Zustandsmaschine verwenden, um schnell abwechselnd Datenpakete von Verbindungen herunterzuladen. In TCP / IP ist Port 80 vielmehr ein Überwachungsport, nicht der Port, über den die Daten übertragen werden.
Slebetman
9
Wenn ein IP-Listening-Socket (Port 80) eine Verbindung empfängt, wird ein anderer Socket an einem anderen Port erstellt, normalerweise mit einer Zufallszahl über 1000. Dieser Socket wird dann mit dem Remote-Socket verbunden, sodass Port 80 frei ist, auf neue Verbindungen zu warten.
Slebetman
11
@slebetman Zunächst geht es um HTTP. Der aktive FTP-Modus gilt hier nicht. Zweitens wird die Abhörbuchse nicht bei jeder Verbindung blockiert. Sie können so viele Verbindungen zu einem Port haben, wie die anderen Seiten Ports haben, an die sie ihr eigenes Ende binden können.
Slotos
33
Beachten Sie, dass die Begrenzungszeichenfolge, die als Teil des Headerfelds "Inhaltstyp" übergeben wird, 2 Zeichen kürzer ist als die Begrenzungszeichenfolgen für die einzelnen Teile unten. Ich habe gerade eine Stunde lang versucht herauszufinden, warum mein Uploader nicht funktioniert, da es ziemlich schwer zu bemerken ist, dass die erste Grenzzeichenfolge tatsächlich nur 4 Striche enthält, die anderen Grenzzeichenfolgen jedoch 6 Striche. Mit anderen Worten: Wenn die Begrenzungszeichenfolge zum Trennen der einzelnen Formulardaten verwendet wird, müssen zwei Bindestriche vorangestellt werden: - Es wird natürlich in RFC1867 beschrieben, aber ich denke, es sollte auch hier darauf hingewiesen werden
Andreas
279

Wie wird die Datei intern gesendet?

Das Format wird multipart/form-datawie folgt aufgerufen : Was bedeutet enctype = 'multipart / form-data'?

Ich werde:

  • Fügen Sie weitere HTML5-Referenzen hinzu
  • Erklären Sie, warum er Recht hat, mit einem Beispiel zum Senden eines Formulars

HTML5-Referenzen

Es gibt drei Möglichkeiten für enctype:

So generieren Sie die Beispiele

Sobald Sie ein Beispiel für jede Methode sehen, wird klar, wie sie funktionieren und wann Sie jede verwenden sollten.

Sie können Beispiele erstellen mit:

Speichern Sie das Formular in einer minimalen .htmlDatei:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8"/>
  <title>upload</title>
</head>
<body>
  <form action="http://localhost:8000" method="post" enctype="multipart/form-data">
  <p><input type="text" name="text1" value="text default">
  <p><input type="text" name="text2" value="a&#x03C9;b">
  <p><input type="file" name="file1">
  <p><input type="file" name="file2">
  <p><input type="file" name="file3">
  <p><button type="submit">Submit</button>
</form>
</body>
</html>

Wir setzen den Standardtextwert auf a&#x03C9;b, was bedeutet , aωbda ωist U+03C9, der die Bytes 61 CF 89 62in UTF-8.

Erstellen Sie Dateien zum Hochladen:

echo 'Content of a.txt.' > a.txt

echo '<!DOCTYPE html><title>Content of a.html.</title>' > a.html

# Binary file containing 4 bytes: 'a', 1, 2 and 'b'.
printf 'a\xCF\x89b' > binary

Führen Sie unseren kleinen Echoserver aus:

while true; do printf '' | nc -l 8000 localhost; done

Öffnen Sie den HTML-Code in Ihrem Browser, wählen Sie die Dateien aus, klicken Sie auf Senden und überprüfen Sie das Terminal.

nc druckt die empfangene Anfrage.

Getestet am: Ubuntu 14.04.3, ncBSD 1.105, Firefox 40.

mehrteilige / Formulardaten

Firefox gesendet:

POST / HTTP/1.1
[[ Less interesting headers ... ]]
Content-Type: multipart/form-data; boundary=---------------------------735323031399963166993862150
Content-Length: 834

-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="text1"

text default
-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="text2"

aωb
-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="file1"; filename="a.txt"
Content-Type: text/plain

Content of a.txt.

-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="file2"; filename="a.html"
Content-Type: text/html

<!DOCTYPE html><title>Content of a.html.</title>

-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="file3"; filename="binary"
Content-Type: application/octet-stream

aωb
-----------------------------735323031399963166993862150--

Für die Binärdatei und das Textfeld werden die Bytes 61 CF 89 62( aωbin UTF-8) buchstäblich gesendet. Sie könnten das mit überprüfen nc -l localhost 8000 | hd, was besagt, dass die Bytes:

61 CF 89 62

wurden gesendet ( 61== 'a' und 62== 'b').

Daher ist es klar, dass:

  • Content-Type: multipart/form-data; boundary=---------------------------735323031399963166993862150Setzt den Inhaltstyp auf multipart/form-dataund gibt an, dass die Felder durch die angegebene boundaryZeichenfolge getrennt sind.

    Beachten Sie jedoch, dass die:

    boundary=---------------------------735323031399963166993862150
    

    hat zwei weniger Dadhes --als die eigentliche Barriere

    -----------------------------735323031399963166993862150
    

    Dies liegt daran, dass der Standard erfordert, dass die Grenze mit zwei Strichen beginnt --. Die anderen Striche scheinen genau so zu sein, wie Firefox die willkürliche Grenze implementiert hat. In RFC 7578 wird klar erwähnt, dass diese beiden führenden Striche --erforderlich sind:

    4.1. "Grenze" Parameter von mehrteiligen / Formulardaten

    Wie bei anderen mehrteiligen Typen werden die Teile durch einen Begrenzungsbegrenzer begrenzt, der unter Verwendung von CRLF, "-" und dem Wert des Parameters "Grenze" erstellt wird.

  • Jedes Feld erhält einige Unterüberschriften vor seinen Daten : Content-Disposition: form-data;, das Feld name, das filename, gefolgt von den Daten.

    Der Server liest die Daten bis zur nächsten Grenzzeichenfolge. Der Browser muss eine Grenze auswählen, die in keinem der Felder angezeigt wird. Aus diesem Grund kann die Grenze zwischen den Anforderungen variieren.

    Da wir die eindeutige Grenze haben, ist keine Codierung der Daten erforderlich: Binärdaten werden unverändert gesendet.

    TODO: Was ist die optimale Grenzgröße ( log(N)ich wette) und Name / Laufzeit des Algorithmus, der sie findet? Gefragt unter: /cs/39687/find-the-shortest-sequence-that-is-not-a-sub-sequence-of-a-set-of-sequences

  • Content-Type wird automatisch vom Browser ermittelt.

    Wie es genau bestimmt wird, wurde gefragt unter: Wie wird der MIME-Typ einer hochgeladenen Datei vom Browser bestimmt?

application / x-www-form-urlencoded

Ändern Sie nun das enctypein application/x-www-form-urlencoded, laden Sie den Browser neu und senden Sie es erneut.

Firefox gesendet:

POST / HTTP/1.1
[[ Less interesting headers ... ]]
Content-Type: application/x-www-form-urlencoded
Content-Length: 51

text1=text+default&text2=a%CF%89b&file1=a.txt&file2=a.html&file3=binary

Offensichtlich wurden nicht die Dateidaten gesendet, sondern nur die Basisnamen. Dies kann also nicht für Dateien verwendet werden.

Wie für das Textfeld ein , so sehen wir , dass die üblichen druckbare Zeichen wie aund bwurden in einem Byte, während nicht-druckbaren wie geschickt 0xCFund 0x89nahmen 3 Bytes je: %CF%89!

Vergleich

Das Hochladen von Dateien enthält häufig viele nicht druckbare Zeichen (z. B. Bilder), während Textformulare dies fast nie tun.

Aus den Beispielen haben wir gesehen, dass:

  • multipart/form-data: Fügt der Nachricht ein paar Bytes Boundary Overhead hinzu und muss einige Zeit mit der Berechnung verbringen, sendet jedoch jedes Byte in einem Byte.

  • application/x-www-form-urlencoded: hat eine einzelne Bytegrenze pro Feld ( &), fügt jedoch für jedes nicht druckbare Zeichen einen linearen Overhead-Faktor von 3x hinzu .

Selbst wenn wir Dateien mit senden könnten, application/x-www-form-urlencodedwürden wir dies nicht wollen, weil es so ineffizient ist.

Für druckbare Zeichen in Textfeldern spielt dies jedoch keine Rolle und erzeugt weniger Overhead. Daher verwenden wir es einfach.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
1
Wie würden Sie einen binären Anhang hinzufügen? (dh ein kleines Bild) - Ich kann sehen, wie sich die Werte für Content-Dispositionund Content-TypeAttribute ändern , aber wie gehe ich mit dem 'Inhalt' um?
Blurfus
3
@ianbeks Der Browser führt dies automatisch aus, bevor die Anfrage gesendet wird. Ich weiß nicht, welche Heuristiken verwendet werden, aber höchstwahrscheinlich gehört die Dateierweiterung dazu. Dies kann die Frage beantworten: stackoverflow.com/questions/1201945/…
Ciro Santilli 法轮功 冠状 病 六四 事件 17
3
@CiroSantilli think this 法轮功 纳米比亚 威 视 Ich denke, diese Antwort ist viel besser als die gewählte. Aber bitte entfernen Sie den irrelevanten Inhalt aus Ihrem Profil. Es ist gegen den Geist von SO.
smwikipedia
2
@smwikipedia danke für das rfc-Zitat und dafür, dass dir diese Antwort gefällt! Über den Benutzernamen: Für mich ist der Geist von SO, dass jeder jederzeit die besten Informationen haben sollte. ~~ Lassen Sie uns diese Diskussion auf Twitter oder Meta halten. Frieden.
Ciro Santilli 6 冠状 :0. 事件 6
1
@ KumarHarsh nicht genug Details, um zu antworten, denke ich. Bitte öffnen Sie eine neue super detaillierte Frage.
Ciro Santilli 法轮功 冠状 病 六四 事件 19
62

Datei als binären Inhalt senden (ohne Formular oder FormData hochladen)

In den angegebenen Antworten / Beispielen wird die Datei (höchstwahrscheinlich) mit einem HTML-Formular oder mithilfe der FormData-API hochgeladen . Die Datei ist nur ein Teil der in der Anfrage gesendeten Daten, daher der multipart/form-data Content-TypeHeader.

Wenn Sie die Datei als einzigen Inhalt senden möchten, können Sie sie direkt als Anforderungshauptteil hinzufügen und den Content-TypeHeader auf den MIME-Typ der Datei setzen, die Sie senden. Der Dateiname kann im Content-DispositionHeader hinzugefügt werden . Sie können wie folgt hochladen:

var xmlHttpRequest = new XMLHttpRequest();

var file = ...file handle...
var fileName = ...file name...
var target = ...target...
var mimeType = ...mime type...

xmlHttpRequest.open('POST', target, true);
xmlHttpRequest.setRequestHeader('Content-Type', mimeType);
xmlHttpRequest.setRequestHeader('Content-Disposition', 'attachment; filename="' + fileName + '"');
xmlHttpRequest.send(file);

Wenn Sie keine Formulare verwenden (möchten) und nur eine einzige Datei hochladen möchten, ist dies der einfachste Weg, Ihre Datei in die Anfrage aufzunehmen.

Verwelken
quelle
Wie konfigurieren Sie hierfür mit Asp.Net 4.0 einen serverseitigen Dienst? Wird es auch mehrere Eingabeparameter wie Benutzer-ID, Pfad, Untertitel usw. verarbeiten?
Asle G
1
@AsleG Nein, es dient nur zum Senden einer einzelnen Datei als Inhalt Ihrer Anfrage. Ich bin kein Asp.Net-Experte, aber Sie sollten einfach den Inhalt (einen Blob) aus der Anfrage ziehen und ihn mit dem Content-Typeaus dem Header in einer Datei speichern .
Wilt
@AsleG Vielleicht kann dieser Link helfen
Wilt
@wilt Wenn ich kein Formular verwende, aber die Formdaten-API verwenden möchte, kann ich das so machen?
wütend Kiwi
1
@AnkitKhettry Klingt so, als würde es mit einem Formular oder mithilfe der Formular-API hochgeladen. Diese "seltsamen Zeichenfolgen", auf die Sie sich beziehen, sind die Formulargrenzen, die normalerweise zum Trennen der Formulardaten in Teile auf dem Server verwendet werden.
Wilt
9

Ich habe dieses Beispiel Java-Code:

import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;

public class TestClass {
    public static void main(String[] args) throws IOException {
        ServerSocket socket = new ServerSocket(8081);
        Socket accept = socket.accept();
        InputStream inputStream = accept.getInputStream();

        InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
        char readChar;
        while ((readChar = (char) inputStreamReader.read()) != -1) {
            System.out.print(readChar);
        }

        inputStream.close();
        accept.close();
        System.exit(1);
    }
}

und ich habe diese test.html Datei:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>File Upload!</title>
</head>
<body>
<form method="post" action="http://localhost:8081" enctype="multipart/form-data">
    <input type="file" name="file" id="file">
    <input type="submit">
</form>
</body>
</html>

und schließlich hat die Datei, die ich zu Testzwecken verwenden werde, mit dem Namen a.dat den folgenden Inhalt:

0x39 0x69 0x65

Wenn Sie die obigen Bytes als ASCII- oder UTF-8-Zeichen interpretieren, stellen sie tatsächlich Folgendes dar:

9ie

Lassen Sie uns also unseren Java-Code ausführen , test.html in unserem bevorzugten Browser öffnen a.dat, das Formular hochladen und senden und sehen, was unser Server empfängt:

POST / HTTP/1.1
Host: localhost:8081
Connection: keep-alive
Content-Length: 196
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: null
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.97 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary06f6g54NVbSieT6y
DNT: 1
Accept-Encoding: gzip, deflate
Accept-Language: en,en-US;q=0.8,tr;q=0.6
Cookie: JSESSIONID=27D0A0637A0449CF65B3CB20F40048AF

------WebKitFormBoundary06f6g54NVbSieT6y
Content-Disposition: form-data; name="file"; filename="a.dat"
Content-Type: application/octet-stream

9ie
------WebKitFormBoundary06f6g54NVbSieT6y--

Nun, ich bin nicht überrascht, die Zeichen 9ie zu sehen, weil wir Java angewiesen haben, sie zu drucken und sie als UTF-8-Zeichen zu behandeln. Sie können sie auch als Rohbytes lesen.

Cookie: JSESSIONID=27D0A0637A0449CF65B3CB20F40048AF 

ist eigentlich der letzte HTTP-Header hier. Danach folgt der HTTP-Body, in dem Meta und Inhalt der von uns hochgeladenen Datei tatsächlich angezeigt werden.

Koray Tugay
quelle
6

Bei einer HTTP-Nachricht werden möglicherweise Daten nach den Kopfzeilen gesendet. In einer Antwort wird hier die angeforderte Ressource an den Client zurückgegeben (die häufigste Verwendung des Nachrichtentexts) oder möglicherweise ein erläuternder Text, wenn ein Fehler auftritt. In einer Anfrage werden hier vom Benutzer eingegebene Daten oder hochgeladene Dateien an den Server gesendet.

http://www.tutorialspoint.com/http/http_messages.htm

flagg19
quelle