Microsoft Azure
Облачный бизнес
Вам понадобится

Microsoft Azure

Попробуйте платформу Microsoft Azure совершенно бесплатно.

Visual Studio

Бесплатная версия Visual Studio, позволяющая создавать приложения для платформы Microsoft Azure.

SDKs и дополнительные
инструменты

Инструменты разработки приложений для платформы Microsoft Azure.

Microsoft Azure Blob-storage: поддержка CORS

Недавно вышло много  обновлений Microsoft Azure. Среди них долгожданная поддержка Cross-Origin Resource Sharing для хранилищ. Я плотно использую в работе blob-storage (файловое хранилище) и в этом посте опишу как сделать загрузку файлов простой и приятной.

Чтобы начать работать с хранилищем (blob-storage) с помощью CORS, нужно решить следующие подзадачи:

  1. Создать хранилище
  2. Разрешить поддержку CORS
  3. Создать временный ключ для записи (Shared Access Signature)
  4. Написать загрузчик

 

Создание хранилища


Создать хранилище очень просто: нужно пойти в портал управления Azure и создать его.

После того, как хранилище будет создано нам потребуется его название, точка доступа и ключ доступа.

 




Теперь нужно сделать контейнер, его можно создать в портале или из кода.

CloudStorageAccount account = CloudStorageAccount.Parse("connectionString");
CloudBlobClient client = account.CreateCloudBlobClient();
CloudBlobContainer container = client.GetContainerReference("containername");
if (container.CreateIfNotExists())
{
    container.SetPermissions(new BlobContainerPermissions { PublicAccess = BlobContainerPublicAccessType.Blob });
}

Включение поддержки CORS для сервиса файлового хранилища

На каждый сервис хранилища (блоб, таблицы и очередь) можно создать до пяти CORS-правил. Для наших целей хватит и одного. У правила есть несколько свойств.

Разрешенные домены


