org not net listfiles ftpclient exist example does commons java ftp binary-data apache-commons-net

not - java ftp connection



Transferir binario sin procesar con apache commons-net FTPClient? (3)

ACTUALIZACIÓN: resuelto

Estaba llamando a FTPClient.setFileType() antes de FTPClient.setFileType() sesión, lo que provocó que el servidor FTP utilizara el modo predeterminado ( ASCII ) sin importar en qué lo configuré. El cliente, por otro lado, se comportaba como si el tipo de archivo hubiera sido configurado correctamente. BINARY modo BINARY ahora está funcionando exactamente como se desea, transportando el byte de byte de archivo en todos los casos. Todo lo que tenía que hacer era husmear un poco en wireshark y luego simular los comandos de FTP usando netcat para ver qué estaba pasando. ¿Por qué no pensé en eso hace dos días? ¡Gracias a todos por su ayuda!

Tengo un archivo xml, utf-16 codificado, que descargo de un sitio FTP utilizando el FTPClient de la biblioteca java commons-net-2.0 de apache. Ofrece soporte para dos modos de transferencia: ASCII_FILE_TYPE y BINARY_FILE_TYPE , la diferencia es que ASCII reemplazará los separadores de línea con el separador de línea local apropiado ( ''/r/n'' o simplemente ''/n'' - en hexadecimal, 0x0d0a o simplemente 0x0a ) . Mi problema es este: tengo un archivo de prueba, codificado para utf-16, que contiene lo siguiente:

<?xml version=''1.0'' encoding=''utf-16''?>
<data>
<blah>blah</blah>
</data>

Aquí está el hex:
0000000: 003c 003f 0078 006d 006c 0020 0076 0065 .<.?.xml .ve
0000010: 0072 0073 0069 006f 006e 003d 0027 0031 .rsion=.''.1
0000020: 002e 0030 0027 0020 0065 006e 0063 006f ...0.''. .enco
0000030: 0064 0069 006e 0067 003d 0027 0075 0074 .ding=.''.ut
0000040: 0066 002d 0031 0036 0027 003f 003e 000a .f.-.1.6.''.?.>..
0000050: 003c 0064 0061 0074 0061 003e 000a 0009 .<.data>....
0000060: 003c 0062 006c 0061 0068 003e 0062 006c .<.blah>.bl
0000070: 0061 0068 003c 002f 0062 006c 0061 0068 .ah<./.blah
0000080: 003e 000a 003c 002f 0064 0061 0074 0061 .>...<./.data
0000090: 003e 000a .>..

Cuando uso el modo ASCII para este archivo, se transfiere correctamente, byte por byte; el resultado tiene el mismo md5sum. Estupendo. Cuando uso el modo de transferencia BINARY , que no se supone que haga nada más que mezclar bytes desde un InputStream en un OutputStream , el resultado es que las nuevas líneas ( 0x0a ) se convierten a carriage return + newline pairs ( 0x0d0a ). Aquí está el hex después de la transferencia binaria:

0000000: 003c 003f 0078 006d 006c 0020 0076 0065 .<.?.xml .ve
0000010: 0072 0073 0069 006f 006e 003d 0027 0031 .rsion=.''.1
0000020: 002e 0030 0027 0020 0065 006e 0063 006f ...0.''. .enco
0000030: 0064 0069 006e 0067 003d 0027 0075 0074 .ding=.''.ut
0000040: 0066 002d 0031 0036 0027 003f 003e 000d .f.-.1.6.''.?.>..
0000050: 0a00 3c00 6400 6100 7400 6100 3e00 0d0a ..<.data>...
0000060: 0009 003c 0062 006c 0061 0068 003e 0062 ...<.blah>.b
0000070: 006c 0061 0068 003c 002f 0062 006c 0061 .lah<./.bla
0000080: 0068 003e 000d 0a00 3c00 2f00 6400 6100 .h.>....<./.da
0000090: 7400 6100 3e00 0d0a ta>...

No solo convierte los caracteres de nueva línea (lo que no debería), sino que no respeta la codificación de utf-16 (no es que esperara que supiera que debería ser, es solo un canal de FTP tonto). El resultado es ilegible sin procesamiento adicional para realinear los bytes. Solo usaría el modo ASCII , pero mi aplicación también moverá datos binarios reales (archivos mp3 e imágenes jpeg) a través del mismo conducto. El uso del modo de transferencia BINARY en estos archivos binarios también hace que se 0x0d aleatorios en sus contenidos, que no se pueden eliminar de manera segura ya que los datos binarios a menudo contienen secuencias 0x0d0a legítimas. Si utilizo el modo ASCII en estos archivos, el FTPClient "inteligente" convierte estos 0x0d0a en 0x0a dejando el archivo incoherente sin importar lo que haga.

Supongo que mi (s) pregunta (s) es (son): ¿Alguien sabe de alguna buena biblioteca de FTP para Java que solo mueva los malditos bytes desde allí hasta aquí, o voy a tener que hackear Apache commons-net-2.0 y mantener mi propio código de cliente FTP solo para esta sencilla aplicación? ¿Alguien más ha lidiado con este extraño comportamiento? Cualquier sugerencia sera apreciada.

