Question FtpWebRequest 30 minutes d'expiration


Mon code rencontre une exception de délai d’expiration après exactement 30 minutes lors du téléchargement d’un fichier volumineux via FTP. Le serveur est FileZilla en cours d'exécution sur Windows. Nous avons un certificat SSL configuré avec des options Enable FTP over SSL/TLS support (FTPS) et Allow explicit FTP over TLS activée. J'ai accès au serveur et au FileZilla configuration mais ne peut rien voir qui pourrait causer ce comportement. Vous trouverez ci-dessous le code source qui s'exécute sur .NET 4.6.2 sur une machine serveur Windows 2012. Il peut télécharger des fichiers à partir du serveur FTP mais expirera avec l'exception (répertoriée ci-dessous) après exactement 30 minutes si le téléchargement du fichier prend plus de 30 minutes.

Comme test j'ai utilisé Client FileZilla, à partir du même ordinateur client, pour télécharger simultanément plusieurs fichiers volumineux à partir du même point de terminaison du serveur, de sorte que le téléchargement de chaque fichier a duré plus de 30 minutes. Aucune erreur ne s'est produite dans ce scénario.

J'ai cherché sur StackOverflow aussi bien que Google mais rien de prometteur ne s'est présenté. Si quelqu'un a des conseils sur l'endroit où regarder (côté serveur ou côté client), je vous en serais très reconnaissant.


Code d'application

public class FtpFileDownloader
{
    // log4net
    private static readonly ILog Logger = LogManager.GetLogger(typeof(FtpFileDownloader));

    public void DownloadFile()
    {
        // setting the SecurityProtocol did not change the outcome, both were tried. Originally it was not set at all.
        // ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;


        ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;

        const int timeout = 7200000;
        const string file = "some-existing-file";
        try
        {
            var request = (FtpWebRequest) WebRequest.Create("uri-path-to-file");
            request.KeepAlive = false;
            request.Timeout = -1;
            request.ReadWriteTimeout = timeout;

            request.Credentials = new NetworkCredential("userName", "password");
            request.UsePassive = true;
            request.EnableSsl = true;
            request.Method = WebRequestMethods.Ftp.DownloadFile;

            Logger.Debug($"Downloading '{file}'");
            using (var response = (FtpWebResponse) request.GetResponse())
            using (var sourceStream = response.GetResponseStream())
            using (var targetStream = new FileStream("some-target-on-disk", FileMode.Create, FileAccess.Write))
            {
                try
                {
                    sourceStream.CopyTo(targetStream);
                    targetStream.Flush();
                    Logger.Debug($"Finished download '{file}'");
                }
                catch (Exception exInner)
                {
                    Logger.Error($"Error occurred trying to download file '{file}'.", exInner);
                }
            }
        }
        catch (Exception ex)
        {
            Logger.Error($"Error occurred trying to dispose streams when downloading file '{file}'.", ex);
        }
    }
}

Journal d'application

