Storages3 0.6.0

dotnet add package Storages3 --version 0.6.0
NuGet\Install-Package Storages3 -Version 0.6.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Storages3" Version="0.6.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Storages3 --version 0.6.0
#r "nuget: Storages3, 0.6.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Storages3 as a Cake Addin
#addin nuget:?package=Storages3&version=0.6.0

// Install Storages3 as a Cake Tool
#tool nuget:?package=Storages3&version=0.6.0

.NET Core NuGet NuGet codecov CodeFactor

Клиент для S3

Привет! Это обертка над HttpClient для работы с S3 хранилищами. Мотивация создания была простейшей - я не понимал, почему клиенты AWS и Minio едят так много памяти . Результат моих экспериментов: скорость почти как у Minio, а памяти потребляю почти в 200 раз меньше, чем клиент для AWS. На Windows. На Alpine и Debian (если запустить бенчмарк в контейнере) результаты сильно скромнее - в 7 раз меньше памяти, чем клиент на AWS. Интересно, кстати, почему.

BenchmarkDotNet = v0.13.5, OS=Windows 11 (10.0.22621.1265/22H2/2022Update/SunValley2)
AMD Ryzen 7 5800H with Radeon Graphics, 1 CPU, 16 logical and 8 physical cores
.NET SDK = 7.0.102
           [Host]   : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2 DEBUG
           .NET 7.0 : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2

Job = .NET 7.0  Runtime=.NET 7.0 
Method Mean Ratio Gen0 Gen1 Allocated Alloc Ratio
Aws 2.173 s 1.73 25000.0000 8000.0000 207 341.8 KB 252.99
Minio 1.365 s 1.08 - - 279 989.3 KB 341.64
Storage 1.282 s 1.00 - - 819.5 KB 1.00

Создание клиента

Для работы с хранилищем необходимо создать клиент.

var storageClient = new StorageClient(new StorageSettings
{
    AccessKey = "ROOTUSER",
    Bucket = "mybucket",
    EndPoint = "localhost",     // для Yandex.Objects это "storage.yandexcloud.net" 
    Port = 9000,                // стандартный порт Minio - 9000, для Yandex.Objects указывать не нужно
    SecretKey = "ChangeMe123",
    UseHttps = false,           // для Yandex.Objects укажите true
    UseHttp2 = false            // Yandex.Objects позволяет работать по HTTP2, можете указать true
})

Minio предоставляет playground для тестирования (порт для запросов всё тот же - 9000). Ключи можно найти в документации. Доступ к Amazon S3 не тестировался.

Операции с S3 bucket

Создание bucket'a

Мы передаём название bucket'a в настройках, поэтому дополнительно его вводить не надо.

bool bucketCreateResult = await storageClient.CreateBucket(cancellationToken);
Console.WriteLine(bucketCreateResult 
    ? "Bucket создан"
    : $"Bucket не был создан");

Проверка существования bucket'a

Как и в прошлый раз, мы знаем название bucket'a, так как мы передаём его в настройках клиента.

bool bucketCheckResult = await storageClient.BucketExists(cancellationToken);
if (bucketCheckResult) Console.WriteLine("Bucket существует");

Удаление bucket'a

bool bucketDeleteResult = await storageClient.DeleteBucket(cancellationToken);
if (bucketDeleteResult) Console.WriteLine("Bucket удалён");

Операции с S3 object

Напомню, что объект в смысле S3 это и есть файл.

Создание файла

Создание, то есть загрузка файла в S3 хранилище, возможна двумя путями: можно разбить исходные данных на кусочки ( multipart), а можно не разбивать. Самый простой способ загрузки файла - воспользоваться следующим методом (если файл будет больше 5 МБ, то применяется multipart):

bool fileUploadResult = await storageClient.UploadFile(fileName, fileStream, fileContentType, cancellationToken);
if (fileUploadResult) Console.WriteLine("Файл загружен");
Создание без Multipart

Можно принудительно загружать файл без multipart. Есть сигнатура и для byte[].

bool fileUploadResult = await storageClient.PutFile(fileName, byteArray, fileContentType, cancellationToken);
if (fileUploadResult) Console.WriteLine("Файл загружен");
Создание с использованием Multipart

