Опубликован: 15.05.2013 | Доступ: свободный | Студентов: 265 / 9 | Длительность: 24:25:00
Специальности: Системный архитектор
Лекция 7:

Сетевое взаимодействие

< Лекция 6 || Лекция 7: 12345 || Лекция 8 >
Разбивка больших файлов

Так как скорости передачи исходящих данных (отправки) на большинстве широкополосных соединений гораздо медленнее, чем скорости входящих данных (загрузки) и могут иметь другие ограничение, отправка большого файла на сервер обычно более рискованное предприятие, чем загрузка большого файла. Если в ходе отправки происходит ошибка, она может сделать недействительной всю операцию отправки – весьма неприятное событие, если вы уже час ждете завершения загрузки!

По этой причине облачные сервисы могут позволять разбиение больших файлов на части для передачи, каждая из которых отправляет в виде отдельного HTTP-запроса, а сервер восстанавливает файл из этих данных. Это минимизирует, или, по крайней мере, уменьшает, общее воздействие проблем с подключением.

С точки зрения клиента, каждая часть передается в отдельной UploadOperation; это более, чем очевидно. Сложная часть этой задачи заключается в разбиении большого файла на клиенте. Вся эта непростая задача, весьма вероятно, может разрешиться в виде сложной цепочки вложенных асинхронных операций, возможно, с созданием множества временных файлов из единственного источника. Если вы заняты этим вопросом, я предлагая вам написать программу для этого и куда-нибудь ее отправить, чтобы все мы могли это увидеть!

