retrievefile - java sftp client
Apache Commons FTPClient que cuelga (4)
Ayer no dormí pero creo que resolví el problema.
Puede aumentar el tamaño del búfer con 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();
}
}
Espero que este código pueda ser útil para alguien!
Estamos utilizando el siguiente código FTP de Apache Commons Net para conectarse a un servidor FTP, sondear algunos directorios en busca de archivos y, si se encuentran archivos, recuperarlos en la máquina local:
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());
}
Tenemos esto programado para correr cada minuto, en el minuto. Cuando se implementa en Tomcat (7.0.19), este código se carga perfectamente bien y comienza a trabajar sin problemas. Cada vez, sin embargo, en algún momento u otro, parece que simplemente se cuelga . Con eso quiero decir:
- No existen volcados de pila
- Tomcat todavía se está ejecutando (puedo ver su pid y puedo iniciar sesión en la aplicación del administrador web)
- Dentro de la aplicación del administrador, puedo ver que mi WAR aún está en ejecución / iniciado
-
catalina.out
y mi registro específico de la aplicación no muestran signos de ninguna excepción lanzada
Así que la JVM todavía se está ejecutando. Tomcat todavía se está ejecutando, y mi WAR desplegado aún se está ejecutando, pero simplemente está colgando. A veces se ejecuta durante 2 horas y luego se cuelga; Otras veces se ejecuta por días y luego se cuelga. Pero cuando se cuelga, lo hace entre la línea que lee About to check loadables/ for files...
(que veo en los registros) y la línea que lee File fetch completed.
(que no veo).
Esto me indica que el bloqueo se produce durante el sondeo / recuperación real de los archivos, lo que me indica en la misma dirección que esta pregunta que encontré y que se relaciona con el interbloqueo de FTPClient. Esto me hace preguntarme si estos son los mismos problemas ( si lo son, ¡eliminaré felizmente esta pregunta! ). Sin embargo, no creo que sean iguales (no veo las mismas excepciones en mis registros).
Un compañero de trabajo mencionó que podría ser una cosa "pasiva" frente a "activa" de FTP. Sin saber realmente la diferencia, estoy un poco confundido por los campos del ACTIVE_REMOTE_DATA_CONNECTION_MODE
FTP ACTIVE_REMOTE_DATA_CONNECTION_MODE
, PASSIVE_REMOTE_DATA_CONNECTION_MODE
, etc. y no sabía qué pensaba SO sobre eso como un problema potencial.
Ya que estoy capturando Throwable
como último recurso aquí, habría esperado ver algo en los registros si algo va mal. Ergo, siento que esto es un problema definitivo.
¿Algunas ideas? Desafortunadamente, no sé lo suficiente acerca de las funciones internas de FTP para hacer un diagnóstico firme. ¿Podría ser esto algo del lado del servidor? ¿Relacionado con el servidor FTP?
Esto podría ser una serie de cosas, pero la sugerencia de tu amigo valdría la pena.
Pruebe ftpClient.enterLocalPassiveMode();
Para ver si ayuda.
También sugeriría colocar la desconexión en el bloque finally
para que nunca deje una conexión ahí fuera.
Tuve este mismo problema al intentar realizar una lista de archivos desde una máquina Linux a un servidor IIS. El código funcionó muy bien desde mi estación de trabajo de desarrollador, pero se bloqueaba cuando se ejecutaba en el servidor, específicamente debido a que un cortafuegos no funcionaba.
Debe hacer estas cosas en orden y requerirá que extienda FTPSClient 3.5
- connect (implícito = verdadero, SSLContext = TLS)
- Compruebe isPositiveCompletion
- autenticar (por supuesto)
- execPBSZ (0)
- execPROT ("P")
- configura el valor booleano para indicar Omitir IP pasiva (clase FTPSClient personalizada)
- configurar la dirección IP de la conexión de guardado (clase FTPSClient personalizada)
- setUseEPSVwithIPv4 (falso)
- enterLocalPassiveMode () o enterRemotePassiveMode ()
- initteListParsing () o cualquier comando de lista a. En este punto se ejecutará openDataConnection , asegúrese de guardar el puerto que se está utilizando aquí b.) El comando PASV se ejecuta c.) Se ejecuta _parsePassiveModeReply, aquí abrirá el socket con la dirección IP que utilizó para la conexión y el puerto guardado.
- desconectar (siempre)
Más información: mi problema es específico de un firewall entre la máquina Linux y el servidor IIS.
La raíz de mi problema es que, en modo pasivo, la dirección IP utilizada para abrir el socket cuando se realiza una conexión de datos es diferente a la utilizada para hacer la conexión inicial. Así que debido a dos problemas (ver más abajo) con APACHE commons-net 3.5 fue increíblemente difícil de descifrar. Mi solución: extienda FTPSClient para que pueda anular los métodos _parsePassiveModeReply y openDataConnection . Mi parsePassiveModeReply solo está guardando el puerto de la respuesta ya que la respuesta indica qué puerto se está utilizando. Mi método openDataConnection está utilizando el puerto guardado y la IP original utilizada durante la conexión.
Problemas con APACHE FTPCLient 3.5
- La conexión de datos no se agota (se bloquea), por lo que no es evidente cuál es el problema.
- La clase FTPSClient no omite las direcciones IP pasivas. Establecer pasivoNatWorkaround en true no funciona como esperaba o tal vez no omita la IP en absoluto.
Cosas para prestar atención a:
- Al pasar por un firewall, debe tener acceso al rango de puertos definido por IIS (consulte la configuración del firewall de Microsoft IIS).
- También debe asegurarse de tener los certificados adecuados en su almacén de claves o el certificado especificado en el tiempo de ejecución.
Agregue lo siguiente a su clase, esto es muy útil para saber qué comandos de FTP se están ejecutando.
ftpClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out), true));
- Verifique los registros del servidor FTP, ya que le indicarán qué se está realizando y por qué tiene problemas. Siempre debe ver un canal de datos abierto antes de ejecutar una lista. Compare los resultados de su aplicación con los de lo que realiza un comando curl exitoso.
- Códigos de respuesta ya que indicarán dónde está ocurriendo un problema.
Use el comando curl para verificar que tenga conectividad. El siguiente es un buen comienzo y, si todo está bien, enumerará el contenido en el directorio raíz.
curl -3 ftps://[user id]:[password][ftp server ip]:990/ -1 -v --disable-epsv --ftp-skip-pasv-ip --ftp-ssl --insecure
FTPSClient extendido (CÓDIGO DE MUESTRA)
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);
}
}
Tuve que incluir lo siguiente después del inicio de sesión para llamar a los archivos de la lista y transferirlos sin que se "colgaran" y, finalmente, fallaran:
s.login(username, password);
s.execPBSZ(0);
s.execPROT("P");