Oppure

Loading

Oltre al WebBrowser, ci sono altri tre modi di scaricare file da internet. In questo paragrafo li analizzerò uno per uno.

Download sincrono gestito

Il primo e più semplice dei suddetti modi consiste nell'utilizzare una classe messa a disposizione dal Framework, ossia WebClient (del namespace System.Net). Una volta istanziato un oggetto di questo tipo, è possibile richiamare da esso molti metodi diversi per scaricare praticamente qualsiasi cosa. Qui espongo i metodi sincroni:

  • DownloadData(uri) : scarica il file con dato uri (Uniform Resource Identifier, una forma più generale dell'url) e restituisce tutti i dati scaricati sottoforma di un array di bytes. Particolarmente indicato per scaricare piccoli file binari ad uso temporaneo;
  • DownloadFile(url, path) : scarica il file indicato dall'indirizzo url e lo salva nel percorso path su disco fisso;
  • DownloadString(uri) : molto simile a DownloadData, ma anziché restituire un array di bytes, restituisce una stringa.

Ci sono, poi, altri membri che è interessante conoscere:

  • Credentials : indica le credenziali usate per accedere alla data risorsa. È utile impostare questa proprietà quando si accede a server che richiedono un'autenticazione tramite nome utente e password, come ad esempio si usa fare quando si utilizza il protocollo ftp per il trasferimento di file. Ad esempio:
    Dim W As New Net.WebClient
    W.Credentials = New Net.NetworkCredential("username", "password")
  • Headers : espone una collezione degli header posti all'inizio della richiesta per il file. Quando un metodo di download viene invocato, la classe WebClient si preoccupa di inviare una richiesta opportuna al server. Ad essa può aggiungere alcune metainformazioni note come headers, definite dallo standard del protocollo HTTP (di cui potete trovare una descrizione approfondita qui). Nei prossimi esempi userò un solo tipo di header, Range, che permette di ottenere solo una data parte del file;
  • Proxy : imposta il proxy che la classe attraversa per inoltrare la richiesta;
  • QueryString : indica un insieme di chiavi e valori che costituiscono la query applicata alla pagina richiesta. Una query string può essere accodata alla fine dell'url introducendola con un "?", definendo una coppia come nome=valore e separando tutte le copie da un carattere "&". Serve per ottenere risultati diversi da una stessa pagina, specificando cosa si sta cercando.

Alcuni semplici esempi:

Dim W As New Net.WebClient
'Scarica l'home page del sito e la salva in C:
W.DownloadFile("http://totem.altervista.org/index.php", "C:index.php")

Dim S As String
'Scarica il contenuto del file Capitoli.txt e lo salva
'nella stringa S
S = W.DownloadString("http://totem.altervista.org/guida/versione3/Capitoli.txt")

'Aggiunge una coppia nome-valore alla query
W.QueryString.Add("name", "twaveeditor")
'La prossima richiesta sarà quindi equivalente a:
' http://totem.altervista.org/download/details.php?name=twaveeditor
'Ossia scaricherà la pagina di download di TWave Editor.
'Il contenuto del file verrà salvato in B
Dim B() As Byte = W.DownloadData("http://totem.altervista.org/download/details.php")

La pecca di questi metodi è che sono sincroni, ossia bloccano il funzionamento dell'applicazione fino a quando il download non è terminato. Questo comportamento può rivelarsi utile in certi casi e rendere più maneggevole il codice per scaricare file di piccole dimensioni, ma è tutt'altro che accettabile per grandi quantità di dati.

Download asincrono gestito

Per file molto grandi, invece, ci vengono in aiuto le versioni asincrone dei metodi sopra esposti: sono riconoscibili dal suffisso "Async" dopo il nome del metodo. Questi eseguono il download in un thread separato, perciò non interferiscono con le normali operazioni del programma. In compenso, sono un po' più difficili da gestire, ma nulla di particolarmente complicato.
I metodi asincroni si richiamano usando esattamente gli stessi parametri delle versioni sincrone, ma per sapere come stanno andando le cose, dobbiamo fare uso di due eventi della classe WebClient: DownloadProgressChanged, che notifica il progresso del download, e DownloadFileCompleted (o DownloadDataCompleted o DownloadStringCompleted, a seconda dei casi). Ecco un semplice esempio:

Class Form1
    'WithEvents permette di gestire gli eventi di W
    Private WithEvents W As New Net.WebClient()

    Private Sub btnDownload_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDownload.Click
        'Inizia il download asincrono
        W.DownloadFileAsync(New Uri(txtUrl.Text), txtFile.Text)
        btnCancel.Enabled = True
        btnDownload.Enabled = False
    End Sub

    Private Sub W_DownloadProgressChanged(ByVal sender As Object, ByVal e As Net.DownloadProgressChangedEventArgs) Handles W.DownloadProgressChanged
        'Il parametro e contiene alcune informazioni
        'sul progresso del download
        lblStatus.Text = _
            String.Format("Bytes ricevuti: {0} B{3}Dimensione file: {1} B{3}Progresso: {2:N0}%", _
            e.BytesReceived, e.TotalBytesToReceive, _
            e.ProgressPercentage, Environment.NewLine)
    End Sub

    Private Sub W_DownloadFileCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.AsyncCompletedEventArgs) Handles W.DownloadFileCompleted
        'e.Cancelled vale True se il download è stato annullato.
        'e.Error è di tipo Exception e contiene l'eccezione
        '  generata nel caso si sia verificato un errore.
        If e.Cancelled Then
            MessageBox.Show("Il download è stato cancellato!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
        ElseIf e.Error IsNot Nothing Then
            MessageBox.Show("Si è verificato un errore: " & e.Error.Message, Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
        Else
            MessageBox.Show("Download completato con successo!", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Information)
        End If
        btnDownload.Enabled = True
        btnCancel.Enabled = False
    End Sub

    Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancel.Click
        'Il metodo CancelAsync cancella il download asincrono
        W.CancelAsync()
        btnDownload.Enabled = True
        btnCancel.Enabled = False
    End Sub
    
End Class

 

Download sincrono/asincrono non gestito

Come ho illustrato nei paragrafi precedenti, WebClient si occupa di eseguire molte istruzioni riguardo al download: connettersi al server indicato, creare una richiesta valida secondo il protocollo usato (HTTP o FTP o altri), inoltrare la richiesta, aspettare una risposta, leggere dallo stream di rete i dati forniti dal server e copiarli nel file indicato, quindi chiudere la connessione. Insomma, non lascia nulla al controllo del programmatore. Con il prossimo metodo che andrò ad introdurre, potremmo manipolare alcuni di questi passaggi a nostro piacimento.
Le classi che ci interessano ora sono WebRequest e WebResponse, del namespace System.Net. Esse sono classi astratte, poiché ogni protocollo implementa le proprie richieste e risposte secondo determinati standard. Nel nostro esempio, useremo HttpWebRequest per creare ed inviare una richiesta http ad un server e HttpWebResponse per interpretarne la risposta. Sappiate, però, che esistono anche le rispettive versioni per il protocollo FTP, ossia FtpWebRequest e FtpWebResponse. Ecco una prima semplice versione del codice:

Public Sub DownloadFile(ByVal Address As String, ByVal Path As String)
    'Crea una richiesta http per l'indirizzo Address.
    'Address può anche contenere una query string
    Dim Request As Net.HttpWebRequest = Net.HttpWebRequest.Create(Address)
    'Invia la richieste a ottiene la risposta
    Dim Response As Net.HttpWebResponse = Request.GetResponse()
    'Ottiene da Response uno stream di rete dal quale si
    'potrà leggere il file richiesto.
    Dim Reader As IO.Stream = Response.GetResponseStream()
    'Crea un nuovo file in locale
    Dim Writer As New IO.FileStream(Path, IO.FileMode.Create)
    'Un buffer di byte che contiene i blocchi letti 
    'dallo stream. La lettura a blocchi è più
    'conveniente che trasferire in massa tutto il contenuto,
    'poiché altrimenti si dovrebbe usare un buffer
    'gigantesco (almeno le dimensioni del file)
    Dim Buffer(8127) As Byte
    Dim BytesRead As Int32

    'La funzione Read, vi ricordo, restituisce come risultato
    'il numero di bytes effettivamente letti dallo stream
    BytesRead = Reader.Read(Buffer, 0, Buffer.Length)
    Do While BytesRead > 0
        Writer.Write(Buffer, 0, BytesRead)
        BytesRead = Reader.Read(Buffer, 0, Buffer.Length)
    Loop

    Reader.Close()
    Writer.Close()
    Response = Nothing
    Request = Nothing
End Sub

Prima di procedere, vorrei fare alcuni chiarimenti sullo stream di rete. Esso rappresenta un flusso di dati che proviene dal server a cui si è inviata la richiesta, ed è un flusso a senso unico, perciò non supporta operazioni di ricerca (invocando il metodo Seek o modificando la proprietà Position otterrete degli errori). Non è neppure possibile saperne la dimensione complessiva, poiché anche la proprietà Length genera eccezioni. E, infine, non è possibile scrivervi sopra. Esiste un modo per sapere le dimensioni dei dati, ossia richiamare la proprietà Reponse.ContentLength, che, tuttavia, potrebbe contenere valori privi di senso (ad esempio -1). Questo succede perchè essa si limita ad esporre il valore di un header posto nella risposta http: tuttavia, il server non è obbligato ad inserire questo header, e se non lo fa, non c'è modo di leggerlo.
Osserviamo ora che tutte le operazioni svolte sono sincrone, ma, come il titolo suggerisce, è possibile rendere tutto il metodo asincrono, facendo uso dei thread. Infatti, è sufficiente eseguire la procedura in un thread differente: per ulteriori informazioni sul multithreading, vedere capitolo relativo.
In ultimo, è possibile ottenere solo una parte del file aggiungendo l'header Range alla richiesta, come anticipato nei paragrafi precedenti. Dato che la proprietà Headers di WebClient vieta l'uso di questo header (non è ben chiara la ragione), l'unico modo per usarlo consiste nell'impiegare quest'ultimo metodo di download. Basta richiamare AddRange prima dell'invio della richiesta:

'...
'Indica al server che vogliamo iniziare la lettura
'dall'offset n
Request.AddRange(n)

'oppure

'Indica al server che vogliamo iniziare la lettura dalla
'posizione n, ma solo fino alla posizione q
Request.AddRange(n, q)

Non serve eseguire altre operazioni particolari per la lettura. Lo stream ottenuto consentirà di leggere esattamente ciò che si è richiesto come se fosse un unico flusso di dati.

A cura di: Il Totem