Manuale di ftp4j 1.1

Requisiti

Per eseguire la libreria ftp4j è necessario un Java Runtime Environment J2SE v. 1.4 o successivo.

Installazione

Si aggiunga il file ftp4.jar al classpath dell'applicazione che deve richiamarlo.

Documentazione javadoc

ftp4j api

Per cominciare

La classe principale della libreria è FTPClient (it.sauronsoftware.ftp4j.FTPClient).

Si inizia creando un'istanza di FTPClient:

FTPClient client = new FTPClient();

Il client deve poi essere connesso ad un servizio FTP remoto:

client.connect("ftp.host.com");

Se la porta del servizio FTP remoto non è quella standard, cioè la 21:

client.connect("ftp.host.com", port);

Ad esempio:

client.connect("ftp.host.com", 8021);

Il protocollo FTP richiede ora una fase di autenticazione:

client.login("carlo", "mypassword");

Se nessuna eccezione viene propagata il client è ora autenticato per l'uso del servizio. In caso contrario, se il tentativo di accesso fallisce, si riceve una it.sauronsoftware.ftp4j.FTPException.

L'accesso anonimo, se consentito dal server, può essere svolto inviando lo username "anonymous" ed una password qualsiasi (attenzione al fatto che alcuni server, come password per l'accesso anonimo, richiedono un indirizzo e-mail):

client.login("anonymous", "ftp4j");

Una volta completate le operazioni è necessario disconnettere il client:

client.disconnect(true);

Questa formula invia il comando QUIT al server FTP, richiedendo una procedura di disconnessione legale in base al protocollo. Per interrompere la connessione senza eseguire la procedura legale di disconnessione, è possibile richiamare:

client.disconnect(false);

Connessione attraverso un proxy

Il client di ftp4j si connette al server attraverso un connettore (un oggetto che implementa l'interfaccia it.sauronsoftware.ftp4j.FTPConnector). Il client carica il connettore che gli è stato assegnato e gli domanda la connessione con il server. Il connettore stabilisce la connessione e ne restituisce il controllo al client (attraverso un oggetto che implementa l'interfaccia it.sauronsoftware.ftp4j.FTPConnection). Grazie a questo meccanismo di astrazione ftp4j è in grado di supportare la connessione attraverso diversi tipi di proxy.

Il connettore di un client può essere impostato attraverso il metodo setConnector(), naturalmente prima di connettere il client al server.

client.setConnector(anyConnectorYouWant);

Se nessun connettore viene impostato, il client usa quello predefinito, che è DirectConnector (it.sauronsoftware.ftp4j.connectors.DirectConnector). DirectConnector stabilisce una connessione TCP diretta tra il client ed il server, senza altre destinazioni o protocolli intermediari.

Se il server FTP remoto può essere raggiunto solo passando attraverso un proxy, allora bisogna cambiare il connettore. Con ftp4j sono forniti diversi connettori, in grado di negoziare la connessione con i più comuni tipi di proxy:

Poiché l'architettura di ftp4j è modulare, altri connettori possono essere sviluppati da chiunque, implementando l'interfaccia FTPConnector.

Navigare nel sito remoto

Per recuperare il percorso della directory di lavoro corrente:

String dir = client.currentDirectory();

Per cambiare la directory di lavoro:

client.changeDirectory(newPath);

Possono essere usati sia i percorsi assoluti (rispetto alla radice del sito) sia quelli relativi:

client.changeDirectory("/an/absolute/one");
client.changeDirectory("relative");

Per spostarsi nella directory superiore, cioè quella che contiene la directory di lavoro corrente:

client.changeDirectoryUp();

Rinominare i file e le directory

Per rinominare un file o una directory:

client.rename("oldname", "newname");

Spostare i file e le directory:

Il metodo rename() può essere usato anche per spostare i file e le directory da una posizione ad un'altra.

Si immagini di avere nella directory di lavoro corrente un file chiamato "myfile.txt", che deve essere spostato nella sotto-directory "myfolder":

client.rename("myfile.txt", "myfolder/myfile.txt");

Cancellare i file

Per cancellare un file remoto:

client.deleteFile(relativeOrAbsolutePath);