Можно добавить *, чтобы разрешить доступ отовсюду. Для ограничения доступа с определенных доменов, необходимо их все добавить в список с указанием протокола и учетом регистра (т.е. если добавить только http://domain.com, то запрос c https://domain.com или http://Domain.com уже не пройдет).

Разрешенные заголовки


Если в запросе будут присутствовать заголовки не указанные в списке, он не пройдет. Для загрузки файлов нам потребуется два стандартных заголовка: accept и content-length, и специфичные для блобов: x-ms-blob-type, x-ms-blob-content-type и x-ms-blob-cache-control.

Разрешенные методы


Список методов, которые будут приниматься хранилищем. Для отправки файлов нужен метод PUT.

Срок жизни


Время, которое браузер должен кэшировать предварительный запрос (preflight request), из которого он узнает настройки CORS. Учитывая, что все успешные запросы к хранилищу тарифицируются, стоит его сделать достаточно большим.

Cors-правило


Если делать настройку из .net (все сказанное выше и ниже можно сделать с помощью REST-API) потребуется библиотека Microsoft.WindowsAzure.Storage третьей версии. Самый простой способ её добычи – nuget.

PM> Install-Package WindowsAzure.Storage
CorsRule corsRule = new CorsRule
{
    AllowedOrigins = new List<string>
    {
        "http://allowed.domain.com",
        "https://allowed.domain.com"
    },

    AllowedHeaders = new List<string>
    {
        "x-ms-blob-*",
        "content-type",
        "accept"
    },

    AllowedMethods = CorsHttpMethods.Put,

    MaxAgeInSeconds=60*60
};
//Подключение правила к сервису
ServiceProperties serviceProperties = new ServiceProperties
{
    //обязательно такая (или новее, когда появится) - только у неё появился CORS
    DefaultServiceVersion = "2013-08-15",

    //вообще, следующие свойства не обязательные, но без них NullReference exception
    //установим их в дефолты
    Logging = new LoggingProperties
    {
        Version = "1.0",
        LoggingOperations = LoggingOperations.None
    },
HourMetrics = new MetricsProperties
    {
        Version = "1.0",
        MetricsLevel = MetricsLevel.None
    },

    MinuteMetrics = new MetricsProperties
    {
        Version = "1.0",
        MetricsLevel = MetricsLevel.None
    }
};	
//добавим правило для корса
serviceProperties.Cors.CorsRules.Add(corsRule);
//установим правило сервису хранилища
client.SetServiceProperties(serviceProperties);

Получение временного ключа.


С этим все тоже просто: ключ – это строка передаваемая в параметрах адреса при обращении к хранилищу. Действует в пределах объекта, на который дано разрешение. В нашем случае это будет контейнер. Формируется следующим способом:

CloudBlobContainer container = client.GetContainerReference("containername");
string key=container.GetSharedAccessSignature(new SharedAccessBlobPolicy
{
    Permissions = SharedAccessBlobPermissions.Write,
    SharedAccessStartTime = DateTime.UtcNow.AddMinutes(-1),    //когда начнет действовать
    SharedAccessExpiryTime = DateTime.UtcNow.AddMinutes(60), //когда закончит действовать
})

Загрузчик


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

В Microsoft Azure имеются два типа блобов – блочные и страничные. Страничные оптимизированы для хранения потоковых данных (видео, аудио, т.п.) и режутся на страницы в 512 байт. Их максимальный размер – 1 Тб. В этом примере будут использоваться блочные блобы, с максимальным размером в 400Гб. Для загрузчика важно, что в одной операции загрузки он может передать не более 64Мб. Т.е. файл режется на блоки не превышающие 64Мб и финальной операцией блоки склеиваются. В примере файлы будут загружаться кусками по 512Кб.

Соответственно, алгоритм работы загрузчика будет следующим.

10 Прочитали кусок файла
20 Отправили кусок в хранилище
30 Если файл не кончился ГОТО 10
40 Склеить блоки в хранилище
//Логика загрузчика довольно простая, но, поскольку в нашем мире всё стало асинхронным, нужно быть внимательным.
//Чтение файлов будем осуществлять с помощью нового и удобного FileReader.
var reader=new FileReader();


//Создадим для него обработчик, который по прочтению блока будет отправлять его в хранилище:
reader.onloadend = function(e) {
  if (e.target.readyState == FileReader.DONE) { // DONE == 2
    //url формируется из адреса хранилища, имени контейнера, имени файла, ключа доступа и параметров запроса
    //скажем какой кусок файла загружаем 
    var uri = submitUri + '&comp=block&blockid=' + blockIds[blockIds.length - 1];
    var requestData = new Uint8Array(e.target.result);
    $.ajax({
        url: uri,
        type: "PUT",
        data: requestData,
        processData: false,
        //preflight request
        beforeSend: function (xhr) {
            xhr.setRequestHeader('x-ms-blob-type', 'BlockBlob');
            xhr.setRequestHeader('Content-Length', requestData.length);
        },
        success: function () {
            bytesUploaded += requestData.length;
            var percentage = ((parseFloat(bytesUploaded) / parseFloat(files[fileIndex].size)) * 100).toFixed(2);
            $('#progress_'+fileIndex).text('Загружено : ' + percentage + '%');
            processFile();
        },
        error: function () {
            $('#progress_' + fileIndex).text('Сбой загрузки');
        }
    });
}


//А так выглядит функция, собственно, чтения файла:
function processFile() {
    if (bytesRemain > 0) {
        //сделаем название чанка
        blockIds.push(btoa(blockId()));

        //читаем кусок файла
        var fileContent = files[fileIndex].slice(streamPointer, streamPointer + blockSize);
        reader.readAsArrayBuffer(fileContent);
        streamPointer += blockSize;
        bytesRemain -= blockSize;
        if (bytesRemain < blockSize) {
            blockSize = bytesRemain;
        }
    } else {
        //все прочитали, сказать стореджу склеить блоки
        commitBlocks();
    }
}


//Когда чтение файла завершено и все блоки отправлены, нужно сообщить об этом хранилищу, чтобы оно склеило блоки.
function commitBlocks() {
    //скажем какие блоки склевать
    var uri = submitUri + '&comp=blocklist';
    var requestBody = '<?xml version="1.0" encoding="utf-8"?><BlockList>';
    for (var i = 0; i < blockIds.length; i++) {
        requestBody += '<Latest>' + blockIds[i] + '</Latest>';
    }
    requestBody += '</BlockList>';
    $.ajax({
        url: uri,
        type: 'PUT',
        data: requestBody,
        beforeSend: function (xhr) {
        //установим mime-type для файла, необязательно
            xhr.setRequestHeader('x-ms-blob-content-type', files[fileIndex].type);
            //всё обращения тарифицируются, выставим кэш
            xhr.setRequestHeader('x-ms-blob-cache-control', 'max-age=31536000');
            xhr.setRequestHeader('Content-Length', requestBody.length);
        },
        success: function () {
            $('#progress_' + fileIndex).text('');
            ($('<a>').attr('href', storageurl + files[fileIndex].name).text(files[fileIndex].name)).appendTo($('#progress_' + fileIndex));
            fileIndex++;
            startUpload();
        },
        error: function () {
            $('#progress_' + fileIndex).text('Сбой загрузки');
        }
    });
}

На этом всё. Используя возможности CORS, теперь можно загружать файлы напрямую в хранилища, почти не привлекая свои сервера. И это хорошо: меньше делает сервер, меньше их надо, больше денег остается на всё остальное.

Исходники проекта:  https://github.com/unconnected4/CORSatWindowsAzureBlobStorage

Ссылки для чтения:


HTML5 File API: множественная загрузка файлов на сервер
Put Blob (REST-API) — важен списком заголовков
Microsoft Azure Storage and Cross-Origin Resource Sharing (CORS) – Lets Have Some Fun
Uploading Large Files in Microsoft Azure Blob Storage Using Shared Access Signature, HTML, and JavaScript

Автор статьи: Александр Кузнецов.

 

Данный материал написан участником сообщества. В статье представлено мнение автора, которое может не совпадать с мнением корпорации Microsoft. Microsoft не несет ответственности за проблемы в работе аппаратного или программного обеспечения, которые могли возникнуть после использования материалов данной статьи.