Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Scambiare dati in background (HTML5/JavaScript)

Link copiato negli appunti

Nelle lezioni precedenti abbiamo visto come definire a avviare task in background. Ora ci concentreremo sullo scambio di informazioni e dati in background e su trasferimento di file da e verso servizi remoti.

Trasferire dati in background

Lo scenario più frequente quando utilizziamo un background task è forse quello in cui effettuiamo operazioni di download o upload di una risorsa web.

Il particolare ciclo di vita di un'applicazione Windows Store, ci induce a ricorrere a un processo separato per l'esecuzione di queste attività, che spesso si rivelano lunghe.

Immaginiamo di far partire il download o l'upload dall'applicazione principale, il sistema potrebbe sospendere la nostra app o perfino terminarla prima che completiamo l'esecuzione delle operazioni. Questo ad esempio nel caso in cui un utente porti l'app in background per passare ad altro (ipotesi tutt'altro che improbabile, specie se si scaricano file molto pesanti).

In casi come questi, se l'app viene chiusa o terminata, le nostre operazioni di download/upload subiscono la medesima sorte, poiché un'app sospesa non dispone di CPU, né può accedere al filesystem.

Per questo abbiamo a disposizione una serie di classi del namespace Windows.Networking.BackgroundTransfer che permettono di evitare questo problema eseguendo le operazioni di trasferimento in background per file o risorse web. Queste classi ci permettono di sfruttare i protocolli HTTP e HTTPS, sia per l'upload, sia per il download, mentre il protocollo FTP è supportato solo per il download.

Queste API sono pensate per il trasferimento di file di notevoli dimensioni, come musica, immagini di alta qualità o video (sebbene le API funzionino anche per file di piccole dimensioni, in questo caso Microsoft suggerisce di usare la più tradizionale classe HttpClient).

Ecco le due classi principali per i trasferimenti:

  • BackgroundDownloader, per gestire i download
  • BackgroundUploader, per l'upload

Tramite queste classi è possibile avviare operazioni di downoad/upload concorrenti (purché mediante URI differenti), dato che WinRT gestisce ciascuna operazione separatamente.

Durante lo svolgimento delle operazioni, l'applicazione può ricevere eventi per aggiornare la user interface (nel caso in cui l'app sia ancora in foreground), ed è anche possibile interrompere, riprendere o cancellare l'operazione. Queste API supportano inoltre l'uso di credenziali, cookie e header HTTP, in modo da poter utilizzare molteplici meccanismi di sicurezza.

Tutte le operazioni sono gestire direttamente da WinRT, in modo da ottimizzare il consumo di risorse (in particolare delle batterie) e della banda.

Il seguente codice mostra un esempio di utilizzo della classe BackgroundDownloader per avviare il download di un filmato al click di un ipotetico pulsante, per poi salvarlo nella cartella Videos dell'utente:

app.onloaded = function () {
	document.getElementById("btnDownload").addEventListener("click", download_click);
}
var source = "http://www.contoso.com/videos";
var fileName = "samplevideo.vmw";
function download_click(args) {
	try {
		Windows.Storage.KnownFolders.videosLibrary.createFileAsync(fileName,
			Windows.Storage.CreationCollisionOption.generateUniqueName)
				.done(function (newFile) {
					var uri = Windows.Foundation.Uri(source);
						var downloader = new Windows.Networking.BackgroundTransfer.BackgroundDownloader();
					var downloadOp = downloader.createDownload(uri, newFile);
					downloadOp.startAsync().done(completeHandler, errorHandler);
				}, errorHandler);
	} catch (ex) {
		// gestire l'eccezione
	}
};
function completeHandler(args) {
	// notifica all'utente
}
function errorHandler(args) {
	// notifica all'utente
}

Prima di eseguire il codice, tuttavia è necessario modificare l'application manifest per aggiungere una o più delle seguenti "capability":

