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

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

Choose a file to upload:

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

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

Voyons ce qui se passe lorsque vous sélectionnez un fichier et soumettez votre formulaire (j’ai tronqué les en-têtes pour des raisons de concision):

 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 d’URL codant les parameters du formulaire, les parameters du formulaire (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 de fichier fait partie de l’en Content-Disposition tête Content-Disposition .

Les détails complets sont ici

Comment envoie-t-il le fichier en interne?

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

Je vais:

  • append des références HTML5 supplémentaires
  • expliquer pourquoi il a raison avec un formulaire

Références HTML5

Il y a trois possibilités pour l’ enctype :

  • x-www-urlencoded
  • multipart/form-data (points de spécification pour RFC2388 )
  • text-plain . Ceci n’est “pas interprétable de manière fiable par ordinateur”, il ne devrait donc jamais être utilisé en production et nous ne l’examinerons pas plus avant.

Comment générer les exemples

Une fois que vous voyez un exemple de chaque méthode, il devient évident comment ils fonctionnent et quand vous devez les utiliser.

Vous pouvez produire des exemples en utilisant:

  • nc -l ou un serveur ECHO
  • un agent utilisateur tel qu’un navigateur ou cURL

Enregistrez le formulaire dans un fichier .html minimal:

 < !DOCTYPE html>    upload   

Nous définissons la valeur de texte par défaut sur aωb , qui signifie aωb car ω est U+03C9 , qui sont les octets 61 CF 89 62 dans UTF-8.

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

 echo 'Content of a.txt.' > a.txt echo '< !DOCTYPE html>Content of a.html.' > a.html # Binary file containing 4 bytes: 'a', 1, 2 and 'b'. printf 'a\xCF\x89b' > binary 

Exécutez notre petit serveur d’écho:

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

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

nc imprime la demande reçue.

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

multipart / données de formulaire

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>Content of a.html. -----------------------------735323031399963166993862150 Content-Disposition: form-data; name="file3"; filename="binary" Content-Type: application/octet-stream aωb -----------------------------735323031399963166993862150-- 

Pour le fichier binary et le champ texte, les octets 61 CF 89 62 ( aωb dans UTF-8) sont envoyés littéralement. Vous pouvez vérifier cela avec nc -l localhost 8000 | hd nc -l localhost 8000 | hd , qui dit que les octets:

 61 CF 89 62 

ont été envoyés ( 61 == ‘a’ et 62 == ‘b’).

Il est donc clair que:

  • Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266 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 le Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266 donné chaîne de boundary

  • chaque champ reçoit des sous-en-têtes avant ses données: Content-Disposition: form-data; , le name du champ, le filename du filename , suivi des données.

    Le serveur lit les données jusqu’à la prochaine chaîne de limites. Le navigateur doit choisir une limite qui n’apparaîtra dans aucun des champs, c’est pourquoi la limite peut varier entre les requêtes.

    Comme nous avons la limite unique, aucun codage des données n’est nécessaire: les données binarys sont envoyées telles quelles.

    TODO: quelle est la taille limite optimale ( log(N) je parie), et nom / durée d’exécution de l’algorithme qui la 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 est-il déterminé exactement a été demandé à: Comment le type mime d’un fichier téléchargé est déterminé par le navigateur?

application / x-www-form-urlencoded

Changez maintenant l’ enctype en application/x-www-form-urlencoded , rechargez le navigateur et relancez.

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 de fichier n’ont pas été envoyées, seulement 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 habituels tels a et b ont été envoyés en un octet, tandis que les caractères non imprimables tels que 0xCF et 0x89 ont pris 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 formulaires de texte ne le font presque jamais.

Parmi les exemples, nous avons vu que:

  • multipart/form-data : ajoute quelques octets de dépassement de limite au message et doit passer un certain temps à le calculer, mais envoie chaque octet dans un octet.

  • application/x-www-form-urlencoded : a une limite d’octet unique 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 le voudrions pas, car il est tellement inefficace.

Mais pour les caractères imprimables que l’on trouve dans les champs de texte, cela n’a pas d’importance et génère moins de frais généraux. Nous les utilisons simplement.

Envoyer un fichier en tant que contenu binary (téléchargement sans formulaire ou FormData)

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

Si vous souhaitez envoyer le fichier en tant que seul contenu, vous pouvez l’append directement en tant que corps de requête et définir l’en Content-Type tête Content-Type sur le type MIME du fichier que vous envoyez. Le nom du fichier peut être ajouté dans l’en Content-Disposition 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 de formulaires et que vous ne souhaitez envoyer qu’un seul fichier, c’est la manière la plus simple d’inclure votre fichier dans la requête.

J’ai cet exemple de code Java:

 import java.io.*; import java.net.*; import java.nio.charset.StandardCharsets; public class TestClass { public static void main(Ssortingng[] 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>    File Upload!   

et enfin le fichier que je vais utiliser à des fins de test, 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 réellement:

 9ie 

Alors, exécutons notre code Java, ouvrez test.html dans notre navigateur préféré, téléchargez a.dat et soumettez le formulaire pour voir 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 personnages 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. Après cela vient le corps HTTP, où les méta et le contenu du fichier que nous avons téléchargé peuvent être vus.

Un message HTTP peut avoir un corps de données envoyé après les lignes d’en-tête. Dans une réponse, c’est là que la ressource demandée est renvoyée au client (l’utilisation la plus courante du corps du message), ou peut-être un texte explicatif en cas d’erreur. Dans une requête, c’est là que 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