Hoe werkt het uploaden van HTTP-bestanden?

Als ik een eenvoudig formulier als dit indien met een bijgevoegd bestand:

<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>

Hoe wordt het bestand intern verzonden? Wordt het bestand verzonden als onderdeel van de HTTP-body als gegevens? In de headers van dit verzoek zie ik niets met betrekking tot de naam van het bestand.

Ik zou graag de interne werking van HTTP willen weten bij het verzenden van een bestand.


Antwoord 1, autoriteit 100%

Laten we eens kijken wat er gebeurt als je een bestand selecteert en je formulier verzendt (ik heb de koppen afgekapt voor de beknoptheid):

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--

OPMERKING: elke grensreeks moet worden voorafgegaan door een extra --, net als aan het einde van de laatste grensreeks. Het bovenstaande voorbeeld bevat dit al, maar het kan gemakkelijk over het hoofd worden gezien. Zie commentaar van @Andreas hieronder.

In plaats van URL-codering van de formulierparameters, worden de formulierparameters (inclusief de bestandsgegevens) verzonden als secties in een meerdelig document in de hoofdtekst van het verzoek.

In het bovenstaande voorbeeld ziet u de invoer MAX_FILE_SIZEmet de waarde ingesteld in het formulier, evenals een sectie die de bestandsgegevens bevat. De bestandsnaam maakt deel uit van de Content-Dispositionheader.

De volledige details zijn hier.


Antwoord 2, autoriteit 89%

Hoe verzendt het het bestand intern?

Het formaat heet multipart/form-data, zoals gevraagd op: Wat betekent enctype=’multipart/form-data’?

Ik ga:

  • voeg nog wat HTML5-referenties toe
  • leg uit waaromhij gelijk heeft met een voorbeeld voor het indienen van een formulier

HTML5-referenties

Er zijn drie mogelijkhedenvoor enctype:

Hoe de voorbeelden te genereren

Zodra je een voorbeeld van elke methode ziet, wordt het duidelijk hoe ze werken en wanneer je ze allemaal moet gebruiken.

U kunt voorbeelden maken met:

Sla het formulier op in een minimaal .html-bestand:

<!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>

We hebben de standaard tekstwaarde ingesteld op a&#x03C9;b, wat aωbbetekent omdat ωU+03C9, dit zijn de bytes 61 CF 89 62in UTF-8.

Maak bestanden om te uploaden:

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

Ren onze kleine echo-server:

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

Open de HTML in uw browser, selecteer de bestanden en klik op Verzenden en controleer de terminal.

ncDrukt het ontvangen aanvraag af.

getest op: ubuntu 14.04.3, ncBSD 1.105, Firefox 40.

Multipart / Form-gegevens

Firefox verzonden:

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--

Voor het binaire bestands- en tekstveld worden de bytes 61 CF 89 62(aωbin UTF-8) letterlijk verzonden. Je kunt dat verifiëren met nc -l localhost 8000 | hd, die zegt dat de bytes:

61 CF 89 62

werden verzonden (61== ‘A’ en 62== ‘B’).

Daarom is het duidelijk dat:

  • Content-Type: multipart/form-data; boundary=---------------------------735323031399963166993862150Stelt het inhoudstype in op multipart/form-dataen zegt dat de velden worden gescheiden door de gegeven boundarystring.

    Merk op dat de:

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

    heeft twee minder dadhes --dan de werkelijke barrière

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

    Dit komt omdat de standaard de grens vereist om te beginnen met twee streepjes --. De andere streepjes lijken precies hoe Firefox ervoor koos om de willekeurige grens te implementeren. RFC 7578 vermeldt duidelijk dat die twee toonaangevende streepjes --vereist zijn:

    4.1. Parameter “Grens” van Multipart / Form-gegevens

    Zoals met andere multipart-typen, zijn de onderdelen gescheiden met een
    Grensscheidingsteken, geconstrueerd met behulp van CRLF, “-“, en de waarde van
    de parameter “Grens”.

  • Elk veld krijgt een aantal subkoppen vóór de gegevens: Content-Disposition: form-data;, het veld name, DE filename, gevolgd door de gegevens.

    De server leest de gegevens tot de volgende grensstring. De browser moet een grens kiezen die niet in een van de velden verschijnt, dus dit is de reden waarom de grens tussen aanvragen kan variëren.

    Omdat we de unieke grens hebben, is geen codering van de gegevens noodzakelijk: binaire gegevens worden verzonden zoals.

    TODO: wat is de optimale grensgrootte (log(N)ik wed), en naam/looptijd van het algoritme dat het vindt? Gevraagd op: https://cs.stackexchange.com/questions/39687/find-the-shortest-sequence-that-is-not-a-sub-sequence-of-a-set-of-sequences

  • Content-Typewordt automatisch bepaald door de browser.

    Hoe het precies wordt bepaald, is gevraagd op: Hoe wordt het mime-type van een geüpload bestand bepaald door de browser?

