web-dev-qa-db-fra.com

Comment fonctionne le téléchargement de fichier HTTP?

Lorsque je soumets un formulaire simple comme celui-ci avec un fichier joint:

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

Comment envoie-t-il le fichier en interne? Le fichier envoyé dans le corps du HTTP est-il sous forme de données? Dans les en-têtes de cette demande, je ne vois rien qui soit lié au nom du fichier.

Je voudrais juste savoir le fonctionnement interne du HTTP lors de l'envoi d'un fichier.

460
0xSina

Jetons un coup d'œil à ce qui se passe lorsque vous sélectionnez un fichier et envoyez votre formulaire (j'ai tronqué les en-têtes par souci de brièveté):

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

Au lieu de coder par URL les paramètres de formulaire, ceux-ci (y compris les données du fichier) sont envoyés sous forme de sections dans un document en plusieurs parties dans le corps de la demande.

Dans l'exemple ci-dessus, vous pouvez voir l'entrée MAX_FILE_SIZE avec la valeur définie dans le formulaire, ainsi qu'une section contenant les données du fichier. Le nom du fichier fait partie de l'en-tête Content-Disposition.

Les détails complets sont ici .

271
toddsundsted

Comment envoie-t-il le fichier en interne?

Le format est appelé multipart/form-data , comme demandé à: Que signifie enctype = 'multipart/form-data' signifie?

Je vais:

  • ajouter quelques références HTML5 supplémentaires
  • expliquer pourquoi il a raison avec un exemple d'envoi de formulaire

Références HTML5

Il y a trois possibilités pour enctype:

Comment générer les exemples

Une fois que vous voyez un exemple de chaque méthode, son fonctionnement et le moment d'utilisation de chacune d'entre elles deviennent évidents.

Vous pouvez produire des exemples en utilisant:

Enregistrez le formulaire dans un fichier minimal _.html_:

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

Nous avons défini la valeur de texte par défaut sur _a&#x03C9;b_, ce qui signifie _aωb_ car _ω_ est _U+03C9_, qui sont les octets _61 CF 89 62_ en UTF-8.

Créer des fichiers à télécharger:

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

Lancer notre petit serveur echo:

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

Ouvrez le code HTML de votre navigateur, sélectionnez les fichiers, cliquez sur Soumettre et vérifiez le terminal.

nc imprime la demande reçue.

Testé sur: Ubuntu 14.04.3, nc BSD 1.105, Firefox 40.

multipart/form-data

Firefox envoyé:

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

Pour le fichier binaire et le champ de texte, les octets _61 CF 89 62_ (_aωb_ en UTF-8) sont envoyés littéralement. Vous pouvez vérifier cela avec _nc -l localhost 8000 | hd_, qui dit que les octets:

_61 CF 89 62
_

ont été envoyés (_61_ == 'a' et _62_ == 'b').

Par conséquent, il est clair que:

  • _Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266_ définit le type de contenu sur _multipart/form-data_ et indique que les champs sont séparés par la chaîne boundary donnée.

  • chaque champ contient des en-têtes avant ses données: _Content-Disposition: form-data;_, le champ name, le filename, suivi des données.

    Le serveur lit les données jusqu'à la prochaine chaîne de limite. Le navigateur doit choisir une limite qui n'apparaîtra dans aucun des champs. C'est pourquoi la limite peut varier d'une requête à l'autre.

    Parce que nous avons la limite unique, aucun codage des données n'est nécessaire: les données binaires sont envoyées telles quelles.

    TODO: quelle est la taille de la frontière optimale (log(N) je parie), et le nom/la durée d'exécution de l'algorithme qui le trouve? Demandé à: https://cs.stackexchange.com/questions/39687/find-the-shortest-sequence-that-is-not-a-sub-sequence-of-a-set-of-sequences

  • _Content-Type_ est automatiquement déterminé par le navigateur.

    Comment cela a-t-il été déterminé exactement à l'adresse suivante: Comment le type mime d'un fichier téléchargé est-il déterminé par le navigateur?

application/x-www-form-urlencoded

