in “Java ed il protocollo HTTP (Prima Parte)” abbiamo visto come richiedere una connessione ad un server HTTP, cosa interessante, ma anche abbastanza inutile dal punto di vista dell’utente se non a scopo indicativo.
In questa seconda parte vedremo un semplice classe che si occupa di scaricare un file da un indirizzo URL remoto in un file su un percorso locale in modo sequenziale.
Il codice è ampiamente commentato, quindi dovrebbe essere di facile comprensione per chi ha già seguito il primo articolo della serie o ha un minimo di dimestichezza con le api di rete Java.
Inizializzazione del download
Innanzitutto vengono letti i primi due parametri passati come argomenti dalla linea di comando passati al costruttore ella classe SimpleHttpDownloader (anche se non è molto elegante). Il primo parametro è l’indirizzo URL del file da scaricare tramite HTTP, il secondo parametro è il percorso locale su cui l’utente desidera sia salvato il file scaricato.
Il costruttore innanzitutto controlla se i parametri passati sono corretti e validi per eseguire il download cioè se l’URL è malformato o meno, se non lo è apre una connessione all’indirizzo specificato e crea un nuovo oggetto di tipoURLConnection:
URLConnection connection = null;
try {// Crea un oggetto URL dalla stringa passata come primo parametro…
URL sourceURL = new URL(sourceURLString);
// …se l’URL non è malformato apre una connessione
connection = sourceURL.openConnection();} catch (MalformedURLException ex) {
// …se l’URL è malformato avvisa l’utente ed esce
System.err.println(“L’URL \”" + sourceURLString + “\” specificato è malformato.\nControllare di aver digitato correttamente l’URL del documento da scaricare.”);
System.err.println(“Descrizione dell’errore: ” + ex.getLocalizedMessage());
System.exit(-1);} catch (IOException ex) {
// …se non è possibile aprire una connessione all’URL, ad esempio perchè è inesistente
// o il server non è disponibile in quel momento avvisa l’utente fornendo anche una
// descrizione dell’errore ed esce.
System.err.println(“Si è verificato un errore durante la connessione al server.”);
System.err.println(“Descrizione dell’errore: ” + ex.getLocalizedMessage());
System.exit(-1);}
Instaurata la connessione controlla se il file corrispondente al secondo parametro passato è esistente, se non lo è richiede un handle per la scrittura sul file:
// Crea un oggetto corrispondente al file di destinazione al percorso
// indicato come secondo parametro
File destinationFile = new File(destinationPath);// Controlla se il file esiste già…
if (destinationFile.exists()) {// …se esite avverte l’utente ed esce.
//
// NOTA: questa è solo una precauzione per evitare di sovrascrivere un eventuale
// file preesistente, ma potrebbe tranquillamente essere omessa ed il file
// eventualmente già presente sarebbe sovrascritto.
System.err.println(“Il file di destinazione \”" + destinationFile + “\” specificato è già esistente.\nE’ necessario che il file di destinazione non esista.”);
System.exit(-1);}
FileWriter destinationWriter = null;
try {
// Apre un handle per la scrittura sul file di destinazione
destinationWriter = new FileWriter(destinationFile);} catch (IOException ex) {
// In caso di errore fornisce una breve descrizione all’utente ed esce.
System.err.println(“Si è verificato un errore durante l’inizializzazione del file di destinazione per la scrittura.”);
System.err.println(“Descrizione dell’errore: ” + ex.getLocalizedMessage());
System.exit(-1);}
I dati sono letti dalla posizione remota attraverso uno stream di input reso disponibile dall’oggetto URLConnection:
InputStream urlInputStream = null;
try {
// Apre uno stream di input per la lettura dei dati dalla connessione
// instaurata precedentemente.
urlInputStream = connection.getInputStream();} catch (IOException ex) {
// In caso non sia possibile avverte l’utente ed esce.
System.err.println(“Si è verificato un errore durante la creazione dello stream di lettura.”);
System.err.println(“Descrizione dell’errore: ” + ex.getLocalizedMessage());
System.exit(-1);
}
Prima di poter procedere con il trasferimento è necessario inizializzare alcuni contatori che poi saranno usati per mantenere informato l’utente sullo stato del download.
La variabile totalDownloadSize è inizializzata con la dimensione in bytes del file da scaricare ottenuta tramite invocazione del metodo getContentLength() della claseURLConnection.
int totalDownloadSize = connection.getContentLength();
downloadedSize è il contatore dei bytes scaricati durante il ciclo di trasferimento.
int downloadedSize = 0;
La variabile downloadPrecent rappresenta la percentuale di avanzamento dello scaricamento. E’ utilizzata per scremare l’output delle informazioni mostrate per aggiornare l’utente sullo stato dello scaricamento.
int downloadPrecent = 0;
byteReaded contiene l’ultimo byte letto dallo stream di input.
int byteReaded = -1;
Trasferimento
Terminata l’inizializzazione dei contatori si passa al ciclo di trasferimento vero e proprio che inizia con la lettura del primo byte dallo stream di input.
byteReaded = urlInputStream.read();
Il ciclo legge un singolo byte e lo scrive immediatamente sul file di destinazione
// Il ciclo termina quando tutti i bytes sono stati trasferiti.
while (byteReaded != -1) {// Scrive il byte appena scaricato sul file di destinazione.
destinationWriter.write(byteReaded);
// Incrementa il contatore dei bytes scaricati.
downloadedSize++;
Per mantenere l’utente informato vengono stampate delle informazioni relative alla percentuale di trasferimento completato ed ai bytes scaricati; le informazioni però vengono stampate solo ed esclusivamente quando varia la percentuale di avanzamento perchè se si stampasse una stringa informativa per ogni byte scaricato il ciclo rallenterebbe molto rendendo downloads di pochi byte molto lunghi. se si desidera provare è sufficiente commentare il la stringa di apertura e chiusura del blocco if
// Confronta la percentuale di avanzamento attuale con l’ultima memorizzata.
// Se sono uguali evita di mostrare l’output…
if (((downloadedSize * 100) / totalDownloadSize) != downloadPrecent) {// …altrimenti memorizza la percentuale attuale…
downloadPrecent = ((downloadedSize * 100) / totalDownloadSize);
// …ed aggiorna l’utente sullo stato del trasferimento.
System.out.println(“[ " + downloadPrecent + "% ] Scaricati ” + downloadedSize + ” bytes di ” + totalDownloadSize);}
// Legge il byte successivo.
byteReaded = urlInputStream.read();
}
Terminato il trasferimento vengono chiusi stream ed handle al file di destinazione.
// Forza la scrittura di eventuali dati pendenti nel file di destinazione.
destinationWriter.flush();
// Chiude l’handle al file e lo stream di input.
destinationWriter.close();
urlInputStream.close();
Conclusione
Il codice mostra con quanta facilità è possibile scaricare un file dalla rete, ma indubbiamente non è esattamente un esempio come portare le prestazioni all’estremo. Il codice sarebbe più adatto, infatti, se stessimo prelevando dati da un server FTP in quanto è consentita una sola connessione per scaricare un file ed il download deve essere eseguito in modo sequenziale. A contrario il protocollo HTTP è in grado di accettare richieste multiple per il download di un solo file, in cui ogni richiesta specifica nell’header il range di bytes del file che desidera ottenere, ciò consente di avere più flussi di trasferimento contemporaneamente (multi thread download), che sarà l’argomento della terza parte di questa serie di tutorials
.
Riferimenti
Come per la prima parte della serie di tutorials su Java ed HTTP, il codice sorgente ed i files di progetto per l’IDE NetBeans 6.0 sono disponibili per il download coperti da licenza GPL: SimpleHttpDownloader (Sorgenti – Files progetto NetBeans 6) (0)
Per quanti volessero approfondire l” argomento HTTP è utile consultare questi siti:
Informazioni sull”HTTP in Wikipedia: Pagina su Wikipedia dedicata al protocollo HTTP
RFC 1945: Specifiche della versione 1.0 del protocollo HTTP
RFC 2616: Specifiche della versione 1.1 del protocollo HTTP



marzo 30th, 2010 at 15:16
[...] Nella seconda parte della serie “Java ed il protocollo HTTP” abbiamo imparato come scaricare un file da un server HTTP remoto leggendo un flusso sequenziale di dati; in questa terza parte sfrutteremo il capo “Range” dell’header HTTP per scaricare il file remoto dividendolo in più segmenti che saranno scaricati in modo parallelo. La figura seguente può aiutare a capire il procedimento che, normalmente, è definito multi-threading download: [...]