Ad esempio:

client.deleteFile("useless.txt");

Creare e cancellare le directory

Per creare una nuova directory nel sito remoto, ammesso che i diritti di accesso lo consentano:

client.createDirectory("newfolder");

Per cancellare una directory esistente:

client.deleteDirectory(absoluteOrRelativePath);

Ad esempio:

client.deleteDirectory("oldfolder");

Si faccia attenzione al fatto che, generalmente, i servizi FTP si rifiutano di cancellare le directory non vuote.

Elencare i file, le directory ed i collegamenti

Il protocollo FTP non specifica alcun metodo ampiamente supportato per elencare i dettagli del contenuto di una directory. La cosa che ci va più vicina è il comando LIST, ma che purtroppo lascia al server libera scelta sul formato della risposta. Così accade che alcuni server rispondono con un output che ricorda quello del comando ls di UNIX, altri come il dir di DOS, altri ancora con formati alternativi ai due.

La libreria ftp4j può riconoscere diversi formati. Una volta interpretata la risposta in base al formato riconosciuto, al programmatore viene restituita una rappresentazione ad oggetti di quanto elaborato. Attualmente ftp4j può riconoscere ed interpretare gli stili del tipo:

Tutto ciò viene fatto attraverso degli interpreti modulari. Il pacchetto it.sauronsoftware.ftp4j.listparsers contiene gli interpreti in grado di gestire i formati appena elencati. Il più delle volte sono sufficienti.

Per elencare il contenuto della directory di lavoro corrente:

FTPFile[] list = client.list();

Se si riceve una FTPListParseException (it.sauronsoftware.ftp4j.FTPListParseException) significa che nessuno degli interpreti di ftp4j è riuscito a decodificare la risposta fornita dal server. Se dovesse capitare, si può provare ad usare il metodo alternativo listNames(), che però restituisce molte meno informazioni rispetto a list(). A mali estremi, estremi rimedi: è possibile costruire un nuovo interprete in grado di gestire il particolare formato riscontrato, estendendo l'interfaccia FTPListParser (it.sauronsoftware.ftp4j.FTPListParser). L'istanza del nuovo interprete può essere aggiunta al client con il metodo addListParser().

Gli oggetti FTPFile (it.sauronsoftware.ftp4j.FTPFile) mantengono una rappresentazione dei contenuti della directory di lavoro, e sono usati per rappresentare i file, le sotto-directory ed i collegamenti. In base al tipo di risposta dato dal server alcune informazioni di un oggetto FTPFile possono essere assenti o impostate su valori di poco senso. Si faccia riferimento ai javadoc della libreria per maggiori dettagli.

Data di modifica di file, directory e collegamenti

Normalmente un oggetto FTPFile riporta la data di modifica dell'entità rappresentata, ma come si è appena detto il dettaglio e la completezza della risposta dipendono dalla tipologia del server remoto. Quando l'informazione manca si può provare a recuperarla in questo modo:

java.util.Date md = client.modifiedDate("filename.ext");

Download ed upload dei file

La maniera più semplice per scaricare un file è chiamare il metodo download(String, File):

client.download("remoteFile.ext", new java.io.File("localFile.ext"));

L'analogo per l'upload è:

client.upload(new java.io.File("localFile.ext"));

Si faccia attenzione al fatto che queste sono chiamate bloccanti: il metodo ritorna solo dopo che il trasferimento è stato completato (oppure interrotto, o anche fallito). Durante il trasferimento, inoltre, un lock viene imposto sull'intero client e quindi anche usando più thread non è proprio possibile fare upload e download di più file simultaneamente con un solo oggetto FTPClient. Questo limite è stato imposto perché il protocollo FTP ammette un solo trasferimento alla volta per sessione. Sicuramente, però, sarà capitato di osservare dei client FTP in grado di trasferire simultaneamente due o più file. Ciò viene ottenuto stabilendo più sessioni con lo stesso sito remoto. Naturalmente è possibile farlo anche con ftp4j, istanziando e connettendo allo stesso sito più oggetti FTPClient. Lato-client nessun limite in tal senso viene imposto, si faccia però attenzione al fatto che lato-server può esserci una regola che limita il numero di connessioni ricevibili da parte del medesimo host.