Capability Descrizione
Internet (Client) accesso in uscita a internet e reti in aree pubbliche (pub, negozi, aeroporti, stazioni)
Internet (Client & Server) l'app può effettuare e ricevere richieste in aree pubbliche
Private Networks l'app può effettare e ricevere richieste solo in reti private (home e work)

La prossima immagine mostra l'application manifest con le capability richieste per l'esecuzione in background di operazioni di download/upload. Da notare che è stata aggiunta anche la capability Videos Library, dato che l'app ha bisogno di accedere alla relativa cartella dell'utente per salvare il video scaricato da Internet.

Il codice sopra riportato è piuttosto semplice: una volta creata l'URI per la risorsa da scaricare e istanziato un oggetto di tipo BackgroundDownloader, il metodo CreateFileAsync crea un file nella libreria Videos dell'utente utilizzando il pattern asincrono basato su promise.

La classe BackgroundDownloader espone un metodo CreateDownload che permette di creare un'operazione di download asincrona, rappresentata da un oggetto di tipo DownloadOperation. Questo metodo accetta come parametri l'URI che identifica la risorsa e un oggetto di tipo IStorageFile che rappresenta il file che verrà usato per salvarla.

Per avviare l'operazione, è quindi sufficiente invocare il metodo StartAsync della classe BackgroundDownloader.

La classe BackgroundDownloader permette anche di specificare la politica da seguire in caso di reti con traffico dati a pagamento ("costed newtork"). Il seguente snippet ne mostra un esempio:

var downloader = new Windows.Networking.BackgroundTransfer.BackgroundDownloader();
downloader.costPolicy = Windows.Networking.BackgroundTransfer.BackgroundTransferCostPolicy.unrestrictedOnly;

In questo modo, nel caso di una connessione a una rete a pagamento eventuali operazioni di trasferimento verranno sospese automaticamente, per essere poi riprese non appena il device verrà connesso a una rete gratuita.

È anche possibile differenziare la politica dei costi da seguire per le single operazioni, sfruttando questa volta la proprietà CostPolicy esposta dalla classe DownloadOperation che, come si è detto, rappresenta una specifica operazione di trasferimento.

var downloadOp = downloader.createDownload(uri, newFile);
downloadOp.costPolicy = Windows.Networking.BackgroundTransfer.BackgroundTransferCostPolicy.always;

La classe DownloadOperation espone anche due metodi, Pause e Resume, che permettono, rispettivamente, di interrompere e riprendere l'operazione di trasferimento.

Per tenere traccia del progredire delle operazioni di trasferimento, è possibile sfruttare il metodo StartAsync, passando come parametro la callback che gestisce il progredire dell'operazione. Il seguente listato mostra una versione modificata del codice illustrato in precedenza.

function download_click(args) {
	try {
		// come sopra...
	} catch (ex) {
		// gestire l'eccezione
	}
};
function progressHandler(downloadOp) {
	var result = document.getElementById("result");
	result.innerHTML = "<p>Trasferiti " + downloadOp.progress.bytesReceived + " su " + downloadOp.progress.totalBytesToReceive + "</p>";
}

Come si può notare, la callback progressHandler riceve come parametro un oggetto di tipo DownloadOperation, la cui proprietà Progress (di tipo BackgroundDownloadProgress) permette di conoscere lo stato di avanzamento dell'operazione.

In particolare, la BackgroundDownloadProgress espone le seguenti proprietà:

Campo Descrizione
BytesReceived il numero di byte ricevuti (non sono computati gli header della risposta)
HasResponseChanged se true, indica che la risposta è cambiata a seguito di una nuova richiesta
HasRestarted se true, indica che l'operazione di trasferimento è stata interrotta e che il server non supporta il resume del download, per cui la nuova operazione di download dovrà ripartire dall'inizio
Status questa proprietà, di tipo BackgroundTransferStatus, indica lo stato corrente dell'operazione; può assumere uno dei seguenti valori dai nomi auto-esplicativi: Idle, Running, PausedByApplication, PausedCostedNetwork, PausedNoNetwork, Completed, Canceled, Error
TotalBytesToReceive il numero totale di byte da scaricare (se la dimensione è ignota, il valore della proprietà è impostato a zero)

