web-dev-qa-db-fra.com

Comment livrer de gros fichiers dans ASP.NET Response?

Je ne cherche pas d'alternative de streaming de contenu de fichier à partir de la base de données, en effet je cherche la racine du problème, cela exécutait le fichier jusqu'à IIS 6 où nous avons exécuté notre application en mode classique, maintenant nous avons mis à niveau notre IIS à 7 et nous exécutons le pool d'applications en mode pipeline et ce problème a commencé.

J'ai un gestionnaire, où je dois livrer de gros fichiers à la demande du client. Et je suis confronté aux problèmes suivants,

Les fichiers ont une taille moyenne de 4 à 100 Mo, alors considérons le cas de téléchargement de fichiers de 80 Mo.

Mise en mémoire tampon activée, démarrage lent

Response.BufferOutput = True;

Cela entraîne un démarrage très lent du fichier, car les téléchargements des utilisateurs et même la barre de progression n'apparaissent pas avant quelques secondes, généralement de 3 à 20 secondes, la raison en est: IIS lit le fichier entier en premier, détermine la la longueur du contenu, puis commencez le transfert de fichier. Le fichier est en cours de lecture dans le lecteur vidéo, et il fonctionne très très lentement, mais l'iPad ne télécharge que la fraction du fichier en premier afin qu'il fonctionne rapidement.

Mise en mémoire tampon désactivée, aucune longueur de contenu, démarrage rapide, aucune progression

Reponse.BufferOutput = False;

Cela se traduit par un démarrage immédiat, mais le client final (navigateur typique comme Chrome) ne connaît pas Content-Length comme IIS ne sait pas non plus, donc il n'affiche pas la progression, à la place, il indique X Ko téléchargé .

Buffering Off, Manual Content-Length, Fast Start, Progress and Protocol Violation

Response.BufferOutput = False;
Response.AddHeader("Content-Length", file.Length);

Cela se traduit par un téléchargement de fichier immédiat correct dans Chrome etc, mais dans certains cas IIS entraîne une erreur "Connexion client fermée à distance") (cela est très fréquent) et d'autres résultats WebClient entraînent une violation de protocole. Cela se produit 5 à 10% de toutes les demandes, pas toutes les demandes.

Je suppose que ce qui se passe est, IIS n'envoie rien appelé 100 continuer lorsque nous ne faisons pas de mise en mémoire tampon et le client peut se déconnecter sans attendre de sortie. Cependant, la lecture de fichiers à partir de la source peut prendre plus de temps, mais côté client, j'ai augmenté le délai d'expiration, mais il semble que IIS délai d'expiration et n'a aucun contrôle.

Est-il possible de forcer Response à envoyer 100 continuer et de ne laisser personne fermer la connexion?

MISE À JOUR

J'ai trouvé les en-têtes suivants dans Firefox/Chrome, rien ne semble inhabituel ici pour une violation de protocole ou un mauvais en-tête.

Access-Control-Allow-Headers:*
Access-Control-Allow-Methods:POST, GET, OPTIONS
Access-Control-Allow-Origin:*
Access-Control-Max-Age:1728000
Cache-Control:private
Content-Disposition:attachment; filename="24.jpg"
Content-Length:22355
Content-Type:image/pjpeg
Date:Wed, 07 Mar 2012 13:40:26 GMT
Server:Microsoft-IIS/7.5
X-AspNet-Version:4.0.30319
X-Powered-By:ASP.NET

MISE À JOUR 2

Turning Recycling n'offrait toujours pas grand-chose mais j'ai augmenté mon MaxWorkerProcess à 8 et j'ai maintenant moins d'erreurs qu'avant.

Mais en moyenne, sur 200 requêtes en une seconde, 2 à 10 requêtes échouent .., et cela se produit presque toutes les deux secondes.

MISE À JOUR

Poursuivant 5% des demandes échouant avec "Le serveur a commis une violation de protocole. Section = ResponseStatusLine", j'ai un autre programme qui télécharge le contenu du serveur Web qui utilise WebClient, et qui donne cette erreur 4-5 fois par seconde, en moyenne I 5% des demandes ont échoué. Est-il possible de retracer l'échec de WebClient?

Problèmes redéfinis

Fichier zéro octet reç

IIS ferme la connexion pour une raison quelconque, côté client dans WebConfig, je reçois 0 octet pour le fichier qui n'est pas zéro octet, nous faisons un contrôle de hachage SHA1, cela nous a dit que dans IIS serveur Web, aucune erreur n'est enregistrée.