Comprobé el código fuente de commons-net y no parece que sea responsable del comportamiento extraño cuando se usa el modo BINARY . Pero el InputStream que está leyendo en modo BINARY es solo un java.io.BufferedInptuStream envuelto alrededor de un socket InputStream . ¿Alguna vez estas secuencias Java de nivel más bajo hacen alguna manipulación de bytes extraña? Me sorprendería si lo hicieran, pero no veo qué más podría estar sucediendo aquí.

EDIT 1:

Aquí hay una pieza mínima de código que imita lo que estoy haciendo para descargar el archivo. Para compilar, solo hazlo

javac -classpath /path/to/commons-net-2.0.jar Main.java

Para ejecutar, necesitará directorios / tmp / ascii y / tmp / binary para el archivo para descargar, así como un sitio ftp configurado con el archivo que se encuentra en él. El código también deberá configurarse con el host ftp, el nombre de usuario y la contraseña apropiados. Puse el archivo en mi sitio ftp de prueba bajo la carpeta test / y llamé al archivo test.xml. El archivo de prueba debe tener al menos más de una línea y estar codificado en UTF-16 (esto puede no ser necesario, pero ayudará a recrear mi situación exacta). Utilicé el comando vim''s :set fileencoding=utf-16 después de abrir un nuevo archivo e ingresé el texto xml mencionado anteriormente. Finalmente, para correr, solo hazlo

java -cp .:/path/to/commons-net-2.0.jar Main

Código:

(NOTA: este código se modificó para usar un objeto FTPClient personalizado, vinculado a continuación debajo de "EDIT 2")