Dal momento che WinRT può sospendere o terminare l'applicazione in qualunque momento, durante il lancio successivo dell'app da parte dell'utente, è importante re-impostare la callback che permette di monitorare lo stato del trasferimento.

Per sapere quali sono le operazioni di download pendenti in un certo momento, è possibile usare il metodo GetCurrentDownloadAsync esposto dalla classe BackgroundDownloader. Il prossimo snippet mostra questo punto.

Windows.Networking.BackgroundTransfer.BackgroundDownloader.getCurrentDownloadsAsync()
	.done(function (downloads) {
		if (downloads.length > 0) {
			downloads[0].attachAsync().then(completeHanlder, errorHandler, progressHanlder);
		}
	});

Il codice enumera tutte le operazioni di download correnti e re-imposta la callback per monitorare il progresso di ciascuna di esse tramite il metodo AttachAsync. Questo metodo permette a un'app di riprendere operazioni di download iniziate durante una precedente esecuzione della medesima app.

Un'altra cosa da considerare riguarda la politica di time-out seguita da WinRT. Quando viene creata una nuova richiesta di connessione, il tempo a disposizione per stabilire una connessione è di cinque minuti, trascorsi i quali la connessione viene abortita.

Dopo aver stabilito la connessione, ogni richiesta HTTP che non riceve una risposta nei due minuti successivi verrà a sua volta abortita.

Upload

Finora abbiamo visto il funzionamento della classe BackgroundDownloader, ma gli stessi concetti si applicano anche all'upload di un file tramite la classe BackgroundUploader. Anche questa classe è infatti pensata per file di grandi dimensioni, come immagini di alta qualità, video e musica (mentre per file di ridotte dimensioni è sempre possibile ricorrere alla tradizionale classe HttpClient).

Il prossimo snippet mostra un esempio di utilizzo della classe BackgroundUploader in cui sono evidenti le analogie con quanto visto per il download:

function upload_click(args) {
	try {
		Windows.Storage.KnownFolders.videosLibrary.getFileAsync(fileName)
			.done(function (newFile) {
				var uri = Windows.Foundation.Uri(source);
				var uploader = new Windows.Networking.BackgroundTransfer.BackgroundUploader;
				uploader.costPolicy = Windows.Networking.BackgroundTransfer.BackgroundTransferCostPolicy.unrestrictedOnly;
				var uploadOp = uploader.createUpload(uri, newFile);
				uploadOp.startAsync().done(completeHandler, errorHandler, progressHandler);
		}, errorHandler);
	} catch (ex) {
		// gestire l'eccezione
	}
};
function progressHandler(uploadOp) {
	var result = document.getElementById("result");
	result.innerHTML = "<p>Trasferiti " + uploadOp.progress.bytesSent + " su " + uploadOp.progress.totalBytesToSend + "</p>";
}

In alternativa, possiamo utilizzare il metodo CreateUploadFromStreamAsync, il quale restituisce, una volta completata l'operazione, un oggetto di tipo UploadOperation con l'URI indicata e lo stream. Il prossimo snippet mostra un possibile utilizzo di questa classe:

function upload_click(args) {
	try {
		Windows.Storage.KnownFolders.videosLibrary.getFileAsync(fileName)
		.then(function (newFile) {
			return newFile.openReadAsync();
		})
		.then(function (fileStream) {
			var uploader = new Windows.Networking.BackgroundTransfer.BackgroundUploader();
			uploader.costPolicy = Windows.Networking.BackgroundTransfer.BackgroundTransferCostPolicy.unrestrictedOnly;
			var uri = Windows.Foundation.Uri(source);
			return uploader.createUploadFromStreamAsync(uri, fileStream);
		})
		.then(function (uploadOp){
			uploadOp.startAsync()
		})
		.done(completeHandler, errorHandler, progressHandler);
	} catch (ex) {
		// gestire l'eccezione
	}
};