Piuttosto che attendere il completamento di un trasferimento senza fare nulla, può essere preferibile monitorare il progresso dell'operazione. Può essere fatto mediante un FTPDataTransferListener (it.sauronsoftware.ftp4j.FTPDataTransferListener). Si implementi il proprio:

import it.sauronsoftware.ftp4j.FTPDataTransferListener;

public class MyTransferListener implements FTPDataTransferListener {

	public void started() {
		// Trasferimento avviato
	}

	public void transferred(int length) {
		// Altri length byte sono stati trasferiti da quando questo metodo
		// è stato richiamanto l'ultima volta
	}

	public void completed() {
		// Trasferimento completato
	}

	public void aborted() {
		// Trasferimento annullato
	}

	public void failed() {
		// Trasferimento fallito
	}

}

Adesso si trasferisca come segue:

client.download("remoteFile.ext", new java.io.File("localFile.ext"), new MyTransferListener());
client.upload(new java.io.File("localFile.ext"), new MyTransferListener());

Mentre il client sta trasferendo un file, l'operazione può essere interrotta da un altro thread che richiama il metodo abortCurrentDataTransfer() sul medesimo oggetto FTPClient. Il metodo richiede un parametro booleano: true per negoziare l'annullamento del trasferimento con il server (mediante il comando ABOR), false per chiudere bruscamente il canale di trasferimento senza darne previa comunicazione al server.

client.abortCurrentDataTransfer(true); // Invia ABOR
client.abortCurrentDataTransfer(false); // Interrompre bruscamente

Si osservi che anche i metodi list() e listNames() implicameno un trasferimento (la risposta del server viene fornita su un canale di trasferimento secondario, e non lungo la linea di comunicazione principale). Per questo motivo il metodo abortCurrentDataTransfer() può interrompere anche una procedura di tipo list.

Quando un trasferimento viene interrotto su comando del client, i metodi download(), upload(), list() e listNames() terminano lanciando una FTPAbortedException (it.sauronsoftware.ftp4j.FTPAbortedException).

Le operazioni di download ed upload non completate possono essere riprese fornendo un parametro restartAt:

client.download("remoteFile.ext", new java.io.File("localFile.ext"), 1056);

Questo esempio riprende l'operazione a partire dal byte 1056 del file. Il primo byte trasferito sarà il numero 1057.

Altre varianti di download() ed upload() lavorano con gli stream invece che con gli oggetti java.io.File. In questa maniera è possibile scambiare i dati non solo con il file system, ma anche con un database, una connessione di rete o... un altro oggetto FTPClient!

Trasferimenti attivi e passivi

I canali di trasferimento sono stabiliti su una separata connessione di rete tra il client ed il server. Chi contatta l'altro? La risposta dipende dal modo adottato. Il server può essere attivo o passivo. Quando è attivo il server si connette al client per trasferire dei dati; quando è passivo è il client a dover stabilire la connessione.

Il trasferimento attivo può essere utilizzato solo se il client è in grado di ricevere connessioni da parte del server. Se il client è dietro un firewall, un proxy, un gateway o un misto di questi è molto poco probabile che il server possa raggiungerlo per stabilire una connessione. Perciò di solito si preferisce chiedere al server di operare in maniera passiva, in modo che sia sempre e solo il client a svolgere le connessioni attraverso il proprio connettore.

Con ftp4j è possibile scegliere il modo in cui sarà stabilita la connessione per un trasferimento:

client.setPassive(false); // Server attivo
client.setPassive(true); // Server passivo

Il valore predefinito è true, cioè al server viene chiesto di restare passivo in modo che sia sempre il client a connettersi.

Trasferimenti binari e testuali

