Apache Commons FTPClient Hanging

nous utilisons le code FTP Net Apache Commons suivant pour se connecter à un serveur FTP, rechercher des répertoires de fichiers, et si des fichiers sont trouvés, les récupérer sur la machine locale:

try {
logger.trace("Attempting to connect to server...");

// Connect to server
FTPClient ftpClient = new FTPClient();
ftpClient.setConnectTimeout(20000);
ftpClient.connect("my-server-host-name");
ftpClient.login("myUser", "myPswd");
ftpClient.changeWorkingDirectory("/loadables/");

// Check for failed connection
if(!FTPReply.isPositiveCompletion(ftpClient.getReplyCode()))
{
    ftpClient.disconnect();
    throw new FTPConnectionClosedException("Unable to connect to FTP server.");
}

// Log success msg
logger.trace("...connection was successful.");

// Change to the loadables/ directory where we poll for files
ftpClient.changeWorkingDirectory("/loadables/");    

// Indicate we're about to poll
logger.trace("About to check loadables/ for files...");

// Poll for files.
FTPFile[] filesList = oFTP.listFiles();
for(FTPFile tmpFile : filesList)
{
    if(tmpFile.isDirectory())
        continue;

    FileOutputStream fileOut = new FileOutputStream(new File("tmp"));
    ftpClient.retrieveFile(tmpFile.getName(), fileOut);
    // ... Doing a bunch of things with output stream
    // to copy the contents of the file down to the local
    // machine. Ommitted for brevity but I assure you this
    // works (except when the WAR decides to hang).
    //
    // This was used because FTPClient doesn't appear to GET
    // whole copies of the files, only FTPFiles which seem like
    // file metadata...
}

// Indicate file fetch completed.
logger.trace("File fetch completed.");

// Disconnect and finish.
if(ftpClient.isConnected())
    ftpClient.disconnect();

logger.trace("Poll completed.");
} catch(Throwable t) {
    logger.trace("Error: " + t.getMessage());
}

nous avons prévu cette opération à chaque minute, à chaque minute. Lorsqu'il est déployé sur Tomcat (7.0.19), ce code se charge parfaitement et commence à fonctionner sans accrochage. Chaque fois, à un moment ou à un autre, il semble juste hang. Par ce que je moyenne:

  • Pas de tas de décharges existent
  • Tomcat est toujours en cours d'exécution (je peux voir son pid et me connecter à l'application web manager)
  • à l'Intérieur de l'application gestionnaire, je peux voir ma GUERRE est toujours en cours d'exécution/commencé
  • catalina.out et mon journal de bord spécifique à l'application ne montre aucun signe d'exceptions lancées
<!-Le JVM est donc toujours en cours d'exécution. Tomcat court toujours, et ma guerre en déploiement court toujours, mais elle est juste pendante. Parfois, il s'exécute pendant 2 heures et puis il pend; d'autres fois il court pendant des jours et puis il pend. Mais quand il ne raccrochez, il le fait entre la ligne suivante About to check loadables/ for files... (ce que je vois dans les logs) et la ligne qui lit File fetch completed. (que je ne vois pas).

cela me dit que la suspension se produit pendant le sondage/la recherche réelle des dossiers, qui genre de points moi dans la même direction que cette question que j'ai pu trouver ce qui se préoccupe de L'impasse FTPClient. Cela m'a je me demande s'il s'agit des mêmes questions (si c'est le cas, je supprimerai volontiers cette question!). Cependant, je ne pense pas que croire ils sont les mêmes (je ne vois pas les mêmes exceptions dans mes logs).

un collègue a mentionné qu'il pourrait s'agir d'une FTP "Passive" par opposition à une FTP "Active". Ne connaissant pas vraiment la différence, je suis un peu confus par les champs FTPClient ACTIVE_REMOTE_DATA_CONNECTION_MODE,PASSIVE_REMOTE_DATA_CONNECTION_MODE, etc. et je ne savais pas ce que je pensais être un potentiel question.

Depuis que je suis attraper Throwableen dernier recours, ici, je me serais attendu à voir quelque chose dans les journaux, si quelque chose va mal. Ergo, j'ai l'impression que c'est un réel problème de blocage.

des idées? Malheureusement, je n'en sais pas assez sur les internes FTP ici pour faire un diagnostic ferme. Ça pourrait être quelque chose côté serveur? En rapport avec le serveur FTP?

16
demandé sur Community 2012-03-14 21:27:29

4 réponses

ftpClient.enterLocalPassiveMode(); pour voir si ça aide.