È anche possibile usare il metodo CreateUploadAsync per creare operazioni asincrone che, una volta completate, restituiscono un oggetto di tipo UploadOperation (per ulteriori approfondimenti su questo metodo si rinvia alla documentazione di MSDN).

Infine, entrambe le classi BackgroundDownloader e BackgroundUploader espongono le proprietà ProxyCredential, per l'autenticazione presso un proxy, e ServerCredential, per l'autenticazione presso un server. È anche possibile usare il metodo SetRequestHeader per specificare gli header HTTP.

Mantenere aperti i canali di comunicazione

Per applicazioni che richiedono di lavorare in background, come applicazioni Voice over Internet Protocol (VoIP), instant messaging (IM) ed email, WinRT mette a disposizione una esperienza utente di tipo always-connected. In pratica, un'app che necessita di una connessione "duratura" verso un server remoto è in grado di funzionare anche in caso di sospensione dell'app da parte di WinRT.

Mantenere un canale di comunicazione aperto è importante per quelle applicazioni che inviano o ricevono dati da endpoint remoti, o che devono attendere il completamento di lunghe operazioni lato server. In genere, questo tipo di applicazioni si trova dietro un proxy, un firewall o un device NAT. Questi componenti hardware mantengono aperta la connessione solo se i due endpoint continuano a scambiare dati, altrimenti la connessione viene chiusa.

Per evitare la chiusura della connessione tra il server e il client, l'applicazione può essere configurata per usare una connessione "keep-alive" (che prevede l'invio di messaggi a intervalli regolari, in modo da prolungare la durata della connessione). Questa strategia era di semplice implementazione nelle precedenti versioni di Windows, in quanto l'applicazione rimaneva in esecuzione fino a quando l'utente non decideva di chiuderla. Da Windows 8, al contrario, l'app può essere sospesa o addirittura terminata dal sistema, non appena in background, con la conseguente chiusura di ogni connessione eventualmente aperta.

Bisogna tener presente che se l'applicazione ha bisogno di ricevere notifiche (ad esempio per aggiornare un live tile o un badge, eseguire del codice in risposta a una notifica, mostrare le notifiche all'utente, ecc.), possiamo sfruttare il Windows Push Notification Service (WNS), un servizio cloud ospitato da Microsoft per le app Windows Store.

