Сетевое взаимодействие
Фоновая передача данных
Одна из операций, которая касается пользовательских данных, заключается в передаче потенциально больших файлов в онлайновые хранилища и в загрузке этих данных оттуда. Даже для файлов умеренного размера, это являет собой некоторую проблему: немногие пользователи хотели бы смотреть на дисплей, наблюдая за процессом передачи файла, поэтому весьма вероятно, что они переключатся на другое приложение, чтобы заняться чем-нибудь поинтереснее пока завершится передача данных. При этом приложение, выполняющее передачу данных будет приостановлено и, возможно, даже остановлено. Это не сулит ничего хорошего при попытке выполнить подобные действия с использованием механизмов наподобие WinJS.xhr!
Одно из решений – предоставить для этой цели фоновую задачу, что было обычным пожеланием к ранним предварительным версиям Windows 8 до тех пор, пока API не было готово. Это означает запуск некоторого кода, выполняющего эту задачу. WinRT, в итоге, предоставляет специальный API для фоновой передачи данных, Windows.Networking.BackgroundTransfer (http://msdn.microsoft.com/library/windows/apps/windows.networking.backgroundtransfer.aspx), которое поддерживает до 500 запланированных передач данных во всей системе. Он предлагает встроенный механизм учета стоимости соединения и механизм, позволяющий ему устойчиво работать при изменениях в сетевом соединении, что освобождает приложения от необходимости беспокоиться обо всем этом самостоятельно. Передача данных продолжается, когда приложение приостанавливается и приостанавливается, когда приложение останавливается. Когда приложение снова начинает работу, оно может проверить состояние фоновых передач данных, которые оно ранее инициировало и предпринять, при необходимости, дальшейшие действия – обработать загруженные данные, оповестить пользователя об успешной отправке данных в своем интерфейсе, перечислить неоконченные передачи, перезапустить те из них, что были приостановлены или прерваны. С другой стороны, если пользователь явным образом закрывает приложение (с помощью жеста, сочетания клавиш Alt+ F4 или из Диспетчера задач), все запрошенные приложением сеансы передачи данных отменяются. Это так же справедливо при остановке отладки приложения в Visual Studio.
Вообще говоря, рекомендовано использовать API фоновой передачи данных всегда, когда вы ожидаете, что операция по передаче данных может превысить возможность пользователя по ожиданию ее окончания. Это, очевидно, зависит от скорости сетевого соединения и от того, предполагаете ли вы, что пользователь переключится на другое приложение в то время, как осуществляется передача данных в вашем приложении. Например, если вы начали передачу данных, но пользователь может продолжать работать (или развлекаться) в вашем приложении в то время, пока это происходит, использование WinJS.xhr c HTTP GET и POST/PUT вполне возможно, хотя вам все еще нужно отслеживать параметры стоимости соединения и обрабатывать изменения в подключении. Если, с другой стороны, пользователь ничего не может делать в вашем приложении до завершения передачи данных, вы можете решить использовать фоновую передачу, видимо, для любых данных, объем которых превышает 10 килобайт или какой-то другой размер, зависящий от текущей скорости сетевого соединения.
В любом случае, когда вы готовые использовать фоновую передачу данных в приложении, объекты BackgroundDownloader (http://msdn.microsoft.com/library/windows/apps/windows.networking.backgroundtransfer.backgrounddownloader.aspx) и BackgroundUploader (http://msdn.microsoft.com/library/windows/apps/windows.networking.backgroundtransfer.backgrounduploader.aspx) в пространстве имен Windows.Networking.BackgroundTransfer станут вашими верными друзьями. У обоих объектов есть методы и свойства, посредством которых вы можете перечислять отложенные передачи, выполнять общие настройки учетных данных, HTTP-заголовков запросов, методов передачи, политики тарификации (для лимитированных сетей), и групповые операции. Каждая отдельная операций, таким образом, представлена объектом DownloadOperation или UploadOperation, с помощью которых вы можете управлять операцией (приостанавливать, отменять ) и получать сведения о ее состоянии. Вместе с каждой операцией вы можете задать учетные данные, политику тарификации и так далее, переназначая общие настройки в классах BackgroundDownloader и BackgroundUploader.
Примечение. И для случая передачи данных, и при загрузке, запрос на подключение будет отменен, если новое соединение не было установлено в течение пяти минут. Далее, срок любых других HTTP-запросов, которые использованы при передаче, истекает через две минуты. Фоновая передача данных попытается повторить операцию, до трех раз, при наличии подключения.
Для того, чтобы увидеть основы этого API в действии, начнем с примера "Фоновая передача данных" (http://code.msdn.microsoft.com/windowsapps/Background-Transfer-Sample-d7833f61). Для того, чтобы запустить этот пример, сначала вам нужно настроить сервер на локальном хосте, а так же файл данных и целевую страницу загрузки. Убедитесь, что у вас установлен Internet Information Services, как описано в Главе 2. Затем, в командной строке администратора, перейдите к папке примера Server и выполните команду powershell –file serversetup.ps1. Эта команда произведет установку необходимых серверных файлов для примера на локальном хосте и позволит вам запускать дополнительные упражнения к этой лекции, которые находятся в дополнительных материалах к ней.
Основы загрузки данных
Сценарий 1 примера "Фоновая передача данных" , (js/downloadFile.js) позволяет загружать файл изображения с сервера, расположеного на локальном хосте, и сохранять это изображение в Библиотеке изображений. По умолчанию поле для ввода URI установлено на конкретное URI локального хоста и элемент управления не активен. Это так, потому что пример не производит проверку правильности ввода URI, что всегда следует выполнять в ваших приложениях. Если вы хотите ввести другие URI, просто удалите disabled="disabled" из элемента serverAddressField в html/downloadFile.html. Для того, чтобы увидеть загрузчик в действии, так же полезно подготовить какой-нибудь большой файл, передача которого займет некоторое время. В этом вам может помочь любимая поисковая машина, или вы можете скопировать какой-нибудь из своих файлов на сервер локального хоста.
Пользовательский интерфейс примера так же предоставляет удобные кнопки для начала, отмены, приостановки и возобновления асинхронной операции, обычные функции приложений, использующих фоновую передачу данных. В обработчике прогресса операции, который поддерживают операции передачи данных, пример показывает, как отобразить то, какая часть изображения передана. Так же вы можете запустить одновременно несколько сеансов передачи данных для того, чтобы посмотреть, как осуществляется одновременное управление ими.
Начало фоновой загрузки данных выглядит так. Сначала создают StorageFile для получения данных (хотя, это и не обязательно, как мы увидим дальше). Затем создают объект DownloadOperation (http://msdn.microsoft.com/library/windows/apps/windows.networking.backgroundtransfer.downloadoperation.aspx) для передачи данных, используя BackgroundDownloader.createDownload, в данный момент можно усатновить свойства объекта method, costPolicy, и group для переопределения параметров, заданных объектом BackgroundDownloader. Метод передачи данных (method) – это строка, которая идентифицирует тип используемой передачи данных (обычно GET для HTTP или RETR для FTP). К двум другим параметрам мы вернемся в разделах "Задание стоимостной политики" и "Группировка нескольких запросов"
Как только операция соответствующим образом сконфигурирована, последний шаг заключается в вызове ее метода startAsync с вашими обработчиками завершения, ошибки и прогресса операции :
// Асинхронно создает файл в папке изображений (требуется объявление возможностей). Windows.Storage.KnownFolders.picturesLibrary.createFileAsync(fileName, Windows.Storage.CreationCollisionOption.generateUniqueName) .done(function (newFile) { // Предполагается, что uriString это текстовое URI файла для загрузки var uri = Windows.Foundation.Uri(uriString); var downloader = new Windows.Networking.BackgroundTransfer.BackgroundDownloader(); // Создание новой операции загрузки. var download = downloader.createDownload(uri, newFile); // Запуск загрузки download.startAsync().then(complete, error, progress); }
Когда операция выполняется, следующие свойства предоставляют дополнительные сведения о передаче данных:
- requestedUri и resultFile Те же, которые были переданы createDownload.
- guid Уникальный идентификатор, присвоенный операции.
- progress Структура BackgroundDownloadProgress (http://msdn.microsoft.com/library/windows/apps/windows.networking.backgroundtransfer.backgrounddownloadprogress.aspx), содержащая следующие члены: bytesReceived, totalBytesToReceive, hasResponseChanged (логическое значение, смотрите сведения о методе getResponseInformation ниже), hasRestarted (логическое значение, устанавливается в true если загрузка была перезапущена), и status (значение типа BackgroundTransferStatus (http://msdn.microsoft.com/library/windows/apps/windows.networking.backgroundtransfer.backgroundtransferstatus.aspx): idle, running, pausedByApplication, pausedCostedNetwork, pausedNoNetwork, canceled, error, и completed).
Вот несколько методов DownloadOperation, которые так же можно использовать при передаче данных:
- pause и resume Управляют производимой загрузкой. Больше о них – в разделе "Приостановка, возобновление, перезапуск фоновых передач данных" ниже.
- getResponseInformation Возвращает объект ResponseInformation (http://msdn.microsoft.com/library/windows/apps/windows.networking.backgroundtransfer.responseinformation.aspx ) со свойствами headers (коллекция заголовков ответа сервера), actualUri, isResumable, и statusCode (с сервера). Повторяющиеся вызовые этого метода возвращают те же самые данные до тех пор, пока свойство hasResponseChanged не будет установлено в true.
- getResultStreamAt Возвращает IInputStream для загруженного содержимого, с тем, что уже загружено, или, когда операция завершена, со всеми загруженными данными.
В Сценарии 1 примера, функция прогресса – переданная promise-объекту, возвращенному startAsync, использует getResponseInformation и getResultStreamAt для показа частично загруженного изображения:
var currentProgress = download.progress; // ... // Получает заголовок ответа Content-Type. var contentType = download.getResponseInformation().headers.lookup("Content-Type"); // Проверяет, является ли поток изображением. if (contentType.indexOf("image/") === 0) { . // Получает поток, с позиции 0 imageStream = download.getResultStreamAt(0); // Конвертирует поток в тип WinRT var msStream = MSApp.createStreamFromInputStream(contentType, imageStream); var imageUrl = URL.createObjectURL(msStream); // Передает URL потока в тег HTML image. id("imageHolder").src = imageUrl; // Закрывает поток, когда изображение отображено. id("imageHolder").onload = function () { if (imageStream) { imageStream.close(); imageStream = null; } }; }
Все это работает, потому что API фоновой передачи данных сохраняет загруженные данные во временный файл и предоставляет поток для него, а функция наподобие URL.createObjectURL делает то же самое, что мы уже делали с ее помощью напрямую с объектом StorageFile. Как только объект DownloadOperation выйдет из области видимости и будет произведена сборка мусора, однако, данный временный файл будет удален.
Данный временный файл существует еще и потому, что, как отмечено выше, нет необходимости предоставлять объект StorageFile для размещения в нем загруженных данных. Таким образом, вы можете передать в качестве второго аргумента null в createDownload и работать с данными посредством DownloadOperation.getResultStreamAt. Это вполне подходит, если конечной целью не является отдельный файл.
Существуют и вариант createDownload, который принимает второй аргумент StorageFile , содержимого которого представляет собой тело запросов HTTP GET или FTP RETR, которое будет отправлено на URI сервера перед началом загрузке. Это подходит для некоторых веб-сайтов, которые требуют, чтобы вы, перед началом загрузки, заполнили форму.
Врезка: Где команда отмены?
Вы уже могли заметить, что ни у DownloadOperation ни у UploadOperation нет метода для отмены операции. Как же это сделать? Вы отменяете передачу, отменяя операцию startAsync — то есть, вызывая метод cancel promise-объекта, возвращенного startAsync. Это означает, что вам нужно пологаться на promise-объекты каждой вызванной вами операции передачи данных.
Основы отправки данных
Сценарий 2 примера "Фоновая передача данных" (js/uploadFile.js) использует возможность фоновой отправки данных, в частности, он отправляет некоторые файлы (выбранные с помощью средства выбора файлов) по URI, который может их принять. По умолчанию URI указывает на http://localhost/BackgroundTransferSample/upload.aspx, на страницу, установленная с помощью скрипта PowerShell, который использовался для настройки сервера. Как и в случае со Сценарием 1, элемент управления для ввода URI заблокирован, так как пример не проверяет его, как, снова повторю, всегда нужно делать в приложениях, которые принимают любые URI из недоверенных источников (от пользователя, в данном случае). Для тестовых целей, конечно, вы можете убрать disabled="disabled" из элемента serverAddressField element вhtml/uploadFile.html и ввести другие URI, которые позволят испытать ваши собственные сервисы по приему отправленных файлов. Это особенно удобно, если вы исполняете серверную часть примера в Visual Studio 2012 Express для Web, когда URI нуждается в номере порта локального хоста, присвоенного отладчиком.
В дополнении к кнопкам для начала отправки и отмены, пример предоставляет еще одну кнопку, для начала составной, состоящей из нескольких частей, операции отправки данных. Больше об этом – в разделе "Составная отправка данных" ниже.
В коде отправка выглядит очень похожей на загрузку. Предполагая, что у вас имеется StorageFile с содержимым для отправки, создайте объект UploadOperation ( http://msdn.microsoft.com/library/windows/apps/windows.networking.backgroundtransfer.uploadoperation.aspx) для передачи данных с помощью BackgroundUploader.createUpload. Если с другой стороны, у вас есть данные в потоке (IInputStream), вместо этого создайте объект с помощью BackgroundUploader.createUploadFromStreamAsync ( http://msdn.microsoft.com/library/windows/apps/windows.networking.backgroundtransfer.backgrounduploader.createuploadfromstreamasync.aspx). Это так же можно использовать для того, чтобы разбить большие файлы на отдельные части, если сервер может их принять. Смотрите "Разбивка больших файлов" ниже.
Когда есть объект операции, можно настроить некоторые параметры передачи, переопределив свойства по умолчанию, заданные BackgroundUploader. Среди них – method (HTTP POST или PUT, или FTP STOR), costPolicy, и group. По поводу последних, смотрите разделы "Задание стоимостной политики" и "Группировка нескольких запросов" ниже.
Как только все готово, вызов метода операции startAsync приведет к началу отправки данных :
// Предполагается, что uri это объект Windows.Foundation.Uri и file это StorageFile для отправки var uploader = new Windows.Networking.BackgroundTransfer.BackgroundUploader(); var upload = uploader.createUpload(uri, file); promise = upload.startAsync().then(complete, error, progress);
Когда операция выполняется, следующие свойства предоставляют дополнительные сведения о передаче данных:
- requestedUri и sourceFile То же самое, что передано createUpload (операция, созданная с помощью createUploadFromStreamAsync поддерживает лишь requestedUri).
- guid Уникальный идентификатор, присвоенный операции.
progress Структура BackgroundUploadProgress (http://msdn.microsoft.com/library/windows/apps/windows.networking.backgroundtransfer.backgrounduploadprogress.aspx) со следующими членами: bytesReceived, totalBytesToReceive, bytesSent, totalBytesToSend, hasResponseChanged (логическое значение, смотрите описание метода getResponseInformation ниже), hasRestarted (логическое значение, установленное в true, если операция была перезапущена), и status (значение типа BackgroundTransferStatus (http://msdn.microsoft.com/library/windows/apps/windows.networking.backgroundtransfer.backgroundtransferstatus.aspx), с возможными значениями idle, running, pausedByApplication, pausedCostedNetwork, pausedNoNetwork, canceled, error, и completed).
В отличие от операции загрузки, UploadOperation не имеет методов для приостановки или возобновления, но имеет те же методы getResponseInformation и getResultStreamAt. В случае с отправкой данных, ответ от сервера менее интересен, так как он не содержит переданых данных, лишь заголовки, сведения о состояниях и содержимое, которое возвращает страница, осуществляющая прием передаваемых данных. Если данная страница возвращает какой-нибудь интересный HTML-код, однако, вы можете использовать эти данные как часть справочных данных, выводимых приложением при отправке данных.
Как было упомянуто ранее, для отмены операции UploadOperation, вызовите метод cancel promise-объекта, возвращенного startAsync.