ERROR FtpFileDownloader - Error occurred trying to download file 'some-existing-file'.
System.IO.IOException: Received an unexpected EOF or 0 bytes from the transport stream.
   at System.Net.FixedSizeReader.ReadPacket(Byte[] buffer, Int32 offset, Int32 count)
   at System.Net.Security._SslStream.StartFrameBody(Int32 readBytes, Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security._SslStream.StartFrameHeader(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security._SslStream.StartReading(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security._SslStream.ProcessRead(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.TlsStream.Read(Byte[] buffer, Int32 offset, Int32 size)
   at System.Net.FtpDataStream.Read(Byte[] buffer, Int32 offset, Int32 size)
   at System.IO.Stream.InternalCopyTo(Stream destination, Int32 bufferSize)
   at System.IO.Stream.CopyTo(Stream destination)
   at FtpFileDownloader.DownloadFile

ERROR FtpFileDownloader - Error occurred trying to dispose streams when downloading file 'some-existing-file'.
System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a receive.
   at System.Net.FtpWebRequest.SyncRequestCallback(Object obj)
   at System.Net.FtpWebRequest.RequestCallback(Object obj)
   at System.Net.CommandStream.Dispose(Boolean disposing)
   at System.IO.Stream.Close()
   at System.IO.Stream.Dispose()
   at System.Net.ConnectionPool.Destroy(PooledStream pooledStream)
   at System.Net.ConnectionPool.PutConnection(PooledStream pooledStream, Object owningObject, Int32 creationTimeout, Boolean canReuse)
   at System.Net.FtpWebRequest.FinishRequestStage(RequestStage stage)
   at System.Net.FtpWebRequest.SyncRequestCallback(Object obj)
   at System.Net.FtpWebRequest.RequestCallback(Object obj)
   at System.Net.CommandStream.Abort(Exception e)
   at System.Net.CommandStream.CheckContinuePipeline()
   at System.Net.FtpWebRequest.DataStreamClosed(CloseExState closeState)
   at System.Net.FtpDataStream.System.Net.ICloseEx.CloseEx(CloseExState closeState)
   at System.Net.FtpDataStream.Dispose(Boolean disposing)
   at System.IO.Stream.Close()
   at System.IO.Stream.Dispose()
   at FtpFileDownloader.DownloadFile

FileZilla - Paramètres généraux

 Listen on these ports: 21
  Max. number of users: 0 (infinite)
     Number of threads: 2
    Connection timeout: 120 (seconds)
   No Transfer timeout: 9000 (seconds)
           Log timeout: 60 (seconds)

Journal du serveur FileZilla

23-2-2018 11:40:40 - (not logged in) (194.123.75.2)> Connected on port 21, sending welcome message...
23-2-2018 11:40:40 - (not logged in) (194.123.75.2)> 220 Welcome
23-2-2018 11:40:40 - (not logged in) (194.123.75.2)> AUTH TLS
23-2-2018 11:40:40 - (not logged in) (194.123.75.2)> 234 Using authentication type TLS
23-2-2018 11:40:40 - (not logged in) (194.123.75.2)> TLS connection established
23-2-2018 11:40:40 - (not logged in) (194.123.75.2)> USER  my-user-account
23-2-2018 11:40:40 - (not logged in) (194.123.75.2)> 331 Password required for my-user-account
23-2-2018 11:40:40 - (not logged in) (194.123.75.2)> PASS **************
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> 230 Logged on
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> PBSZ 0
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> 200 PBSZ=0
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> PROT P
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> 200 Protection level set to P
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> OPTS utf8 on
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> 202 UTF8 mode is always enabled. No need to send this command.
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> PWD
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> 257 "/" is current directory.
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> TYPE I
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> 200 Type set to I
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> PASV
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> 227 Entering Passive Mode (IP-ADDRESS,245,222)
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> RETR path-to-file
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> 150 Opening data channel for file download from server of "/path-to-file"
23-2-2018 11:40:40 - my-user-account (194.123.75.2)> TLS connection for data connection established
23-2-2018 12:10:41 - my-user-account (194.123.75.2)> disconnected.

Notez qu'il y a 30 minutes entre la déconnexion (dernière ligne) et la ligne avant. Si le transfert avait été effectué avec succès, il y aurait aussi une ligne lisant 226 Successfully transferred "/path-to-file" avant le disconnected ligne


Mise à jour en cours:

  • (2018.02.20) J'ai demandé à notre équipe de réseau de vérifier les règles relatives aux murs coupe-feu, mais elles n'ont rien trouvé d'intéressant.
  • (2018.02.22) J'ai constaté que la version de FileZilla Server utilisée est 0.9.43 beta (date de sortie 2014-01-02 selon le journal des modifications). Bien que je n'ai rien trouvé dans le journal des modifications qui suggère que ce comportement a déjà été un bogue corrigé, je vais passer à la dernière version 0.9.60.2 (date de sortie 2017-02-08) et relancez le test. Rapportera dans les 24 heures.
  • (2018.02.23) FileZilla a été mis à jour avec la dernière version. Cela n'a pas résolu le problème, j'ai mis à jour le journal du serveur, mais il semble presque identique au journal précédent, à l'exception que ce dernier transfert a eu lieu sur TLS au lieu de SSL.
  • (2018.02.23) J'ai trouvé le lien suivant Délais sur les gros fichiers sur la page de support de FileZilla. Je vais le renvoyer au personnel du réseau de notre fournisseur d’hébergement pour qu’il jette un autre coup d’œil.
  • (2018.02.27) Il s'est avéré que le pare-feu de l'entreprise (réseau où le client exécute) et non le pare-feu d'hébergement (réseau où FileZilla est hébergé) était le coupable. Il a été configuré pour supprimer les connexions inactives après 1800 secondes Une règle a été ajoutée pour remplacer cela entre ces 2 points de fin.

Coupable / réponse

Il s'est avéré que le pare-feu de l'entreprise (réseau où le client exécute) et non le pare-feu d'hébergement (réseau où FileZilla est hébergé) était le coupable. Il a été configuré pour supprimer les connexions inactives après 1800 secondes Une règle a été ajoutée pour remplacer cela entre ces 2 points de fin.


20
2018-02-15 19:24


origine


Réponses:


Vous devriez probablement essayer une autre implémentation du client de protocole FTP qui n’est pas construit FtpWebRequest.

Les problèmes connexes existent depuis longtemps, ils n'ont pas de solution ou de réponse claire. Donc je voudrais essayer quelque chose comme Fluentftp, il utilise directement l’API Winsock. XML Documentation Le commentaire indique que DownloadFile() devrait bien gérer les téléchargements de fichiers volumineux:

/// <summary>
/// Downloads the specified file onto the local file system.
/// High-level API that takes care of various edge cases internally.
/// Supports very large files since it downloads data in chunks.
/// </summary>

Pour plus d'informations, consultez:


7
2018-02-26 07:34



Ouais, je ne pense pas qu'il y ait une "erreur" dans votre code; c'est juste que la connexion de contrôle s'éteint au bout de 30 minutes même si la connexion de transfert n'est pas terminée. Peut-être ne sera-t-il même pas nécessaire de modifier vos valeurs KeepAlive et Timeout, essayez simplement de réutiliser votre requête toutes les 20 minutes environ avec un téléchargement factice: vous réinitialiserez ainsi le compteur de connexion de contrôle.

Btw, quelque part, j'ai lu que 30 minutes est un délai d'attente standard pour le serveur FileZilla, qui repose sur 6 éléments configurés pour être envoyés toutes les 300 secondes (ce qui donne 30 minutes d'expérience). Si vous pouviez essayer avec un autre serveur FTP / FTPS, vous trouveriez probablement un délai d'attente d'inactivité différent et ne vous heurteriez pas à cette limite de 30 minutes (mais différente).

Donc, personnellement, j'investirais pour faire le code ci-dessous async, donc le flux d'exécution continue après l'enceinte using et vous pouvez entrer une boucle où toutes les 20 minutes vous réutilisez votre demande (et sa connexion de contrôle) pour effectuer un téléchargement fictif. Bien entendu, FileZilla Client n'a pas besoin d'un téléchargement factice car il fonctionne à un niveau inférieur et envoie probablement des commandes TCP pour maintenir la connexion de contrôle active.

using (var response = (FtpWebResponse) request.GetResponse())
        using (var sourceStream = response.GetResponseStream())
        using (var targetStream = new FileStream("some-target-on-disk", FileMode.Create, FileAccess.Write))
        {
            try
            {
                sourceStream.CopyTo(targetStream);
                targetStream.Flush();
                Logger.Debug($"Finished download '{file}'");
            }
            catch (Exception exInner)
            {
                Logger.Error($"Error occurred trying to download file '{file}'.", exInner);
            }
        }

0
2018-02-22 11:04