Il framework di WinJS attualmente non fornisce classi che ci permettano per mantenere una socket aperta con un server o endpoint remoto. Tuttavia può essere necessario ricorrere a connessioni persistenti nel caso in cui non sia possibile sfruttare il WNS (ad esempio, un'applicazione di mail che usa un server POP3 non può sfruttare il WNS, poiché il server non implementa questo servizio e non può inviare messaggi ai clienti POP3).

Il modo comunque c'è, ed è quello di implementare il codice per mantenere aperta la connessione in un componente Windows Runtime scritto in uno degli altri linguaggi come C#.

Con C# infatti possiamo creare un componente che sfrutti la classe ControlChannelTrigger del namespace Windows.Networking.Sockets, questa classe non è al momento supportata per applicazioni Windows Store in HTML5/JavaScript.

In questo articolo approfondiamo gli aspetti che trovano applicazione in una Windows Store app sviluppata in JavaScript, rinviando all'articolo analogo in XAML/C# per l'implementazione effettiva.

In particolare, perché sia possibile mantenere la connessione aperta, un'applicazione Windows Store ha bisogno di sfruttare il Lock screen (ricordiamo che ogni utente può avere fino a un massimo di 7 app contemporaneamente nel Lock screen). È dunque necessario aggiungere il logo per il badge (ed eventualmente per il tile) nell'application manifest e, successivamente, chiedere all'utente il permesso di aggiungere l'app al Lock screen (ricordati anche di gestire l'eventualità che l'app sia rimossa dal Lock screen).

La prossima immagine mostra l'application manifest modificato per includere la funzionalità di Lock screen.

Il passo successivo è rappresentato dalla richiesta all'utente del permesso di accedere al Lock screen. Per far questo, è necessario invocare il metodo RequestAccessAsync della classe Windows.ApplicationModel.Background.BackgroundExecutionManager. La chiamata a questo metodo mostra all'utente un dialog, tramite il quale l'utente può decidere di concedere o meno il permesso. È importante sottolineare come questo dialog venga mostrato soltanto la prima volta: questo vuol dire che, se l'utente nega il permesso, l'applicazione non potrà più chiedere all'utente di essere aggiunta al Lock screen nella sezione Personalize dei PC Settings.

Il codice che segue mostra come richiedere il permesso per il Lock screen:

var lockScreenEnabled = false;
function ClientInit() {
	if (lockScreenEnabled == false) {
		var background = Windows.ApplicationModel.Background;
		background.BackgroundExecutionManager.requestAccessAsync()
			.done(function (permission) {
				switch (permission) {
					case background.BackgroundAccessStatus.allowedWithAlwaysOnRealTimeConnectivity:
						lockScreenEnabled = true;
						break;
					case background.BackgroundAccessStatus.allowedMayUseActiveRealTimeConnectivity:
						lockScreenEnabled = true;
						break;
					case background.BackgroundAccessStatus.denied:
						break;
				}
			}, errorHandler);
	}
}

L'enum BackgroundAccessStatus ci consente di conoscere quale scelta ha compiuto l'utente. A seconda della risposta dell'utente e della tipologia di dispositivo, l'enum può assumere uno dei seguenti valori:

  • Unspecified
    l'utente non ha prestato né negato il suo permesso (ad esempio perché ha chiuso l'applicazione prima di effettuare una scelta). In questo caso, al successivo avvio l'app potrà mostrare nuovamente il dialog per richiedere il permesso.
  • AllowedWithAlwaysOnRealTimeConnectivity
    l'utente ha prestato il suo consenso all'aggiunta dell'app al Lock screen. L'app è dunque aggiunta al Lock screen, può usare background task e, se necessario, può usare il Real-Time Connectivity broker. Questo significa che l'app può funzionare anche se il device si trova in modalità connected standby (questo particolare stato, introdotto da Windows 8, presenta caratteristiche particolari, diversi dai tradizionali stati di "sleep" e "hibernate" già noti agli utenti delle versioni precedenti di Windows; per un approfondimento sul punto si rinvia alla documentazione MSDN).
  • AllowedMayUseActiveRealTimeConnectivity
    l'utente ha prestato il suo consenso all'aggiunta dell'app al Lock screen. L'app è dunque aggiunta al Lock screen e può usare background task; tuttavia non potrà usufruire del Real-Time Connectivity broker, con la conseguenza che l'app potrà non funzionare se il device si trova in modalità connected standby.

Dopo che l'app è stata aggiunta al Lock screen, dovrebbe essere visibile nella sezione Personalize dei PC Settings. Ricordati di gestire a livello applicativo il diniego e la revoca del permesso da parte dell'utente.

Una volta che l'app è stata aggiunta al Lock screen, i passaggi successivi sono i seguenti:

  1. Creare un control channel;
  2. Aprire una connessione;
  3. Associare la connessione al control channel;
  4. Connettere la socket al server o endpoint remoti.

Per quanto riguarda l'implementazione in concreto di questi passaggi, si rinvia all'articolo analogo in XAML/C# .

Come abbiamo visto in questi tre articoli in sequenza, ci sono molte possibilità di utilizzo di un background task: possiamo semplicemente eseguire qualche operazione locale in modo indipendente dall'applicazione, così come trasferire dati da e verso risorse remote.

Ti consigliamo anche