C'était mon erreur, et elle a été résolue car nous utilisons Entity Framework, il lisait sale (lignes non validées) car la lecture n'était pas dans la portée de la transaction, le mettre dans la portée de la transaction a résolu ce problème.

Exception de violation de protocole déclenchée

WebClient lève WebException en disant "Le serveur a commis une violation de protocole. Section = ResponseStatusLine.

Je sais que je peux activer l'analyse des en-têtes dangereux, mais ce n'est pas le cas, lorsque c'est mon gestionnaire HTTP qui envoie des en-têtes appropriés, je ne sais pas pourquoi IIS envoie quelque chose de plus (vérifié sur Firefox et Chrome , rien d'inhabituel), cela ne se produit que 2% des fois.

MISE À JOUR 4

Erreur sc-win32 64 trouvée et j'ai lu quelque part que WebLimits pour MinBytesPerSecond doit être changé de 240 à 0, tout est toujours le même. Cependant, j'ai remarqué que chaque fois que IIS enregistre une erreur 64 sc-win32, IIS enregistre l'état HTTP à 200 mais il y avait une erreur. Maintenant, je ne peux pas activer Échec Trace Logging for 200 car cela entraînera des fichiers massifs.

Les deux problèmes ci-dessus ont été résolus en augmentant MinBytesPerSecond et en désactivant les sessions, j'ai ajouté une réponse détaillée résumant chaque point.

41
Akash Kava

Bien que la manière correcte de livrer les gros fichiers dans IIS soit l'option suivante,

  1. Définissez MinBytesPerSecond sur zéro dans WebLimits (cela aidera certainement à améliorer les performances, car IIS choisit de fermer les clients détenant des connexions KeepAlive avec des transferts de plus petite taille)
  2. Allouer plus de processus de travail au pool d'applications, j'ai défini 8, maintenant cela ne devrait être fait que si votre serveur distribue des fichiers plus volumineux. Cela ralentira certainement les autres sites, mais cela garantira de meilleures livraisons. Nous avons défini la valeur 8 car ce serveur ne possède qu'un seul site Web et ne fournit que d'énormes fichiers.
  3. Désactiver le recyclage du pool d'applications
  4. Désactiver les sessions
  5. Laisser la mise en mémoire tampon activée
  6. Avant chacune des étapes suivantes, vérifiez si Response.IsClientConnected est vrai, sinon abandonnez et n'envoyez rien.
  7. Définissez la longueur du contenu avant d'envoyer le fichier
  8. Vider la réponse
  9. Écrire dans le flux de sortie et vider à intervalles réguliers
12
Akash Kava

Lorsque vous avez défini la longueur du contenu avec le bufferOutput sur false, la raison possible de l'échec est due au fait que IIS essayez de compresser le fichier que vous envoyez et en définissant Content-Length IIS ne peut pas le remplacer par celui compressé et les erreurs commencent (*).

Alors keep the BufferOutput to false, and second disable the gzip from iis for the files you send - ou désactivez le gzip iis pour tous les fichiers et vous gérez la partie gzip par programmation, en gardant hors gzip les fichiers que vous envoyez.

Quelques questions similaires pour la même raison: le site ASP.NET se bloque parfois et/ou affiche du texte étrange en haut de la page lors du chargement, sur des serveurs à charge équilibrée

Compression HTTP: Certains scripts externes/CSS ne décompressent pas correctement de temps en temps

(*) pourquoi ne pas le changer à nouveau? car à partir du moment où vous définissez un en-tête, vous ne pouvez pas participer, sauf si vous avez activé cette option sur IIS et supposez que l'en-tête n'est pas tout prêt à envoyer au navigateur.

Suivre

S'il n'est pas compressé, la prochaine chose qui m'est venue à l'esprit est que le fichier est envoyé et pour une raison quelconque, la connexion a été retardée, a obtenu un délai d'expiration et s'est fermée. Vous obtenez donc le "Hôte distant fermé la connexion".

Cela peut être résolu en fonction de la cause:

  1. Le client a vraiment fermé la connexion
  2. Le délai d'expiration est à partir de la page elle-même, si vous utilisez un gestionnaire (encore une fois, le message doit probablement être "Page Timed Out").
  3. Le délai d'attente provient de l'attente inactive, la page prend plus que le temps d'exécution, obtient un délai d'attente et ferme la connexion. Peut-être que dans ce cas, le message était la page expirée.
  4. Le pool effectue un recyclage au moment où vous envoyez le fichier. Désactivez tous les recyclages de piscine! Ce sont les cas les plus possibles auxquels je peux penser en ce moment.

S'il provient d'IIS, accédez aux propriétés du site Web et assurez-vous de définir le plus grand "Délai d'expiration de connexion" et "Activer HTTP Keep-Alives".