Modifiez maintenant la variable enctype en _application/x-www-form-urlencoded_, rechargez le navigateur et soumettez à nouveau.

Firefox envoyé:

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

Clairement, les données du fichier n’ont pas été envoyées, mais uniquement les noms de base. Donc, cela ne peut pas être utilisé pour les fichiers.

En ce qui concerne le champ de texte, nous voyons que les caractères imprimables usuels tels que a et b ont été envoyés dans un octet, tandis que ceux non imprimables tels que _0xCF_ et _0x89_ ont été repris 3 octets chacun: _%CF%89_!

Comparaison

Les téléchargements de fichiers contiennent souvent beaucoup de caractères non imprimables (par exemple, des images), alors que les formes de texte ne le font presque jamais.

D'après les exemples, nous avons vu que:

  • _multipart/form-data_: ajoute quelques octets d'en-tête de limite au message et doit passer du temps à le calculer, mais envoie chaque octet sur un octet.

  • _application/x-www-form-urlencoded_: a une limite d'un octet par champ (_&_), mais ajoute un facteur de surcharge linéaire de 3x pour chaque caractère non imprimable.

Par conséquent, même si nous pouvions envoyer des fichiers avec _application/x-www-form-urlencoded_, nous ne voudrions pas, car cela est tellement inefficace.

Mais pour les caractères imprimables trouvés dans les champs de texte, cela n'a pas d'importance et génère moins de temps système, donc nous l'utilisons simplement.

Envoyer le fichier en tant que contenu binaire (upload sans formulaire ni FormData)

Dans les réponses/exemples donnés, le fichier est (très probablement) chargé avec un formulaire HTML ou en utilisant le FormData API . Le fichier n'est qu'une partie des données envoyées dans la requête, d'où l'en-tête multipart/form-dataContent-Type.

Si vous souhaitez envoyer le fichier en tant que contenu unique, vous pouvez directement l'ajouter en tant que corps de la demande et définir l'en-tête Content-Type sur le type MIME du fichier que vous envoyez. Le nom du fichier peut être ajouté dans l'en-tête Content-Disposition. Vous pouvez télécharger comme ceci:

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

Si vous (ne voulez pas) utiliser des formulaires et que vous ne souhaitez télécharger qu'un seul fichier, c'est le moyen le plus simple d'inclure votre fichier dans la demande.

50
Wilt

J'ai cet exemple Java Code:

import Java.io.*;
import Java.net.*;
import Java.nio.charset.StandardCharsets;
public class TestClass {
    public static void main(String[] args) throws IOException {
        final ServerSocket socket = new ServerSocket(8081);
        final Socket accept = socket.accept();
        final InputStream inputStream = accept.getInputStream();
        final 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);
    }
}

et j'ai ce fichier test.html:

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

enfin, le fichier que je vais utiliser à des fins de test et nommé a.dat a le contenu suivant:

0x39 0x69 0x65

si vous interprétez les octets ci-dessus comme des caractères ASCII ou UTF-8, ils représenteront en réalité:

9ie

Lançons donc notre code Java, ouvrons test.html dans notre navigateur préféré, chargez a.dat et envoyez le formulaire, puis voyez ce que notre serveur reçoit:

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

Eh bien, je ne suis pas surpris de voir les caractères 9ie car nous avons demandé à Java de les imprimer en les traitant comme des caractères UTF-8. Vous pouvez aussi choisir de les lire sous forme d'octets bruts.

Cookie: JSESSIONID=27D0A0637A0449CF65B3CB20F40048AF 

est en fait le dernier en-tête HTTP ici. Ensuite vient le corps HTTP, où la méta et le contenu du fichier que nous avons téléchargé peuvent être vus.

8
Koray Tugay

Un message HTTP peut avoir un corps de données envoyé après les lignes d'en-tête. Dans une réponse, la ressource demandée est renvoyée au client (utilisation la plus courante du corps du message), voire un texte explicatif en cas d'erreur. Dans une demande, il s’agit du lieu où les données saisies par l’utilisateur ou les fichiers téléchargés sont envoyés au serveur.

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

6
flagg19