725 votes

Comment fonctionne le téléchargement de fichiers par 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 est-il envoyé dans le corps du HTTP en tant que données ? Dans les en-têtes de cette requête, je ne vois rien concernant le nom du fichier.

J'aimerais simplement connaître le fonctionnement interne du HTTP lors de l'envoi d'un fichier.

0 votes

Je n'ai pas utilisé de renifleur depuis un certain temps mais si vous voulez voir ce qui est envoyé dans votre requête (puisqu'elle est adressée au serveur, c'est une requête), reniflez-la. Cette question est trop large. SO est plus pour des questions de programmation spécifiques.

0 votes

...en tant que renifleur, violoniste est mon arme de prédilection. Vous pouvez même créer vos propres demandes de test pour voir comment elles s'affichent.

0 votes

Pour les personnes intéressées, voir également " MAX_FILE_SIZE en PHP - quel intérêt ?" sur stackoverflow.com/q/1381364/632951

405voto

toddsundsted Points 2482

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

REMARQUE : chaque chaîne de délimitation doit être précédée d'un préfixe supplémentaire -- comme à la fin de la dernière chaîne de délimitation. L'exemple ci-dessus l'inclut déjà, mais il peut être facile de le manquer. Voir le commentaire de @Andreas ci-dessous.

Au lieu de coder les paramètres du formulaire par URL, les paramètres du formulaire (y compris les données du fichier) sont envoyés sous forme de sections dans un document multipartite 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 la section Content-Disposition en-tête.

Les détails complets sont aquí .

0 votes

Cela signifie-t-il que le port 80 (ou le port servant aux requêtes http) est inutilisable pendant la durée du transfert du fichier ? Par exemple, si un énorme fichier (environ 1 Go) est en cours de téléchargement, le serveur web ne pourra-t-il pas répondre à d'autres demandes pendant ce temps ?

8 votes

@source.rar : Non. Les serveurs web sont (presque ?) toujours threadés afin de pouvoir gérer les connexions simultanées. Essentiellement, le processus démon qui écoute sur le port 80 transmet immédiatement la tâche de servir à un autre thread/processus afin qu'il puisse retourner écouter une autre connexion ; même si deux connexions entrantes arrivent exactement au même moment, elles resteront dans la mémoire tampon du réseau jusqu'à ce que le démon soit prêt à les lire.

14 votes

L'explication du threading est un peu incorrecte, car il existe des serveurs hautes performances conçus en tant que single thread et qui utilisent une machine d'état pour télécharger rapidement et à tour de rôle des paquets de données à partir de connexions. En fait, dans TCP/IP, le port 80 est un port d'écoute, et non le port sur lequel les données sont transférées.

379voto

Ciro Santilli Points 3341

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 le faire :

  • ajouter quelques références HTML5 supplémentaires
  • expliquer por qué il a raison avec un exemple de soumission de formulaire

Références HTML5

Hay trois possibilités para enctype :

Comment générer les exemples

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

Vous pouvez produire des exemples en utilisant :

Sauvegarder le formulaire dans un minimum .html fichier :

<!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 définissons la valeur du texte par défaut comme suit a&#x03C9;b ce qui signifie aωb parce que ω est U+03C9 qui sont les octets 61 CF 89 62 en UTF-8.

Créez 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

Lancez notre petit serveur d'écho :

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

Ouvrez le HTML sur votre navigateur, sélectionnez les fichiers et 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 le vérifier avec 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=---------------------------735323031399963166993862150 définit le type de contenu comme multipart/form-data et indique que les champs sont séparés par les caractères suivants boundary chaîne.

    Mais notez que le :

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

    a deux dadhes de moins -- que la barrière réelle

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

    En effet, la norme exige que la frontière commence par deux tirets. -- . Les autres tirets semblent être simplement la façon dont Firefox a choisi d'implémenter la limite arbitraire. La RFC 7578 mentionne clairement que ces deux tirets de tête -- sont nécessaires :

4.1. Paramètre "Boundary" de multipart/form-data

Comme pour les autres types multipartites, les parties sont délimitées par un signe délimiteur de frontière, construit à l'aide de CRLF, de "--" et de la valeur du paramètre du paramètre "boundary".

application/x-www-form-urlencoded

Maintenant, changez le enctype a 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

Il est clair que les données du fichier n'ont pas été envoyées, seulement les noms de base. Cela ne peut donc pas être utilisé pour les fichiers.

En ce qui concerne le champ de texte, nous voyons que les caractères imprimables habituels comme a y b ont été envoyés dans un octet, tandis que les caractères non imprimables comme 0xCF y 0x89 a pris 3 octets chacun : %CF%89 !

Comparaison

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

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

  • multipart/form-data : ajoute quelques octets d'overhead de frontière au message, et doit passer un certain temps à le calculer, mais envoie chaque octet dans un seul octet.

  • application/x-www-form-urlencoded : a une limite d'un seul octet par champ ( & ), mais ajoute un linéaire facteur de surcharge 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, parce que c'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 frais généraux, donc nous l'utilisons simplement.

83voto

Wilt Points 867

Envoyer le fichier comme contenu binaire (téléchargement sans formulaire ou FormData)

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

Si vous voulez envoyer le fichier comme seul contenu, vous pouvez l'ajouter directement dans le corps de la requête et vous définissez l'attribut Content-Type au type MIME du fichier que vous envoyez. Le nom du fichier peut être ajouté dans l'en-tête Content-Disposition en-tête. 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 n'utilisez pas (ou ne voulez pas utiliser) de 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.

17voto

Koray Tugay Points 1885

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

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>

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 seront en fait représentatifs :

9ie

Alors, exécutons notre code Java, ouvrons-le. test.html dans notre navigateur préféré, télécharger a.dat et soumettre le formulaire et 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 parce que nous avons demandé à Java de les imprimer en les traitant comme des caractères UTF-8. Vous pouvez tout aussi bien choisir de les lire comme des 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.

7voto

flagg19 Points 641

Un message HTTP peut comporter 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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X