import java.io.*; import java.util.zip.CheckedInputStream; import java.util.zip.CheckedOutputStream; import java.util.zip.CRC32; import org.apache.commons.net.ftp.*; public class Main implements java.io.Serializable { public static void main(String[] args) throws Exception { Main main = new Main(); main.doTest(); } private void doTest() throws Exception { String host = "ftp.host.com"; String user = "user"; String pass = "pass"; String asciiDest = "/tmp/ascii"; String binaryDest = "/tmp/binary"; String remotePath = "test/"; String remoteFilename = "test.xml"; System.out.println("TEST.XML ASCII"); MyFTPClient client = createFTPClient(host, user, pass, org.apache.commons.net.ftp.FTP.ASCII_FILE_TYPE); File path = new File("/tmp/ascii"); downloadFTPFileToPath(client, "test/", "test.xml", path); System.out.println(""); System.out.println("TEST.XML BINARY"); client = createFTPClient(host, user, pass, org.apache.commons.net.ftp.FTP.BINARY_FILE_TYPE); path = new File("/tmp/binary"); downloadFTPFileToPath(client, "test/", "test.xml", path); System.out.println(""); System.out.println("TEST.MP3 ASCII"); client = createFTPClient(host, user, pass, org.apache.commons.net.ftp.FTP.ASCII_FILE_TYPE); path = new File("/tmp/ascii"); downloadFTPFileToPath(client, "test/", "test.mp3", path); System.out.println(""); System.out.println("TEST.MP3 BINARY"); client = createFTPClient(host, user, pass, org.apache.commons.net.ftp.FTP.BINARY_FILE_TYPE); path = new File("/tmp/binary"); downloadFTPFileToPath(client, "test/", "test.mp3", path); } public static File downloadFTPFileToPath(MyFTPClient ftp, String remoteFileLocation, String remoteFileName, File path) throws Exception { // path to remote resource String remoteFilePath = remoteFileLocation + "/" + remoteFileName; // create local result file object File resultFile = new File(path, remoteFileName); // local file output stream CheckedOutputStream fout = new CheckedOutputStream(new FileOutputStream(resultFile), new CRC32()); // try to read data from remote server if (ftp.retrieveFile(remoteFilePath, fout)) { System.out.println("FileOut: " + fout.getChecksum().getValue()); return resultFile; } else { throw new Exception("Failed to download file completely: " + remoteFilePath); } } public static MyFTPClient createFTPClient(String url, String user, String pass, int type) throws Exception { MyFTPClient ftp = new MyFTPClient(); ftp.connect(url); if (!ftp.setFileType( type )) { throw new Exception("Failed to set ftpClient object to BINARY_FILE_TYPE"); } // check for successful connection int reply = ftp.getReplyCode(); if (!FTPReply.isPositiveCompletion(reply)) { ftp.disconnect(); throw new Exception("Failed to connect properly to FTP"); } // attempt login if (!ftp.login(user, pass)) { String msg = "Failed to login to FTP"; ftp.disconnect(); throw new Exception(msg); } // success! return connected MyFTPClient. return ftp; } }

EDICION 2:

De acuerdo, seguí el consejo de CheckedXputStream y aquí están mis resultados. Hice una copia del FTPClient de apache llamado MyFTPClient , y envolví tanto el SocketInputStream como el BufferedInputStream en un CheckedInputStream usando las sumas de comprobación CRC32 . Además, envolví el FileOutputStream que doy a FTPClient para almacenar el resultado en un CheckOutputStream con suma de comprobación CRC32 . El código para MyFTPClient se publica aquí y modifiqué el código de prueba anterior para usar esta versión del FTPClient (traté de publicar una URL clave en el código modificado, pero necesito 10 puntos de reputación para publicar más de una URL), test.xml y test.mp3 y los resultados fueron así:

14:00:08,644 DEBUG [main,TestMain] TEST.XML ASCII 14:00:08,919 DEBUG [main,MyFTPClient] Socket CRC32: 2739864033 14:00:08,919 DEBUG [main,MyFTPClient] Buffer CRC32: 2739864033 14:00:08,954 DEBUG [main,FTPUtils] FileOut CRC32: 866869773 14:00:08,955 DEBUG [main,TestMain] TEST.XML BINARY 14:00:09,270 DEBUG [main,MyFTPClient] Socket CRC32: 2739864033 14:00:09,270 DEBUG [main,MyFTPClient] Buffer CRC32: 2739864033 14:00:09,310 DEBUG [main,FTPUtils] FileOut CRC32: 2739864033 14:00:09,310 DEBUG [main,TestMain] TEST.MP3 ASCII 14:00:10,635 DEBUG [main,MyFTPClient] Socket CRC32: 60615183 14:00:10,635 DEBUG [main,MyFTPClient] Buffer CRC32: 60615183 14:00:10,636 DEBUG [main,FTPUtils] FileOut CRC32: 2352009735 14:00:10,636 DEBUG [main,TestMain] TEST.MP3 BINARY 14:00:11,482 DEBUG [main,MyFTPClient] Socket CRC32: 60615183 14:00:11,482 DEBUG [main,MyFTPClient] Buffer CRC32: 60615183 14:00:11,483 DEBUG [main,FTPUtils] FileOut CRC32: 60615183

Esto hace, básicamente, cero sentido alguno porque aquí están los md5sums de los archivos correspondientes:

bf89673ee7ca819961442062eaaf9c3f ascii/test.mp3 7bd0e8514f1b9ce5ebab91b8daa52c4b binary/test.mp3 ee172af5ed0204cf9546d176ae00a509 original/test.mp3 104e14b661f3e5dbde494a54334a6dd0 ascii/test.xml 36f482a709130b01d5cddab20a28a8e8 binary/test.xml 104e14b661f3e5dbde494a54334a6dd0 original/test.xml

Estoy perdido Juro que no he permutado los nombres de archivo / rutas en ningún punto de este proceso, y he verificado tres veces cada paso. Debe ser algo simple, pero no tengo la más remota idea de dónde mirar a continuación. En aras de la practicidad, voy a proceder llamando al intérprete de comandos para hacer mis transferencias FTP, pero tengo la intención de seguir hasta que entienda qué demonios está pasando. Actualizaré este hilo con mis hallazgos, y continuaré agradeciendo cualquier contribución que pueda tener alguien. ¡Espero que esto sea útil para alguien en algún momento!


Me parece que el código de la aplicación podría haber invertido la selección del modo ASCII y BINARIO. ASCII viene sin cambios, BINARY realiza traducciones de fin de línea es exactamente lo opuesto de cómo se supone que FTP funciona.

Si ese no es el problema, edite su pregunta para agregar la parte relevante de su código.

EDITAR

Un par de otras posibles explicaciones (pero IMO improbable):

  • El servidor FTP está roto / mal configurado. (¿Se puede descargar con éxito el archivo en modo ASCII / BINARY utilizando una utilidad FTP de línea de comandos que no sea Java?)
  • Está hablando con el servidor FTP a través de un proxy que está roto o mal configurado.
  • De alguna manera has logrado obtener una copia poco fiable (pirateada) del archivo JAR del cliente Apache FTP. (Sí, sí, muy poco probable ...)

Después de iniciar sesión en el servidor ftp

ftp.setFileType(FTP.BINARY_FILE_TYPE);

La siguiente línea no lo resuelve:

//ftp.setFileTransferMode(org.apache.commons.net.ftp.FTP.BINARY_FILE_TYPE);


Descubrí que Apache retrieveFile (...) a veces no funcionaba con File Sizes excediendo un cierto límite. Para superar eso, usaría retrieveFileStream () en su lugar. Antes de la descarga, configuré el tipo de archivo correcto y establecí el modo en PassiveMode

Entonces el código se verá

.... ftpClientConnection.setFileType(FTP.BINARY_FILE_TYPE); ftpClientConnection.enterLocalPassiveMode(); ftpClientConnection.setAutodetectUTF8(true); //Create an InputStream to the File Data and use FileOutputStream to write it InputStream inputStream = ftpClientConnection.retrieveFileStream(ftpFile.getName()); FileOutputStream fileOutputStream = new FileOutputStream(directoryName + "/" + ftpFile.getName()); //Using org.apache.commons.io.IOUtils IOUtils.copy(inputStream, fileOutputStream); fileOutputStream.flush(); IOUtils.closeQuietly(fileOutputStream); IOUtils.closeQuietly(inputStream); boolean commandOK = ftpClientConnection.completePendingCommand(); ....