Un altro concetto legato ai trasferimenti riguarda i tipi binari e testuali. Quando un trasferimento è binario il file viene trattato da ambo le parti come una sequenza binaria, cioè un flusso di byte, e viene registrato da chi lo riceve così come gli è arrivato, senza analisi o manipolazioni dei dati ricevuti. Quando un trasferimento è testuale, al contrario, ambo le parti possono applicare delle trasformazioni di charset. Si immagini di avere un client in esecuzione su una piattaforma Windows, mentre il server gira su un sistema UNIX. Normalmente i charset usati dalle due piattaforme sono diversi. Il client deve inviare al sito remoto un file dai contenuti testuali. Scegliendo il trasferimento testuale il client leggerà il file supponendo che i suoi contenuti siano stati codificati secondo il charset predefinito della macchina che lo esegue. Il contenuto del file sarà decodificato e ricodificato in una codifica intermedia. Il server riceverà i dati nella codifica intermedia, e sarà suo compito riconvertirli secondo il charset predefinito della propria macchina. Se i charset di partenza e di destinazione sono differenti la sequenza di byte che compone il file trasferito sarà molto probabilmente cambiata, ma il contenuto rimarrà lo stesso e la leggibilità mantenuta.

Per scegliere il tipo del trasferimento con ftp4j:

client.setType(FTPClient.TYPE_TEXTUAL);
client.setType(FTPClient.TYPE_BINARY);
client.setType(FTPClient.TYPE_AUTO);

La costante TYPE_AUTO chiede al client di scegliere di volta in volta quale tipo di trasferimento negoziare con il server. Il client individua la natura del file da trasferire in base alla sua estensione, e lo fa attraverso un FTPTextualExtensionRecognizer (it.sauronsoftware.ftp4j.FTPTextualExtensionRecognizer). Il riconoscitore di estensioni testuali predefinito è un'istanza di it.sauronsoftware.ftp4j.recognizers.DefaultTextualExtensionRecognizer, che riconosce come testuali le seguenti estensioni:

abc acgi aip asm asp c c cc cc com conf cpp
csh css cxx def el etx f f f77 f90 f90 flx
for for g h h hh hh hlb htc htm html htmls
htt htx idc jav jav java java js ksh list
log lsp lst lsx m m mar mcf p pas php pl pl
pm py rexx rt rt rtf rtx s scm scm sdml sgm
sgm sgml sgml sh shtml shtml spc ssi talk
tcl tcsh text tsv txt uil uni unis uri uris
uu uue vcs wml wmls wsc xml zsh

E' possibile costruire altri riconoscitori, implementando l'interfaccia FTPTextualExtensionRecognizer, ma è molto più conveniente istanziare la classe ParametricTextualExtensionRecognizer (it.sauronsoftware.ftp4j.recognizers.ParametricTextualExtensionRecognizer), che permette al programmatore di specificare liberamente la lista delle estensioni da riconoscere come testuali. In ogni caso un'istanza del riconoscitore va poi registrata presso il client:

client.setTextualExtensionRecognizer(myRecognizer);

Il comando NOOP

Si immagini che il client è fermo senza fare nulla perché in attesa di input da parte dell'utente. Può accadere che il server, non riscontrando alcuna attività per un periodo troppo lungo, chiuda la connessione con il client. Non appena il client tenterà un'operazione questa fallirà, poiché la linea di comunicazione è stata chiusa unilateralmente. Per evitare lo scadere del timeout di inattività è possibile inviare di tanto in tanto, durante i periodi di stasi, un comando NOOP al server. Questo genere di comando non esegue alcuna reale operazione, ed ha il solo effetto di far sapere al server che il client è ancora attivo.

Con ftp4j è sufficiente chiamare:

client.noop();

Comandi del sito e personalizzati

Per inviare un comando specifico del sito:

FTPReply reply = client.sendSiteCommand("YOUR COMMAND");

Per inviare comandi personalizzati:

FTPReply reply = client.sendCustomCommand("YOUR COMMAND");

Sia sendSiteCommand() che sendCustomCommand() restituiscono un oggetto FTPReply (it.sauronsoftware.ftp4j.FTPReply). Attraverso questo oggetto è possibile controllare la risposta ricevuta, recuperando il codice ed il messaggio che la costituiscono. L'interfaccia FTPCodes (it.sauronsoftware.ftp4j.FTPCodes) riporta i codici comuni delle risposte FTP, così che è possibile andare a raffrontare il codice di una FTPReply senza dover consultare le specifiche FTP.

Gestione delle eccezioni

La libreria ftp4j definisce cinque tipi di eccezione: