2010-06-30 4 views
24

MISE À JOUR: RésoluTransfert binaire brut avec apache commons-net FTPClient?

j'appelle FTPClient.setFileType()avant je me suis connecté, ce qui provoque le serveur FTP pour utiliser le mode par défaut (ASCII), peu importe ce que je l'ai mis à. Le client, en revanche, se comportait comme si le type de fichier avait été correctement défini. Le mode BINARY fonctionne maintenant exactement comme vous le souhaitez, en transportant le fichier byte-by-byte dans tous les cas. Tout ce que je devais faire était un peu de trafic reniflant dans wireshark et puis imitant les commandes FTP en utilisant netcat pour voir ce qui se passait. Pourquoi n'y ai-je pas pensé il y a deux jours? Merci à tous pour votre aide!

J'ai un fichier xml, codé en utf-16, que je télécharge à partir d'un site FTP en utilisant FTPClient de la bibliothèque java commons-net-2.0 d'apache. Il offre un support pour deux modes de transfert: ASCII_FILE_TYPE et BINARY_FILE_TYPE, la différence étant que ASCII remplacera les séparateurs de ligne avec le séparateur de ligne locale approprié ('\r\n' ou seulement '\n' - en hexadécimal, 0x0d0a ou seulement 0x0a).Mon problème est le suivant: J'ai un fichier de test, utf-16 encodée, qui contient les éléments suivants:

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

est ici l'hexagone:
0000000: 003c 003f 0078 006d 006c 0020 0076 0065 .<.?.x.m.l. .v.e
0000010: 0072 0073 0069 006f 006e 003d 0027 0031 .r.s.i.o.n.=.'.1
0000020: 002e 0030 0027 0020 0065 006e 0063 006f ...0.'. .e.n.c.o
0000030: 0064 0069 006e 0067 003d 0027 0075 0074 .d.i.n.g.=.'.u.t
0000040: 0066 002d 0031 0036 0027 003f 003e 000a .f.-.1.6.'.?.>..
0000050: 003c 0064 0061 0074 0061 003e 000a 0009 .<.d.a.t.a.>....
0000060: 003c 0062 006c 0061 0068 003e 0062 006c .<.b.l.a.h.>.b.l
0000070: 0061 0068 003c 002f 0062 006c 0061 0068 .a.h.<./.b.l.a.h
0000080: 003e 000a 003c 002f 0064 0061 0074 0061 .>...<./.d.a.t.a
0000090: 003e 000a                                                                                                                       .>..

Lorsque j'utilise le mode ASCII pour ce fichier, il transfère correctement, octet par octet; le résultat a le même md5sum. Génial. Lorsque j'utilise BINARY mode de transfert, ce qui est pas censé faire quoi que ce soit, mais d'une lecture aléatoire octets InputStream dans un OutputStream, le résultat est que les nouvelles lignes (0x0a) sont convertis en retour chariot + paires de nouvelle ligne (0x0d0a).Voici l'hexagone après le transfert binaire:

0000000: 003c 003f 0078 006d 006c 0020 0076 0065 .<.?.x.m.l. .v.e
0000010: 0072 0073 0069 006f 006e 003d 0027 0031 .r.s.i.o.n.=.'.1
0000020: 002e 0030 0027 0020 0065 006e 0063 006f ...0.'. .e.n.c.o
0000030: 0064 0069 006e 0067 003d 0027 0075 0074 .d.i.n.g.=.'.u.t
0000040: 0066 002d 0031 0036 0027 003f 003e 000d .f.-.1.6.'.?.>..
0000050: 0a00 3c00 6400 6100 7400 6100 3e00 0d0a ..<.d.a.t.a.>...
0000060: 0009 003c 0062 006c 0061 0068 003e 0062 ...<.b.l.a.h.>.b
0000070: 006c 0061 0068 003c 002f 0062 006c 0061 .l.a.h.<./.b.l.a
0000080: 0068 003e 000d 0a00 3c00 2f00 6400 6100 .h.>....<./.d.a.
0000090: 7400 6100 3e00 0d0a                                                                                 t.a.>...

Non seulement il ne convertit les caractères (qui newline il ne devrait pas), mais il ne respecte pas l'encodage utf-16 (pas que j'attendre à savoir qu'il doit , c'est juste un tuyau FTP stupide). Le résultat est illisible sans autre traitement pour réaligner les octets. Je voudrais simplement utiliser le mode ASCII, mais mon application va également se déplacer réelles données binaires (fichiers mp3 et images jpeg) à travers le même canal. L'utilisation du mode de transfert BINARY sur ces fichiers binaires entraîne également l'injection aléatoire de 0x0d s dans leur contenu, ce qui ne peut pas être supprimé en toute sécurité car les données binaires contiennent souvent des séquences 0x0d0a légitimes. Si j'utilise le mode ASCII sur ces fichiers, le FTPClient "intelligent" convertit ces 0x0d0a en 0x0a en laissant le fichier incohérent, peu importe ce que je fais.

Je suppose que ma/mes question (s) est (est): est-ce que quelqu'un sait de bonnes bibliothèques FTP pour Java qui déplacent simplement les damnés octets de là à, ou vais-je devoir hack apache commons-net -2.0 et gérer mon propre code client FTP juste pour cette application simple? Est-ce que quelqu'un d'autre a fait face à ce comportement bizarre? Toute suggestion serait appréciée.

J'ai vérifié le code source de commons-net et il ne semble pas qu'il soit responsable du comportement étrange lorsque le mode BINARY est utilisé. Mais le InputStream il lit du mode BINARY est juste un java.io.BufferedInptuStream enveloppé autour d'une prise InputStream. Est-ce que ces streams java de niveau inférieur font des manipulations d'octets bizarres? Je serais choqué s'ils le faisaient, mais je ne vois pas ce qui pourrait se passer d'autre ici.

EDIT 1:

Voici un morceau de code minimal qui imite ce que je fais pour télécharger le fichier.Pour compiler, il suffit de faire

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

Pour exécuter, vous aurez besoin des répertoires/tmp/ascii et/tmp/binaire pour le fichier à télécharger, ainsi qu'un site FTP mis en place avec le fichier assis dans ce . Le code devra également être configuré avec l'hôte ftp, le nom d'utilisateur et le mot de passe appropriés. J'ai mis le fichier sur mon site ftp testing sous le dossier test/et j'ai appelé le fichier test.xml. Le fichier de test devrait au moins avoir plus d'une ligne, et être encodé en utf-16 (cela peut ne pas être nécessaire, mais aidera à recréer ma situation exacte). J'ai utilisé la commande :set fileencoding=utf-16 de vim après avoir ouvert un nouveau fichier et entré le texte xml référencé ci-dessus. Enfin, pour courir, il suffit de faire

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

code:

(REMARQUE: ce code modifié pour utiliser l'objet personnalisée ClientFTP, lien ci-dessous sous "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; 
    } 

} 

EDIT 2:

Okay J'ai suivi le conseil CheckedXputStream et voici mes résultats. J'ai fait une copie de FTPClient d'Apache appelé MyFTPClient, et j'ai enveloppé le SocketInputStream et le BufferedInputStream dans un CheckedInputStream en utilisant CRC32 checksums. En outre, j'ai enveloppé le FileOutputStream que je donne à FTPClient pour stocker la sortie dans un total de contrôle CheckOutputStreamCRC32. Le code pour MyFTPClient est posté here et j'ai modifié le code de test ci-dessus pour utiliser cette version de FTPClient (j'ai essayé de poster une URL majeure au code modifié, mais j'ai besoin de 10 points de réputation pour publier plus d'une URL!), test.xml et test.mp3 et les résultats étaient ainsi:

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 

Cela fait, pratiquement nul sens que ce soit parce que voici les md5sums des fichiers corresponsing:

bf89673ee7ca819961442062eaaf9c3f ascii/test.mp3 
7bd0e8514f1b9ce5ebab91b8daa52c4b binary/test.mp3 
ee172af5ed0204cf9546d176ae00a509 original/test.mp3 

104e14b661f3e5dbde494a54334a6dd0 ascii/test.xml 
36f482a709130b01d5cddab20a28a8e8 binary/test.xml 
104e14b661f3e5dbde494a54334a6dd0 original/test.xml 

Je suis à une perte. I jurer Je n'ai pas permuté les noms de fichiers/chemins à tout moment dans ce processus, et j'ai vérifié trois fois chaque étape. Ça doit être quelque chose de simple, mais je n'ai pas la moindre idée de l'endroit où aller. Dans l'intérêt de la commodité, je vais procéder en appelant à la coque pour faire mes transferts FTP, mais j'ai l'intention de poursuivre jusqu'à ce que je comprenne ce qui se passe. Je vais mettre à jour ce fil avec mes conclusions, et je continuerai à apprécier toute contribution que quelqu'un pourrait avoir. Espérons que cela sera utile à quelqu'un à un moment donné!

+2

Wow, c'est bizarre. J'ai vérifié le code source pour 'BufferedInputStream' et' SocketInputStream' (au moins la partie Java) et je ne vois rien qui puisse changer les octets comme ça. Je suggérerais de faire une copie de 'FTPClient' et de changer la hiérarchie de flux d'entrée' CheckedInputStream (BufferedInputStream (CheckedInputStream (SocketInputStream()))), et utiliser les checksums pour voir si vous pouvez identifier où les octets sont changés. Ce serait une information utile à avoir dans la question. (Encore mieux, mettez votre code de test en ligne et un lien vers celui-ci) –

+1

Aussi, +1 pour une question bien écrite ;-) –