Но есть и более простой подход, с использованием createUploadFromStreamAsync, посредством которого вы можете создать различные объекты UploadOperation для разных сегментов потока. Задав в качестве источника StorageFile, начните с вызова его метода openReadAsync, результатом чего будет объект IrandomAccessStreamWithContentType (http://msdn.microsoft.com/library/windows/apps/windows.storage.streams.irandomaccessstreamwithcontenttype.aspx). С помощью его метода getInputStreamAt затем вы IInputStream для каждой из начальных точек в потоке (то есть, задавая смещения в зависимости от размеров фрагмента). Затем вы создаете UploadOperation с каждым из входных потоков, используя createUploadFromStreamAsync. Последнее условие – указать, что операция обслуживает лишь некоторую часть этого потока. Это делается с помощью вызова ее метода setRequestHeader("content-length", <length> ), где <length> - это размер сегмента, плюс – размер других данных в запросе; так же вам понадобится доавить заголовок для идентификации сегмента для данной операции отправки информации. После всего этого можно вызывать методы startAsync операций для начала передачи данных.

Составная отправка данных

В дополнение к методам createUpload и createUploadFromStreamAsync, объект BackgroundUploader предоставляет другой метод, который называется createUploadAsync (в трех вариантах). Он обрабатывает то, что называется составной отправкой данных (multipart upload).

С точки зрения сервера, составная отправка данных – это один HTTP-запрос, который содержит разные фрагменты данных (части), такие. как идентификаторы приложений, маркеры авторизации, и так далее, вместе с содержимым, где каждая часть может быть отделена с помощью специальной строки. Подобная отправка используется сетевыми сервисами, наподобие Flickr и YouTube, каждый из которых принимает запросы с multipart Content-Type. (Смотрите материал "Content-type: multipart" (http://msdn.microsoft.com/library/ms527355.aspx)). Например, как показано в примере "Отправка фото – POST" ( http://www.flickr.com/services/api/upload.example.html), Flickr ожидает запросов с типом содержимого multipart/form-data, с частями api_key, auth_token, api_sig, photo, и, наконец, с содержимым файла. В случае с YouTube, как описано в "YouTube API v2.0 – Прямая отправка данных" (https:xmlns//developers.google.com/youtube/2.0/developers_guide_protocol_direct_uploading), сервис ожидает типа содержимого multipart/related с частями, которые содержат XML-данные запроса, тип видео, и, затем двоичный файл данных.

Средство фоновой отправки данных поддерживает все это посредством метода BackgroundUploader.createUploadAsync. (обратите внимание на то, что суффикс "Async" отличает его от синхронного createUpload.). Существует три варианта этого метода. Первый принимает URI сервера, принимающий отправляемые данные и массив объектов BackgroundTransferContentPart (http://msdn.microsoft.com/library/windows/apps/windows.networking.backgroundtransfer.backgroundtransfercontentpart.aspx), каждый из которых представляет одну из частей отправляемых данных. Результирующая операция отправит запрос с типом содержимого multipart/form-data со случайным GUID для строк-разделителей. Второй вариант createUploadAsync позволяет задавать тип содержимого напрямую (посредством подтипов, таких, как related), и третий вариант добавляет разделительную строку.

То есть, учитывая, что parts - это как массив частей, методы выглядят так:

var uploadOpPromise1 = uploader.createUploadAsync(uri, parts);
var uploadOpPromise2 = uploader.createUploadAsync(uri, parts, "related");
var uploadOpPromise3 = uploader.createUploadAsync(uri, parts, "form-data", "-------123456");
        

Для создания каждой из частей, сначала создайте BackgroundTransferContentPart с использованием одного из трех его конструкторов (http://msdn.microsoft.com/library/windows/apps/windows.networking.backgroundtransfer.backgroundtransfercontentpart.backgroundtransfercontentpart.asp):

  • new BackgroundContentPart() Создает часть по умолчанию.
  • new BackgroundContentPart(<name> ) Создает часть с заданным именем.
  • new BackgroundContentPart(<name> , <file>) Создает часть с заданным именем и локальным именем файла.

В каждом случае выполняют дальнейшую инициализацию частей, вызывая их методы setText, setHeader, и setFile. Первый, setText, присваивает части значение. Второй, setHeader, можно вызывать несколько раз для предоставления части значений заголовков. Третий, setFile, это способ предоставления StorageFile части, созданной с помощью третьего из вышеописанных вариантов.

Сценарий 2 исходного примера "Фоновая передача данных" показывает последний вариант с использованием массива из выбранных файлов, но, возможно, немногие сервисы смогут принять подобный запрос. Вместо этого давайте посмотрим, как можно создать составной запрос на отправку данных, на примере "Отправка фото – POST" (http://www.flickr.com/services/api/upload.example.html). Для этой цели я создал упражнение "Multipart Upload", которое можно найти в дополнительных материалах к этой лекции. Вот код из js/uploadMultipart.js, который создает все необходимые части с использованием файла tinyimage.jpg из пакета приложения:

// К этому моменту переменные file и uri уже установлены. bt – это короткое имя для пространства имен
var bt = Windows.Networking.BackgroundTransfer;
var uploader = new bt.BackgroundUploader();
var contentParts = [];

// Вместо отправки нескольких файлов (как в исходном примере), мы создаем эти части, которые
// совпадают с примером POST дляFlickr on http://www.flickr.com/services/api/upload.example.html
var part;

part = new bt.BackgroundTransferContentPart();
part.setHeader("Content-Disposition", "form-data; name=\"api_key\"");
part.setText("3632623532453245");
contentParts.push(part);

part = new bt.BackgroundTransferContentPart();
part.setHeader("Content-Disposition", "form-data; name=\"auth_token\"");
part.setText("436436545");
contentParts.push(part);

part = new bt.BackgroundTransferContentPart();
part.setHeader("Content-Disposition", "form-data; name=\"api_sig\"");
part.setText("43732850932746573245");
contentParts.push(part);

part = new bt.BackgroundTransferContentPart();
part.setHeader("Content-Disposition", "form-data; name=\"photo\"; filename=\"" + file.name + "\"");
part.setHeader("Content-Type", "image/jpeg");
part.setFile(file);
contentParts.push(part);

// Создаем новую операцию отправки, задаем разделительную строку.
uploader.createUploadAsync(uri, contentParts,
"form-data", "-----------------------------7d44e178b0434")
.then(function (uploadOperation) {
// Start the upload and persist the promise
upload = uploadOperation;
promise = uploadOperation.startAsync().then(complete, error, progress);
}
);
        

Итоговый запрос будет выглядеть так, как показано ниже, что очень похоже на то, что показано на странице Flickr (лишь с несколькими дополнительными заголовками):

POST /website/multipartupload.aspx HTTP/1.1
Cache-Control=no-cache Connection=Keep-Alive Content-Length=1328
Content-Type=multipart/form-data; boundary="-----------------------------7d44e178b0434" Accept=*/*
Accept-Encoding=gzip, deflate
Host=localhost:60355
User-Agent=Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0; Touch) UA-CPU=AMD64
-------------------------------7d44e178b0434
Content-Disposition: form-data; name="api_key"

3632623532453245
-------------------------------7d44e178b0434
Content-Disposition: form-data; name="auth_token"

436436545
-------------------------------7d44e178b0434
Content-Disposition: form-data; name="api_sig"

43732850932746573245
-------------------------------7d44e178b0434
Content-Disposition: form-data; name="photo"; filename="tinysquare.jpg" Content-Type: image/jpeg

{RAW JFIF DATA}
-------------------------------7d44e178b0434--
        

Для того, чтобы запустить пример и увидеть, как принимается этот запрос, перейдите в папку MultipartUploadServer в дополнительных материалах к этой лекции. Загрузите website.sln в Visual Studio 2012 Express для Web, откройте MultipartUploadServer.aspx, и установите точку останова на первое выражение if внутри метода Page_Load. Затем запустите сайт в Internet Explorer для открытия данной страницы с помощью отладочного порта локального хоста. Скопируйте URI страницы для выполнения следующего шага.

В упражнении "Multipart Upload" вставьте данный URI в поле URI и нажмите на кнопку Start Multipart Transfer (Начать составную передачу данных). Когда будет вызван метод startAsync операции, должна сработать точка останова серверной страницы в Visual Studio для Web. Вы можете пошагово исполнить данный код, если нужно, и изучить объект Request; в конце код скопирует запрос в файл на сервере, который называется multipart-request.txt. Он будет включать в себя содержимое запроса, как показано выше, где вы можете увидеть взаимосвязь между тем, как вы настраиваете части на клиенте и как они принимаются сервером.

Предоставление заголовков и учетных данных

При работе с объектами BackgroundDownloader и BackgroundUploader есть возможность устанавливать значения для отдельных HTTP-заголовков, используя их методы setRequestHeader. Оба принимают имя заголовка и значение, и их вызывают несколько раз, если нужно установить больше одного заголовка.

Похожим образом, и тот и другой объекты имеют два свойства для учетных данных: serverCredential и proxyCredential, которые используются в зависимости от нужд URI сервера. Оба свойства это объекты Windows.Security.Credentials.PasswordCredential (http://msdn.microsoft.com/library/windows/apps/windows.security.credentials.passwordcredential.aspx). Так как цель операции фоновой передачи данных – предоставление учетных данных для сервера, обычно PasswordCredential создают так:

var cred = new Windows.Security.Credentials.PasswordCredential
          (resource, userName, password);

В данном случае resource это обычная строка, которая идентифицирует ресурс, к которому применяются учетные данные. Это используется для управления учетными данными в хранилище учетных данных, как мы увидим в разделе "Проверка подлинности, учетные данные и профиль пользователя" ниже. Сейчас создание учетных данных таким способом – это все, что нужно для того, чтобы пройти проверку подлинности сервером при выполнении передачи данных.

Примечание В настоящий момент установка свойства serverCredential не работает с URI, которые описывают FTP-сервер. Чтобы решить эту проблему, включайте учетные данные прямо в URI, в форме ftp://<user> : <password>@server.com/file.ext (например, ftp://admin:password1@server.com/file.bin).

Задание стоимостной политики

Как упомянуто выше, в разделе "Сведения о стоимости передачи данных", политика Магазина Windows требует, чтобы приложения осторожно относились к передаче больших объемов данных в лимитированных сетях. API фоновой передачи данных учитывает это, основываясь на значениях из перечисления BackgroundTransferCostPolicy:

  • default Разрешает передачу данных в сетях с оплатой.
  • unrestrictedOnly Не разрешает передачу данных в сетях с оплатой.
  • always Всегда разрешает загрузку вне зависимости от стоимости передачи данных.

Для того, чтобы применить правила к последующим передачам данных, установите значение BackgroundDownloader.costPolicy и/или BackgroundUploader.costPolicy. Правила для отдельной операции можно установить с помощью свойств DownloadOperation.costPolicy и UploadOperation.costPolicy.

Обычно политику изменяют после того, как запрашивают согласие пользователя, или позволяют пользователю задать поведение программы посредством параметров. Например, если у пользователь запретил загрузку или отправку данных в лимитированной сети, приложению следует устанавливать общий параметр costPolicy в unrestrictedOnly. Если устройство подключено к лимитированной сети, где применяется плата за роуминт и пользователь разрешил передачу данных, параметр costPolicy каждой отдельной операции устанавливается в always. В противном случае API не выполнит передачу данных, так как по умолчанию в лимитированных сетях это не разрешено.

Когда передача данных заблокирована политикой, свойство операции progress.status будет содержать BackgroundTransferStatus.pausedCostedNetwork.

Группировка нескольких запросов

Свойство group, которое есть у объектов BackgroundDownloader, BackgroundUploader, DownloadOperation, и UploadOperation это простая строка, которая указывает на то, что операция передачи данных принадлежит некоторой группе. Свойство можно установить только посредством BackgroundDownloader и BackgroundUploader. Его задают до создания последовательностей отдельных операций. В этих операциях свойство group доступно, но только для чтения.

Цель группировки заключается в том, что с ее помощью можно выборочно перечислять связанные запросы и управлять ими, как мы увидим в следующем разделе. Например, приложение для работы с фотографиями, которое организует фотографии в альбомы или в альбомные страницы, может предоставить интерфейс, с помощью которого пользователь может приостановить, возобновить или отменить передачу целого альбома, вместо того, чтобы работать с отдельными файлами. Свойство group упрощает реализацию подобного механизма , так как приложению не нужно поддерживать собственные структуры для группировки.

Свойство group не имеет отношения к самой передаче, она не связана с серверной страницей, которая принимает данные.

Приостановка, возобновление, перезапуск фоновых передач данных

В начале этого раздела я упоминал, что фоновая передача данных продолжается, когда приложение приостановлено, и приостанавливается, когда система останавливает приложение. Так как работа приложений завершается лишь в условиях нехватки памяти, приостановка фоновой передачи данных так же имеет смысл.

Когда приложение восстанавливается из состояния приостановки, оно может проверить состояние неоконченных передач данных с помощью использования методов BackgroundDownloader.getCurrentDownloadsAsync и BackgroundUploader.getCurrentUploadsAsync. В обоих случаях существуют два варианта этих методов: один, который перечисляет все передачи данных, и другой, который перечисляет лишь те операции, которые относятся к определенной группе (к той, которая совпадает со свойством group операций).

Список, который возвращают эти методы, это вектор объектов DownloadOperation и UploadOperation, и, как всегда, к вектору можно обращаться как к массиву. Вот как может выглядеть код для обхода списка:

Windows.Networking.BackgroundTransfer.BackgroundDownloader.getCurrentDownloadsAsync()
.done(function (downloads) {
for (var i = 0; i<downloads.size; i++) {	
var download = downloads[i];	
}	
});	

Windows.Networking.BackgroundTransfer.BackgroundUploader.getCurrentUploadsAsync()
.done(function (uploads) {	
for (var i = 0; i<uploads.size; i++) {	
var upload = uploads[i];	
}	
});
        

В каждом случае, свойство progress каждой операции сообщает о том, насколько продвинулась операция передачи данных. Свойство progress.status особенно важно. Повторюсь, status это значение типа BackgroundTransferStatus (http://msdn.microsoft.com/library/windows/apps/windows.networking.backgroundtransfer.backgroundtransferstatus.aspx), которое может принимать одно из следующих значений: idle, running, pausedByApplication, pausedCostedNetwork, pausedNoNetwork, canceled, error, и completed. Очевидно, это необходимо для предоставления соответствующих оповещений пользователю и для того, чтобы дать ему возможность повторно запустить операция, которая приостановлена или завершилась с ошибкой, для приостановки выполняющейся операции и для того, чтобы выполнить что-либо при завершении передачи данных.

Кстати, при использовании API фоновой передачи данных, приложению всегда следует давать пользователю возможность управления начатыми операциями. Загрузку можно приостановить с помощью метода DownloadOperation.pause и снова начать с помощью метода DownloadOperation.resume. (Для отправки данных эквивалентов нет.) Операции загрузки и отправки отменяют, отменяя promise-вызовы, которые возвращены startAsync.

Возникает интересная ситуация: если ваше приложение закрыто и позже запущено снова, как заново снова запустить операцию передачи данных, которая была приостановлена? Ответ довольно прост. При перечислении операций с помощью getCurrentDownloadsAsync и getCurrentUploadsAsync, незавершенные передачи данных автоматически запускаются. Но как получить promise-объекты, которые изначально были возвращены методами startAsync? Они не являются значениями, которые можно сохранить в составе состояния сеанса приложения и загрузить при запуске, и, все же, они нужны для того, чтобы можно было отменить операции, если нужно, и так же для того, чтобы присоединить к ним обработчики завершения, ошибок и прогресса.

По этой причине и DownloadOperation и UploadOperation предоставляют метод, который называется attachAsync, который возвращает promise-объект для операции, так же, как изначально это делает startAsync. Затем вы можете вызвать методы promise-объекта then или done для предоставления собственных обработчиков:

  promise = download.attachAsync().then(complete, error, progress);

А так же, если нужно, вызвать promise.cancel(). Короче говоря, когда Windows снова запускает фоновую передачу данных и обычным образом вызывает startAsync от имени вашего приложения, эти promise-объекты хранятся внутри. Методы attachAsync просто возвращают эти новые promise-объекты.

Последний вопрос заключается в том, может ли приостановленное приложение быть оповещено о том, что передача данных завершена, возможно, для того, чтобы отправить всплывающее уведомление и сообщить об этом пользователе. Подобная возможность в Windows 8 не поддерживается, так как нет фоновой задачи, подходящей для этой цели. В настоящее время, таким образом, пользователь должен снова переключиться к приложению для того, чтобы проверить состояние операции передачи данных.

< Лекция 6 || Лекция 7: 12345 || Лекция 8 >