Exporter des fichiers vers DigitalOcean Spaces en .NET (C#)
Utilisez AWS SDK for .NET pour téléverser un fichier local vers un Space DigitalOcean privé, puis le télécharger et comparer sa taille et son empreinte SHA-256. Le programme ci-dessous accepte les fichiers vides et les fichiers jusqu’à 64 MiB, et ne signale la réussite qu’après vérification.
Comprendre la compatibilité S3 de DigitalOcean Spaces
Spaces implémente un sous-ensemble de l’API S3.
Pour un Space existant, configurez explicitement son point de terminaison régional, la région de
signature et les informations d’accès Spaces. Cet exemple utilise des requêtes où le nom du bucket
figure dans le chemin. Le point de terminaison contient donc la région, mais aucun nom de bucket :
https://fra1.digitaloceanspaces.com.
Vous devez disposer d’un Space privé existant et d’une clé Spaces permettant d’écrire et de lire ses objets. Pour un bucket sans politique de bucket, DigitalOcean prend en charge une clé limitée à ce bucket avec les autorisations Read/Write/Delete. Les clés à accès limité ne peuvent pas être combinées avec des politiques de bucket ; consultez la gestion des accès Spaces. Un jeton d’API DigitalOcean n’est pas la paire de clés Spaces utilisée par ce programme. Ce guide suppose un bucket privé sans politique de lecture publique.
Installer les packages NuGet requis
Les commandes shell utilisent Bash sous Linux. Installez le SDK .NET 10 actuel, plutôt que le seul environnement d’exécution. Cet exemple a été testé avec le SDK 10.0.402 et l’environnement d’exécution 10.0.13. Les versions ci-dessous fixent les packages à des versions précises d’AWS SDK v4 pour une installation reproductible ; il s’agit de versions testées, pas de versions minimales intrinsèquement requises.
Commencez dans un répertoire accessible en écriture, en dehors d’un projet .NET existant. Le bloc
refuse les fichiers SpacesExport déjà présents et les configurations des répertoires parents
susceptibles de modifier la sélection du SDK, la compilation ou l’installation des packages.
Si la configuration réussit, votre shell se place dans le nouveau projet ; en cas d’échec, il reste
dans son répertoire initial.
(
command -v dotnet >/dev/null 2>&1 || { printf 'Install the .NET 10 SDK first.\n' >&2; exit 1; }
parent=$PWD
while :; do
for config in "$parent/global.json" "$parent"/Directory.Build.* \
"$parent/Directory.Packages.props" "$parent"/[Nn][Uu][Gg][Ee][Tt].[Cc][Oo][Nn][Ff][Ii][Gg]; do
if [ -e "$config" ]; then
printf 'Use a directory outside existing .NET configuration.\n' >&2
exit 1
fi
done
[ "$parent" = / ] && break
parent=${parent%/*}
[ -n "$parent" ] || parent=/
done
[ ! -e SpacesExport ] || { printf 'SpacesExport already exists.\n' >&2; exit 1; }
dotnet new console --framework net10.0 --name SpacesExport &&
cd SpacesExport &&
dotnet add package AWSSDK.Core --version 4.0.102.10 &&
dotnet add package AWSSDK.S3 --version 4.0.104.3
) && cd SpacesExport
Si l’installation des packages échoue après la création du projet, conservez ce projet. Corrigez le problème de connexion ou de source NuGet signalé, puis réessayez ces commandes depuis le répertoire initial :
dotnet add ./SpacesExport/SpacesExport.csproj package AWSSDK.Core --version 4.0.102.10 &&
dotnet add ./SpacesExport/SpacesExport.csproj package AWSSDK.S3 --version 4.0.104.3
Après une nouvelle tentative réussie, placez-vous dans SpacesExport avant de continuer.
Utiliser AWS SDK for .NET avec DigitalOcean Spaces
Fournissez ces variables d’environnement via votre gestionnaire de secrets ou l’environnement de votre shell. La clé et le secret doivent appartenir à Spaces, pas à AWS.
| Variable | Valeur |
|---|---|
SPACES_ENDPOINT | Point de terminaison HTTPS régional, par exemple https://fra1.digitaloceanspaces.com |
SPACES_REGION | Région contenant le Space existant, par exemple fra1 |
SPACES_BUCKET | Nom du Space existant |
SPACES_KEY_ID | Identifiant de la clé d’accès Spaces |
SPACES_APPLICATION_KEY | Clé secrète Spaces |
Le guide du SDK de DigitalOcean explique
comment sélectionner le point de terminaison. Transmettez les clés d’objet sous forme de chaînes
non encodées. Le SDK signe la requête et encode les espaces, #,
?, +, les signes de pourcentage et les caractères Unicode.
Remplacez Program.cs par ce programme complet. Utilisez des fichiers locaux ordinaires
et des noms UTF-8 valides. Le programme rejette le caractère de remplacement
� dans les arguments afin qu’un nom de fichier natif invalide ne puisse pas
désigner silencieusement un autre fichier. Gardez le fichier local inchangé jusqu’à la fin de la
vérification et utilisez une clé de destination sur laquelle aucun autre processus n’écrit.
using System.Security.Cryptography;
using Amazon.Runtime;
using Amazon.S3;
using Amazon.S3.Model;
public static class SpacesExporter
{
private const long MaxBytes = 64L * 1024 * 1024;
public static async Task<int> Main(string[] args)
{
using var deadline = new CancellationTokenSource(TimeSpan.FromMinutes(5));
Console.CancelKeyPress += (_, e) => { e.Cancel = true; deadline.Cancel(); };
try
{
if (args.Length is not (2 or 3) || (args.Length == 3 && args[2] != "--verify-only"))
throw new ArgumentException("Pass a local file, an object key, and optionally --verify-only.");
if (args.Any(value => value.Contains('\uFFFD')))
throw new ArgumentException("Use UTF-8 arguments without the replacement character.");
if (string.IsNullOrWhiteSpace(args[1]))
throw new ArgumentException("An object key is required.");
var endpoint = new Uri(Required("SPACES_ENDPOINT"));
if (endpoint.Scheme != "https" || endpoint.UserInfo.Length != 0 ||
endpoint.AbsolutePath != "/" || endpoint.Query.Length != 0 || endpoint.Fragment.Length != 0)
throw new ArgumentException("Use the HTTPS regional endpoint without a bucket or path.");
var config = new AmazonS3Config
{
ServiceURL = endpoint.AbsoluteUri,
AuthenticationRegion = Required("SPACES_REGION"),
ForcePathStyle = true,
MaxErrorRetry = 2,
RequestChecksumCalculation = RequestChecksumCalculation.WHEN_REQUIRED,
ResponseChecksumValidation = ResponseChecksumValidation.WHEN_REQUIRED
};
using var client = new AmazonS3Client(
Required("SPACES_KEY_ID"), Required("SPACES_APPLICATION_KEY"), config);
var bucket = Required("SPACES_BUCKET");
await using var input = new FileStream(args[0], FileMode.Open, FileAccess.Read, FileShare.Read);
if (input.Length > MaxBytes)
{
Console.Error.WriteLine("File exceeds the 64 MiB limit.");
return 1;
}
var expectedHash = await SHA256.HashDataAsync(input, deadline.Token);
if (args.Length == 2)
await UploadAsync(client, bucket, args[0], args[1], deadline.Token);
using var stored = await client.GetObjectAsync(bucket, args[1], deadline.Token);
if (stored.ContentLength != input.Length)
throw new InvalidDataException();
using var hash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256);
var buffer = new byte[64 * 1024];
long received = 0;
int count;
while ((count = await stored.ResponseStream.ReadAsync(buffer, deadline.Token)) != 0)
{
received += count;
if (received > input.Length) throw new InvalidDataException();
hash.AppendData(buffer, 0, count);
}
if (received != input.Length || !hash.GetHashAndReset().SequenceEqual(expectedHash))
throw new InvalidDataException();
Console.WriteLine($"Verified {received} bytes.");
return 0;
}
catch (OperationCanceledException) { Console.Error.WriteLine("Transfer canceled or timed out."); }
catch (AmazonS3Exception error) { Console.Error.WriteLine($"Storage returned HTTP {(int)error.StatusCode}."); }
catch (InvalidDataException) { Console.Error.WriteLine("Stored bytes do not match the local file."); }
catch (Exception) { Console.Error.WriteLine("Transfer failed. Check the file, arguments, and storage configuration."); }
return 1;
}
private static string Required(string name) =>
Environment.GetEnvironmentVariable(name) is { Length: > 0 } value
? value : throw new ArgumentException($"Missing {name}.");
public static async Task UploadAsync(IAmazonS3 client, string bucket, string path,
string key, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(key)) throw new ArgumentException("An object key is required.");
await using var input = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
if (input.Length > MaxBytes) throw new IOException("File exceeds the 64 MiB limit.");
await client.PutObjectAsync(new PutObjectRequest
{
BucketName = bucket,
Key = key,
InputStream = input,
AutoCloseStream = false,
ContentType = "application/octet-stream",
UseChunkEncoding = false
}, cancellationToken);
}
}
Depuis le répertoire du projet, compilez le programme et exportez un fichier dont vous disposez déjà :
dotnet build && dotnet run --no-build -- './report.txt' 'reports/report #1.txt'
Remplacez les deux arguments par votre chemin local et votre clé de destination.
-- sépare les options de la CLI .NET des arguments du programme, y compris les
noms de fichiers commençant par -. Une exécution réussie affiche
Verified suivi du nombre d’octets téléchargés et se termine
avec un code de sortie égal à zéro. Le fichier reste dans Spaces. Réutiliser la même clé remplace
son contenu actuel ; choisissez une nouvelle clé pour conserver un export antérieur.
Ajouter une logique de nouvelle tentative et gérer les erreurs
MaxErrorRetry = 2 autorise au maximum deux nouvelles tentatives du SDK par requête pour les
échecs pouvant faire l’objet d’une nouvelle tentative. Le SDK
accepte un jeton d’annulation pour les appels asynchrones.
Ce programme fixe un délai global de cinq minutes pour le calcul local de l’empreinte, le
téléversement, les nouvelles tentatives et la vérification. Même un fichier de moins de 64 MiB
peut dépasser ce délai sur une connexion lente. Ctrl+C demande l’annulation via le même jeton.
Dans les deux cas, aucun résultat de vérification réussie n’est affiché et le code de sortie est
non nul.
Un téléversement peut avoir atteint Spaces avant la perte d’une réponse ou l’échec d’une vérification ultérieure. Le programme ne supprime pas l’objet distant en cas d’échec. Comparez l’objet à cette clé avec le fichier local inchangé, sans le téléverser à nouveau :
dotnet build && dotnet run --no-build -- './report.txt' 'reports/report #1.txt' --verify-only
Ce mode se limite au téléchargement et à la comparaison ; il renvoie un code non nul si le contenu est absent ou différent. Si la vérification réussit, l’objet actuellement stocké correspond à votre fichier. Sinon, résolvez le problème avant de décider de téléverser à nouveau le fichier. La vérification est une observation de l’objet actuel, pas un verrou empêchant un autre processus d’y écrire.
Transférer en flux des fichiers de taille limitée
Le téléversement envoie une seule requête PUT depuis un flux de fichier ouvert. La vérification lit la réponse via un tampon de 64 KiB et contrôle la taille totale et l’empreinte, y compris pour les fichiers vides. Elle télécharge le fichier une fois ; incluez donc ce transfert supplémentaire dans vos estimations de durée et de bande passante. Le plafond de 64 MiB est la limite applicative de cet exemple, pas la limite de taille des objets Spaces.
La configuration désactive l’encodage de téléversement par blocs et ne demande les sommes de contrôle facultatives du SDK que lorsqu’une opération les exige. Ce sont les paramètres utilisés pour cet accès à Spaces ; la relecture explicite assure le contrôle du contenu de bout en bout. Le programme libère ses flux, sa réponse et son client, qu’il réussisse ou échoue.
Cet exemple n’effectue pas de téléversements multiparties. Les fichiers plus volumineux nécessitent une procédure multipartie distincte, comprenant la reprise et le nettoyage des téléversements inachevés ; augmenter cette constante ne suffit pas à la mettre en place.
Gérer les clés d’accès et les autorisations de manière sécurisée
Conservez les informations d’accès dans la configuration côté serveur. Ne placez pas le secret dans une URL, un fichier source, un bundle destiné au navigateur ou un journal capturant les échanges réseau du SDK. Ce programme fournit explicitement la paire de clés et utilise HTTPS ; il ne rend pas le Space public et ne modifie pas les autorisations du bucket. Limitez la sortie de diagnostic au statut et aux messages propres au programme.
Résoudre les problèmes courants
Pour Storage returned HTTP 403., vérifiez que vous avez fourni la paire de clés Spaces, qu’elle dispose des accès en lecture et en écriture à ce bucket, et que le point de terminaison et la région de signature correspondent à son emplacement. Une requête PUT réussie suivie d’une requête GET refusée entraîne tout de même l’échec de la vérification.
Pour File exceeds the 64 MiB limit., utilisez un fichier plus petit ou une implémentation multipartie distincte. Pour Stored bytes do not match the local file., vérifiez si le fichier local ou la destination a changé pendant l’opération, puis utilisez le mode de vérification seule avec le fichier et la clé prévus. Une connexion refusée ou une erreur HTTP transitoire persistant après épuisement des tentatives entraîne un code de sortie non nul ; vérifiez que le service est joignable avant de réessayer. Ne considérez pas l’échec d’une exécution comme une preuve qu’aucun objet n’a été stocké.