+0

Je vais essayer ça; Je vous remercie. Je n'avais jamais entendu parler de CheckedInputStream. Très sympa!! – cgs1019

Répondre

4

Il me semble que votre code d'application a peut-être inversé la sélection des modes ASCII et BINARY. ASCII est en cours d'exécution inchangée, BINARY effectuant des traductions de caractères de fin de ligne est le exactement opposé de la façon dont FTP est censé fonctionner.

Si ce n'est pas le problème, veuillez modifier votre question pour ajouter la partie pertinente de votre code.

EDIT

Deux ou trois autres possibles (mais l'OMI improbable) explications:

  • Le serveur FTP est cassé/mal configuré. (Pouvez-vous télécharger avec succès le fichier en mode ASCII/BINARY en utilisant un utilitaire FTP en ligne de commande non Java?
  • Vous parlez au serveur FTP via un proxy brisé ou mal configuré.
  • Vous avez réussi à obtenir une copie douteuse du fichier JAR du client FTP Apache. (Oui, oui, très peu probable ...)
+0

Cela semblerait être le cas, mais j'ai exécuté le code au moins 5 fois et j'ai supprimé autant de variables que possible. J'ai modifié mon post pour inclure le code que j'ai vérifié recrée le problème. Malheureusement, je ne peux pas proposer un site ftp à partir duquel télécharger le fichier, donc j'espère que vous avez accès à un site (je ne fais que tester sur localhost). Merci pour la réponse, et j'apprécierais que vous ayez des idées à partager! – cgs1019

+0

J'ai considéré le premier cas que vous mentionnez comme l'explication la plus probable si le code est correct. C'est une installation par défaut de proftp sur Ubuntu. J'ai juste essayé de télécharger avec le client standard de ligne de commande de ftp et le dossier de xml vient finit (c'est, quelque peu, à prévoir puisque le client emploie probablement le mode ascii, qui a transféré le xml correctement avec FTPClient). Il transfère également le fichier mp3 correctement (même md5sum), donc il ne ressemble pas à un serveur, à moins que FTPClient ne s'y connecte avec des paramètres différents de ceux du client de ligne cmd (une possibilité). – cgs1019

+1

En outre, je vous avais upvoted pour votre aide mais je n'ai pas 15 points de rep pour le moment! :) – cgs1019

25

Après connexion au serveur ftp

ftp.setFileType(FTP.BINARY_FILE_TYPE); 

La ligne ci-dessous ne résout pas:

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

Merci, cela l'a fait pour moi. Étrange que le mode texte serait par défaut. – Davor

2

Je trouve que Apache retrieveFile (...) ne fonctionnait parfois pas avec des tailles de fichier dépassant une certaine limite. Pour surmonter cela, j'utiliserait retrieveFileStream() à la place. Avant de télécharger J'ai mis CORRECTEMENT FileType et définir le mode modePassif

Ainsi, le code ressemblera

.... 
    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(); 
    .... 
Questions connexes