je dirais aussi mettre la déconnexion dans le finally bloc de sorte qu'il ne quitte jamais d'une connexion.

26
répondu tjg184 2013-04-09 06:24:20

Hier, je n'ai pas dormi mais je pense que j'ai résolu le problème.

Vous pouvez augmenter la taille du tampon avec FTPClient.setBufferSize ();

   /**
 * Download encrypted and configuration files.
 * 
 * @throws SocketException
 * @throws IOException
 */
public void downloadDataFiles(String destDir) throws SocketException,
        IOException {

    String filename;
    this.ftpClient.connect(ftpServer);
    this.ftpClient.login(ftpUser, ftpPass);

    /* CHECK NEXT 4 Methods (included the commented) 
    *  they were very useful for me!
    *  and icreases the buffer apparently solve the problem!!
    */
    //  ftpClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out), true));
    log.debug("Buffer Size:" + ftpClient.getBufferSize());
    this.ftpClient.setBufferSize(1024 * 1024);
    log.debug("Buffer Size:" + ftpClient.getBufferSize());


    /*  
     *  get Files to download
     */
    this.ftpClient.enterLocalPassiveMode();
    this.ftpClient.setAutodetectUTF8(true);
            //this.ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
    this.ftpClient.enterLocalPassiveMode();
    FTPFile[] ftpFiles = ftpClient
            .listFiles(DefaultValuesGenerator.LINPAC_ENC_DIRPATH);

    /*
     * Download files
     */
    for (FTPFile ftpFile : ftpFiles) {

        // Check if FTPFile is a regular file           
        if (ftpFile.getType() == FTPFile.FILE_TYPE) {
            try{

            filename = ftpFile.getName();

            // Download file from FTP server and save
            fos = new FileOutputStream(destDir + filename);

            //I don't know what useful are these methods in this step
            // I just put it for try
            this.ftpClient.enterLocalPassiveMode();
            this.ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
            this.ftpClient.setAutodetectUTF8(true);
            this.ftpClient.enterLocalPassiveMode();

            ftpClient.retrieveFile(
                    DefaultValuesGenerator.LINPAC_ENC_DIRPATH + filename,
                    fos
                    );

            }finally{
                fos.flush();
                fos.close();                }
        }
    }
    if (fos != null) {
        fos.close();
    }
}

j'espère que ce code pourrait être utile pour quelqu'un!

20
répondu molavec 2013-05-08 17:25:41

j'ai eu à inclure ce qui suit après la connexion dans l'ordre de demande.listFiles et le transfert sans qu'il "tenir" et finit par défaut:

s.login(username, password);
s.execPBSZ(0);
s.execPROT("P");
2
répondu John Blomberg 2015-08-21 19:30:31

j'ai eu ce même problème quand j'ai essayé d'exécuter un listfiles d'une machine Linux vers un serveur IIS. Le code fonctionnait très bien depuis mon poste de travail de développeur, mais il s'accrochait lors de l'exécution sur le serveur spécifiquement en raison d'un pare-feu gommant le mix.

doit faire ces choses dans l'ordre et vous demandera d'étendre FTPSClient 3.5

  1. connect (implicit = true, SSLContext = TLS)
  2. vérifier isPositiveCompletion
  3. s'authentifier (de cours)
  4. execPBSZ (0)
  5. execPROT ("P")
  6. définir boolean pour indiquer Skip Passive IP (custom FTPSClient class)
  7. définir l'adresse IP de la connexion de sauvegarde (custom FTPSClient class)
  8. setUseEPSVwithIPv4 (false)
  9. enterLocalPassiveMode () or enterRemotePassiveMode ()
  10. initiateListParsing() ou toute commande list un.) À ce stade, le openDataConnection sera exécuté, assurez-vous d'enregistrer le port utilisé ici b) La commande PASV est exécutée C.) Le _parsePassiveModeReply est exécuté, ici vous ouvrez la socket avec l'adresse IP que vous avez utilisée pour vous connecter et le port sauvegardé.
  11. se déconnecter (toujours)

Plus d'INFO: Mon problème est spécifique à un pare-feu entre la machine Linux et le serveur IIS.

La racine de mon problème est que en mode passif, l'adresse IP utilisée pour ouvrir le socket lors d'une connexion de données est différent que celui utilisé pour la connexion initiale. Donc en raison de deux problèmes (voir ci-dessous) avec APACHE commons-net 3.5 il était incroyablement difficile à comprendre. Ma solution: Étendre FTPSClient pour que je puisse outrepasser les méthodes _parsePassiveModeReply&openDataConnection. Mon parsePassiveModeReply est vraiment de gagner le port de la réponse puisque la réponse indique que le port est utilisé. Ma méthode openDataConnection utilise le port sauvegardé et L'IP d'origine utilisée lors de la connexion.