application/x-www-form-urlencoded

Verander nu het enctypein application/x-www-form-urlencoded, laad de browser opnieuw en verzend opnieuw.

Firefox verzonden:

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

Het is duidelijk dat de bestandsgegevens niet zijn verzonden, alleen de basisnamen. Dit kan dus niet worden gebruikt voor bestanden.

Wat het tekstveld betreft, zien we dat gebruikelijke afdrukbare tekens zoals aen bin één byte werden verzonden, terwijl niet-afdrukbare tekens zoals 0xCFen 0x89namen elk 3 bytesin beslag: %CF%89!

Vergelijking

Bestandsuploads bevatten vaak veel niet-afdrukbare tekens (bijv. afbeeldingen), terwijl tekstformulieren dat bijna nooit doen.

Van de voorbeelden die we hebben gezien dat:

  • multipart/form-data: voegt een paar bytes aan grensoverhead aan het bericht toe en moet wat tijd besteden aan het berekenen ervan, maar verzendt elke byte in één byte.

  • application/x-www-form-urlencoded: heeft een grens van één byte per veld (&), maar voegt een lineaireoverheadfactor van 3xvoor elk niet-afdrukbaar teken.

Dus zelfs als we bestanden zouden kunnen verzenden met application/x-www-form-urlencoded, zouden we dat niet willen, omdat het zo inefficiënt is.

Maar voor afdrukbare tekens die in tekstvelden worden gevonden, maakt het niet uit en genereert het minder overhead, dus gebruiken we het gewoon.


Antwoord 3, autoriteit 20%

Bestand verzenden als binaire inhoud (upload zonder formulier of FormData)

In de gegeven antwoorden/voorbeelden is het bestand (waarschijnlijk) geüpload met een HTML-formulier of met behulp van de FormData-API. Het bestand is slechts een deel van de gegevens die in het verzoek zijn verzonden, vandaar de kop multipart/form-dataContent-Type.

Als u het bestand als enige inhoud wilt verzenden, kunt u het direct toevoegen als de hoofdtekst van het verzoek en stelt u de Content-Type-header in op het MIME-type van het bestand dat u verzendt. De bestandsnaam kan worden toegevoegd in de Content-Dispositionheader. Je kunt als volgt uploaden:

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);

Als je geen formulieren wilt (willen) gebruiken en je bent alleen geïnteresseerd in het uploaden van één enkel bestand, dan is dit de gemakkelijkste manier om je bestand op te nemen in het verzoek.


Antwoord 4, Autoriteit 3%

Ik heb deze voorbeeld 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);
    }
}

en ik heb dit test.html-bestand:

<!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>

En ten slotte het bestand dat ik zal gebruiken voor testdoeleinden, genaamd A.DAT heeft de volgende inhoud:

0x39 0x69 0x65

Als u de bytes hierboven interpreteert als ASCII- of UTF-8-tekens, zullen ze daadwerkelijk vertegenwoordigen:

9ie

Dus laten we onze Java-code uitvoeren, open Test.html in onze favoriete browser, upload a.daten verzend het formulier en zie wat onze server ontvangt :

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--

Nou, het verbaast me niet om de tekens 9iete zien, want we hebben Java gezegd ze af te drukken en ze te behandelen als UTF-8-tekens. Je kunt er net zo goed voor kiezen om ze te lezen als onbewerkte bytes..

Cookie: JSESSIONID=27D0A0637A0449CF65B3CB20F40048AF 

is eigenlijk de laatste HTTP-header hier. Daarna komt de HTTP-body, waar de meta en de inhoud van het bestand dat we hebben geüpload daadwerkelijk te zien zijn.


Antwoord 5, autoriteit 2%

Een HTTP-bericht kan een aantal gegevens bevatten die na de kopregels worden verzonden. In een reactie wordt hier de gevraagde bron teruggestuurd naar de client (het meest voorkomende gebruik van de berichttekst), of misschien een verklarende tekst als er een fout is. In een verzoek worden hier door de gebruiker ingevoerde gegevens of geüploade bestanden naar de server gestuurd.

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

Other episodes