Можно принудительно загружать файл с использованием multipart. В этом случае нужно будет явно указать размер одного кусочка (не менее 5 МБ).

bool fileUploadResult = await storageClient.PutFileMultipart(fileName, fileStream, fileContentType, partSize, cancellationToken);
if (fileUploadResult) Console.WriteLine("Файл загружен");
Управление Multipart-загрузкой

Для самостоятельного управления multipart-загрузкой, можно использовать методы клиента, начинающиеся со слова Multipart.

Stream fileStream = ...
// получаем идентификатор загрузки
string uploadId = await storageClient.Multipart(fileName, fileType, cancellationToken);
while(fileStream.Position < fileStream.Position) {
    // создаём свою логику разделения на данных на куски (parts)...
    
    string eTag = await MultipartUpload(fileName, uploadId, partNumber, partData, partSize, cancellation);
    
    // запоминаем 'eTag' и номер куска... 
    
    if (string.IsNullOrEmpty(eTag)) { // отменяем всю загрузку, если кусок загрузить не удалось
        await MultipartAbort(fileName, uploadId, cancellation); 
        return false;
    }
}

// сообщаем хранилищу, что загрузка завершена
await MultipartComplete(fileName, uploadId, tags, cancellation);

В коде клиента именно эту логику использует метод PutFileMultipart. Конкретную реализацию можно подсмотреть в нём.

Получение файла

StorageFile fileGetResult = await storageClient.GetFile(fileName, cancellationToken);
if (fileGetResult) {
    Console.WriteLine($"Размер файла {fileGetResult.Length}, контент {fileGetResult.ContetType}");
    return await fileGetResult.GetStream(cancellationToken);
}
else {
    Console.WriteLine($"Файл не может быть загружен, так как {fileGetResult}");
}

Проверка существования файла

bool fileExistsResult = await storageClient.FileExists(fileName, cancellationToken);
if (fileExistsResult) Console.WriteLine("Файл существует");

Создание подписанной ссылки на файл

Метод проверяет наличие файла в хранилище S3 и формирует GET запрос файла. Параметр expiration должен содержать время валидности ссылки начиная с даты формирования ссылки.

string? preSignedFileUrl = storageClient.GetFileUrl(fileName, expiration);
if (preSignedFileUrl != null) Console.WriteLine($"URL получен: {preSignedFileUrl}");

Существует не безопасный способ создать ссылку, без проверки наличия файла в S3.

string preSignedFileUrl = await storageClient.BuildFileUrl(fileName, expiration, cancellationToken);

Удаление

Удаление объекта из S3 происходит почти мгновенно. На самом деле в S3 хранилище просто ставится задача на удаление и клиенту возвращается результат. Кстати, если удалить файл, который не существует, то ответ будет такой же, как если бы файл существовал. Поэтому этот метод ничего не возвращает.

await storageClient.DeleteFile(fileName, cancellationToken);
Console.WriteLine("Файл удалён, если он, конечно, существовал");

Измерение производительности и тестирование

Локальное измерение производительности и тестирование осуществляется с помощью Minio в Docker'e по http. Понимаю, что это не самый хороший способ, но зато он самый доступный и простой.

  1. Файл docker-compose для локального тестирования можно найти в репозитории.
  2. Запускаем docker-compose up -d. Если всё хорошо, то бенчмарк заработает в Docker'e.
  3. Если нужно запустить бенчмарк локально, то обращаем внимание на файл appsettings.json. В нём содержатся основные настройки для подключения к Minio.
  4. Свойство BigFilePath файла appsettings.json сейчас не заполнено. Его можно использвоать для загрузки реального файла (больше 100МБ). Если свойство не заполнено, то тест сгенерирует случайную последовательность байт размером 123МБ в памяти.
Product Compatible and additional computed target framework versions.
.NET net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net7.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.6.0 636 3/24/2023
0.5.9 604 3/12/2023
0.5.8 605 3/9/2023
0.5.7 556 3/9/2023
0.5.6 615 3/9/2023
0.5.5 592 3/8/2023
0.5.4 561 3/7/2023
0.5.3 575 3/6/2023
0.5.2 590 3/6/2023
0.5.1 565 3/6/2023
0.5.0 738 3/6/2023