problèmes avec APACHE FTPCLient 3.5

  1. la connexion de données ne s'arrête pas (hang) donc son n'est pas apparent quel est le problème.
  2. la classe FTPSClient ne saute pas les adresses IP passives. Paramètre passiveNatWorkaround à vrai ne fonctionne pas comme je m'y attendais, ou peut-être il ne saute pas l'IP du tout.

choses à surveiller:

  • lorsque vous passez par un pare-feu, vous devez avoir accès à la plage de ports définir par IIS (voir Configuration du pare-feu Microsoft IIS).
  • Vous devez également vous assurer que vous avez des certificats appropriés dans votre keystore ou le cert spécifié à l'exécution.
  • ajouter ce qui suit à votre classe, c'est très utile pour savoir ce que Les commandes FTP sont exécutées.

       ftpClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out), true));
    
  • vérifiez les journaux du serveur FTP car il vous dira ce qui est effectué et possible pourquoi vous rencontrez des problèmes. Vous devriez toujours voir un canal de données ouvertes avant d'exécuter une liste. Comparer les résultats de votre application à celle de ce qu'une commande curl réussie exécute.
  • codes de réponse car ils indiqueront où un problème se produit.
  • utilisez la commande curl pour vérifier que vous avez la connectivité, la suivante c'est un bon début et si tout va bien va lister le contenu à la racine répertoire.

    curl -3 ftps://[user id]:[password][ftp server ip]:990/ -1 -v --disable-epsv --ftp-skip-pasv-ip --ftp-ssl --insecure
    

FTPSClient extended (SAMPLE CODE)

import java.io.IOException;
import java.net.Inet6Address;
import java.net.InetSocketAddress;
import java.net.Socket;

import javax.net.ssl.SSLContext;

import org.apache.commons.net.MalformedServerReplyException;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.net.ftp.FTPSClient;

/**
 * TODO Document Me!
 */
public class PassiveFTPSClient extends FTPSClient {
    private String passiveSkipToHost;
    private int passiveSkipToPort;
    private boolean skipPassiveIP;


    /** Pattern for PASV mode responses. Groups: (n,n,n,n),(n),(n) */
    private static final java.util.regex.Pattern PARMS_PAT;    
    static {
    PARMS_PAT = java.util.regex.Pattern.compile(
            "(\d{1,3},\d{1,3},\d{1,3},\d{1,3}),(\d{1,3}),(\d{1,3})");
       }
    /**
     * @param b
     * @param sslContext
     */
    public PassiveFTPSClient(boolean b, SSLContext sslContext) {
    super(b, sslContext);
    }

    protected void _parsePassiveModeReply(String reply) throws MalformedServerReplyException {
    if (isSkipPassiveIP()) {
        System.out.println( "================> _parsePassiveModeReply"  + getPassiveSkipToHost());
        java.util.regex.Matcher m = PARMS_PAT.matcher(reply);
        if (!m.find()) {
        throw new MalformedServerReplyException(
            "Could not parse passive host information.\nServer Reply: " + reply);
        }
        try {
        int oct1 = Integer.parseInt(m.group(2));
        int oct2 = Integer.parseInt(m.group(3));
        passiveSkipToPort = (oct1 << 8) | oct2;
        }
        catch (NumberFormatException e) {
        throw new MalformedServerReplyException(
            "Could not parse passive port information.\nServer Reply: " + reply);
        }            
        //do nothing
    } else {
        super._parsePassiveModeReply(reply);
    }
    }