Délai d'expiration de la page en modifiant le fichier web.config (vous ne pouvez le modifier par programme que pour une page spécifique)

<httpRuntime executionTimeout="43200"

Jetez également un œil à: http://weblogs.asp.net/aghausman/archive/2009/02/20/prevent-request-timeout-in-asp-net.aspx

Verrouillage de session

Une autre chose que vous devez examiner est de ne pas utiliser de session sur le gestionnaire que vous utilisez pour envoyer le fichier, car la session verrouille l'action jusqu'à la fin et si un utilisateur prend plus de temps pour télécharger un fichier, un second peut obtenir temps libre.

un parent:

appeler la page aspx pour renvoyer une image au hasard lent

remplacement complet de la session ASP.Net

la fonction Response.WriteFile échoue et donne un délai d'expiration de la passerelle 504

11
Aristos

Ce que je ferais, c'est utiliser la méthode ASP.NET Response.TransmitFile pas si connue, car elle est très rapide (et utilise éventuellement IIS cache du noyau) et prend soin de tous les trucs d'en-tête. Il est basé sur l'API non géré TransmitFile de Windows.

Mais pour pouvoir utiliser cette API, vous avez besoin d'un fichier physique à transférer. Voici donc un pseudo code c # qui explique comment le faire avec un chemin de fichier physique myCacheFilePath fictif. Il prend également en charge les possibilités de mise en cache client. Bien sûr, si vous avez déjà un fichier à portée de main, vous n'avez pas besoin de créer ce cache:

    if (!File.Exists(myCacheFilePath))
    {
        LoadMyCache(...); // saves the file to disk. don't do this if your source is already a physical file (not stored in a db for example).
    }

    // we suppose user-agent (browser) cache is enabled
    // check appropriate If-Modified-Since header
    DateTime ifModifiedSince = DateTime.MaxValue;
    string ifm = context.Request.Headers["If-Modified-Since"];
    if (!string.IsNullOrEmpty(ifm))
    {
        try
        {
            ifModifiedSince = DateTime.Parse(ifm, DateTimeFormatInfo.InvariantInfo);
        }
        catch
        {
            // do nothing
        }

        // file has not changed, just send this information but truncate milliseconds
        if (ifModifiedSince == TruncateMilliseconds(File.GetLastWriteTime(myCacheFilePath)))
        {
            ResponseWriteNotModified(...); // HTTP 304
            return;
        }
    }

    Response.ContentType = contentType; // set your file content type here
    Response.AddHeader("Last-Modified", File.GetLastWriteTimeUtc(myCacheFilePath).ToString("r", DateTimeFormatInfo.InvariantInfo)); // tell the client to cache that file

    // this API uses windows lower levels directly and is not memory/cpu intensive on Windows platform to send one file. It also caches files in the kernel.
    Response.TransmitFile(myCacheFilePath)
5
Simon Mourier

Ce morceau de code fonctionne pour moi. Il démarre immédiatement le flux de données vers le client. Il montre la progression pendant le téléchargement. Il ne viole pas HTTP. L'en-tête Content-Length est spécifié et le codage de transfert tronqué n'est pas utilisé.

protected void PrepareResponseStream(string clientFileName, HttpContext context, long sourceStreamLength)
{
    context.Response.ClearHeaders();
    context.Response.Clear();

    context.Response.ContentType = "application/pdf";
    context.Response.AddHeader("Content-Disposition", string.Format("filename=\"{0}\"", clientFileName));

    //set cachebility to private to allow IE to download it via HTTPS. Otherwise it might refuse it
    //see reason for HttpCacheability.Private at http://support.Microsoft.com/kb/812935
    context.Response.Cache.SetCacheability(HttpCacheability.Private);
    context.Response.Buffer = false;
    context.Response.BufferOutput = false;
    context.Response.AddHeader("Content-Length", sourceStreamLength.ToString    (System.Globalization.CultureInfo.InvariantCulture));
}

protected void WriteDataToOutputStream(Stream sourceStream, long sourceStreamLength, string clientFileName, HttpContext context)
{
    PrepareResponseStream(clientFileName, context, sourceStreamLength);
    const int BlockSize = 4 * 1024 * 1024;
    byte[] buffer = new byte[BlockSize];
    int bytesRead;
    Stream outStream = m_Context.Response.OutputStream;
    while ((bytesRead = sourceStream.Read(buffer, 0, BlockSize)) > 0)
    {
        outStream.Write(buffer, 0, bytesRead);
    }
    outStream.Flush();
}
2
HANiS