    protected Socket _openDataConnection_(String command, String arg) throws IOException {
    System.out.println( "================> _openDataConnection_"  + getPassiveSkipToHost());
    System.out.println( "================> _openDataConnection_ isSkipPassiveIP: " + isSkipPassiveIP());        
    if (!isSkipPassiveIP()) {
        return super._openDataConnection_(command, arg);
    }
    System.out.println( "================> getDataConnectionMode: " + getDataConnectionMode());
    if (getDataConnectionMode() != ACTIVE_LOCAL_DATA_CONNECTION_MODE &&
        getDataConnectionMode() != PASSIVE_LOCAL_DATA_CONNECTION_MODE) {
        return null;
    }

    final boolean isInet6Address = getRemoteAddress() instanceof Inet6Address;

    Socket socket;
    if (getDataConnectionMode() == ACTIVE_LOCAL_DATA_CONNECTION_MODE) {
        return super._openDataConnection_(command, arg);

    }
    else
    { // We must be in PASSIVE_LOCAL_DATA_CONNECTION_MODE

        // Try EPSV command first on IPv6 - and IPv4 if enabled.
        // When using IPv4 with NAT it has the advantage
        // to work with more rare configurations.
        // E.g. if FTP server has a static PASV address (external network)
        // and the client is coming from another internal network.
        // In that case the data connection after PASV command would fail,
        // while EPSV would make the client succeed by taking just the port.
        boolean attemptEPSV = isUseEPSVwithIPv4() || isInet6Address;
        if (attemptEPSV && epsv() == FTPReply.ENTERING_EPSV_MODE)
        {

        System.out.println( "================> _parseExtendedPassiveModeReply a: ");                
        _parseExtendedPassiveModeReply(_replyLines.get(0));
        }
        else
        {
        if (isInet6Address) {
            return null; // Must use EPSV for IPV6
        }
        // If EPSV failed on IPV4, revert to PASV
        if (pasv() != FTPReply.ENTERING_PASSIVE_MODE) {
            return null;
        }
        System.out.println( "================> _parseExtendedPassiveModeReply b: ");
        _parsePassiveModeReply(_replyLines.get(0));
        }
        // hardcode fore testing
        //__passiveHost = "10.180.255.181";
        socket = _socketFactory_.createSocket();
        if (getReceiveDataSocketBufferSize() > 0) {
        socket.setReceiveBufferSize(getReceiveDataSocketBufferSize());
        }
        if (getSendDataSocketBufferSize()  > 0) {
        socket.setSendBufferSize(getSendDataSocketBufferSize() );
        }
        if (getPassiveLocalIPAddress() != null) {
        System.out.println( "================> socket.bind: " + getPassiveSkipToHost());
        socket.bind(new InetSocketAddress(getPassiveSkipToHost(), 0));
        }

        // For now, let's just use the data timeout value for waiting for
        // the data connection.  It may be desirable to let this be a
        // separately configurable value.  In any case, we really want
        // to allow preventing the accept from blocking indefinitely.
        //     if (__dataTimeout >= 0) {
        //         socket.setSoTimeout(__dataTimeout);
        //     }

        System.out.println( "================> socket connect: " + getPassiveSkipToHost() + ":" + passiveSkipToPort);
        socket.connect(new InetSocketAddress(getPassiveSkipToHost(), passiveSkipToPort), connectTimeout);
        if ((getRestartOffset() > 0) && !restart(getRestartOffset()))
        {
        socket.close();
        return null;
        }

        if (!FTPReply.isPositivePreliminary(sendCommand(command, arg)))
        {
        socket.close();
        return null;
        }
    }

    if (isRemoteVerificationEnabled() && !verifyRemote(socket))
    {
        socket.close();

        throw new IOException(
            "Host attempting data connection " + socket.getInetAddress().getHostAddress() +
            " is not same as server " + getRemoteAddress().getHostAddress());
    }

    return socket;
        }

    /**
    * Enable or disable passive mode NAT workaround.
    * If enabled, a site-local PASV mode reply address will be replaced with the
    * remote host address to which the PASV mode request was sent
    * (unless that is also a site local address).
    * This gets around the problem that some NAT boxes may change the
    * reply.
    *
    * The default is true, i.e. site-local replies are replaced.
    * @param enabled true to enable replacing internal IP's in passive
    * mode.
    */
    public void setSkipPassiveIP(boolean enabled) {
    super.setPassiveNatWorkaround(enabled);
    this.skipPassiveIP = enabled;
    System.out.println( "================> skipPassiveIP: " + skipPassiveIP);
    }
    /**
     * Return the skipPassiveIP.
     * @return the skipPassiveIP
     */
    public boolean isSkipPassiveIP() {
    return skipPassiveIP;
    }
    /**
     * Return the passiveSkipToHost.
     * @return the passiveSkipToHost
     */
    public String getPassiveSkipToHost() {
    return passiveSkipToHost;
    }

    /**
     * Set the passiveSkipToHost.
     * @param passiveSkipToHost the passiveSkipToHost to set
     */
    public void setPassiveSkipToHost(String passiveSkipToHost) {
    this.passiveSkipToHost = passiveSkipToHost;
    System.out.println( "================> setPassiveSkipToHost: " + passiveSkipToHost);
    }

}
1
répondu Tm Zengerle 2016-12-01 22:28:45