Dateien mit .NET (C#) nach DigitalOcean Spaces exportieren
Laden Sie mit dem AWS SDK for .NET eine lokale Datei in einen privaten DigitalOcean Space hoch. Laden Sie sie anschließend herunter und vergleichen Sie ihre Länge und ihren SHA-256-Hash. Das folgende Programm akzeptiert leere Dateien und Dateien bis zu 64 MiB und meldet Erfolg erst nach der Verifizierung.
Die S3-Kompatibilität von DigitalOcean Spaces verstehen
Spaces implementiert eine Teilmenge der S3-API.
Konfigurieren Sie für einen bestehenden Space explizit den regionalen Endpunkt, die Signaturregion
und die Spaces-Zugangsdaten. Dieses Beispiel verwendet Anfragen im Path-Style. Der Endpunkt enthält
also die Region, aber keinen Bucket-Namen: https://fra1.digitaloceanspaces.com.
Sie benötigen einen bestehenden privaten Space und einen Spaces-Schlüssel, der dessen Objekte schreiben und lesen darf. Für einen Bucket ohne Bucket-Richtlinie unterstützt DigitalOcean einen auf diesen Bucket beschränkten Schlüssel mit den Berechtigungen Read/Write/Delete. Eingeschränkte Schlüssel lassen sich nicht mit Bucket-Richtlinien kombinieren; siehe Zugriffsverwaltung für Spaces. Ein DigitalOcean-API-Token ist nicht das Spaces-Schlüsselpaar, das dieses Programm verwendet. Diese Anleitung setzt einen privaten Bucket ohne Richtlinie für öffentlichen Lesezugriff voraus.
Erforderliche NuGet-Pakete installieren
Die Shell-Befehle verwenden Bash unter Linux. Installieren Sie das aktuelle .NET 10 SDK statt nur der Laufzeitumgebung. Dieses Beispiel wurde mit SDK 10.0.402 und Laufzeitumgebung 10.0.13 getestet. Die folgenden Paketversionen legen AWS SDK v4 für eine reproduzierbare Einrichtung fest. Es sind getestete Versionen, keine grundsätzlich erforderlichen Mindestversionen.
Beginnen Sie in einem beschreibbaren Verzeichnis außerhalb eines bestehenden .NET-Projekts.
Der Befehlsblock verweigert die Ausführung bei vorhandenen Dateien namens
SpacesExport und bei Konfigurationen in übergeordneten Verzeichnissen, die
SDK-Auswahl, Builds oder Paketinstallation beeinflussen könnten. Bei erfolgreicher Einrichtung
wechselt die Shell in das neue Projekt; bei einem Fehler bleibt sie im ursprünglichen Verzeichnis.
(
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
Falls die Paketinstallation nach dem Erstellen des Projekts fehlschlägt, behalten Sie das Projekt. Beheben Sie das gemeldete Problem mit der NuGet-Verbindung oder Paketquelle und führen Sie dann diese Befehle erneut aus dem ursprünglichen Verzeichnis aus:
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
Wechseln Sie nach einem erfolgreichen erneuten Versuch in SpacesExport,
bevor Sie fortfahren.
AWS SDK for .NET mit DigitalOcean Spaces verwenden
Stellen Sie diese Umgebungsvariablen über Ihre Verwaltung für sensible Zugangsdaten oder Ihre Shell-Umgebung bereit. Schlüssel und geheimer Schlüssel müssen zu Spaces gehören, nicht zu AWS.
| Variable | Wert |
|---|---|
SPACES_ENDPOINT | Regionaler HTTPS-Endpunkt, etwa https://fra1.digitaloceanspaces.com |
SPACES_REGION | Region des bestehenden Space, etwa fra1 |
SPACES_BUCKET | Name des bestehenden Space |
SPACES_KEY_ID | Spaces-Zugriffsschlüssel-ID |
SPACES_APPLICATION_KEY | Geheimer Spaces-Schlüssel |
Die SDK-Anleitung von DigitalOcean zeigt,
wie Sie den Endpunkt auswählen. Übergeben Sie Objektschlüssel als Strings ohne URL-Kodierung.
Das SDK signiert die Anfrage und URL-kodiert Leerzeichen, #,
?, +, Prozentzeichen und Unicode-Zeichen.
Ersetzen Sie Program.cs durch dieses vollständige Programm. Verwenden Sie
reguläre lokale Dateien und gültige UTF-8-Namen. Das Programm weist Argumente mit dem
Ersetzungszeichen � zurück, damit ein ungültiger nativer Dateiname
nicht unbemerkt auf eine andere Datei verweist. Lassen Sie die lokale Datei bis zum Abschluss der
Verifizierung unverändert und verwenden Sie einen Zielschlüssel, unter dem kein anderer Prozess
schreibt.
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);
}
}
Erstellen Sie im Projektverzeichnis den Build und exportieren Sie eine bereits vorhandene Datei:
dotnet build && dotnet run --no-build -- './report.txt' 'reports/report #1.txt'
Ersetzen Sie beide Argumente durch Ihren lokalen Pfad und den Zielschlüssel.
Mit -- werden Optionen der .NET CLI von den Programmargumenten
abgegrenzt, darunter Dateinamen, die mit - beginnen.
Ein erfolgreicher Durchlauf gibt Verified
gefolgt von der Anzahl der heruntergeladenen Bytes aus und endet mit Status null.
Die Datei verbleibt in Spaces. Wenn Sie denselben Schlüssel erneut verwenden, wird sein aktueller
Inhalt ersetzt. Wählen Sie einen neuen Schlüssel, um einen früheren Export zu behalten.
Wiederholungslogik und Fehlerbehandlung hinzufügen
MaxErrorRetry = 2 erlaubt bei wiederholbaren Fehlern höchstens zwei SDK-Retries pro
Anfrage. Das SDK
akzeptiert ein Abbruch-Token für asynchrone Aufrufe.
Dieses Programm setzt eine gemeinsame Frist von fünf Minuten für lokale Hash-Berechnung, Upload,
Retries und Verifizierung. Bei einer langsamen Verbindung kann selbst eine Datei unter 64 MiB
diese Frist überschreiten. Strg+C fordert über dasselbe Token einen Abbruch an.
In beiden Fällen wird kein verifiziertes Ergebnis ausgegeben, und das Programm endet mit einem
Status ungleich null.
Ein Upload kann Spaces bereits erreicht haben, bevor eine Antwort verloren geht oder eine spätere Verifizierung fehlschlägt. Das Programm löscht das entfernte Objekt bei einem Fehler nicht. Vergleichen Sie den Inhalt unter diesem Schlüssel mit der unveränderten lokalen Datei, ohne sie erneut hochzuladen:
dotnet build && dotnet run --no-build -- './report.txt' 'reports/report #1.txt' --verify-only
Dieser Modus lädt nur herunter und vergleicht. Bei fehlendem oder abweichendem Inhalt liefert er einen Status ungleich null. Ist die Verifizierung erfolgreich, stimmt das aktuell gespeicherte Objekt mit Ihrer Datei überein. Andernfalls beheben Sie den Fehler, bevor Sie entscheiden, ob Sie erneut hochladen möchten. Die Verifizierung erfasst den aktuellen Zustand des Objekts, sperrt es aber nicht gegen andere Schreibzugriffe.
Dateien begrenzter Größe streamen
Der Upload sendet eine einzelne PUT-Anfrage aus einem geöffneten Dateistream. Zur Verifizierung liest das Programm die Antwort über einen Puffer von 64 KiB und prüft die vollständige Länge und den Hash, auch bei leeren Dateien. Die Datei wird einmal heruntergeladen. Berücksichtigen Sie diese zusätzliche Übertragung daher bei der Schätzung von Zeit und Bandbreite. Die Obergrenze von 64 MiB ist das Anwendungslimit dieses Beispiels, nicht das Limit für die Objektgröße in Spaces.
Die Konfiguration deaktiviert die Chunked-Kodierung des Anfragekörpers beim Upload und fordert optionale SDK-Prüfsummen nur an, wenn ein Vorgang sie erfordert. Diese Einstellungen werden für diesen Zugriff auf Spaces verwendet; das explizite Zurücklesen übernimmt die durchgängige Inhaltsprüfung. Das Programm gibt seine Streams, die Antwort und den Client sowohl bei Erfolg als auch bei einem Fehler frei.
Dieses Beispiel führt keine mehrteiligen Uploads aus. Größere Dateien benötigen einen separaten Workflow für mehrteilige Uploads, einschließlich Wiederaufnahme und Bereinigung unvollständiger Uploads. Das Erhöhen dieser Konstante stellt diesen Workflow nicht bereit.
Zugriffsschlüssel und Berechtigungen sicher verwalten
Bewahren Sie Zugangsdaten in der serverseitigen Konfiguration auf. Platzieren Sie den geheimen Schlüssel nicht in einer URL, einer Quelldatei, einem Browser-Bundle oder einem aufgezeichneten SDK-Netzwerkprotokoll. Dieses Programm übergibt das Schlüsselpaar explizit und verwendet HTTPS. Es macht den Space weder öffentlich noch ändert es Bucket-Berechtigungen. Beschränken Sie Diagnoseausgaben auf den Status und die eigenen Meldungen des Programms.
Häufige Probleme beheben
Prüfen Sie bei Storage returned HTTP 403., ob Sie das Spaces-Schlüsselpaar angegeben haben, ob es Lese- und Schreibzugriff auf diesen Bucket hat und ob Endpunkt und Signaturregion zum Standort des Buckets passen. Auch nach einer erfolgreichen PUT-Anfrage schlägt die Verifizierung fehl, wenn die folgende GET-Anfrage verweigert wird.
Verwenden Sie bei File exceeds the 64 MiB limit. eine kleinere Datei oder eine separate Implementierung für mehrteilige Uploads. Prüfen Sie bei Stored bytes do not match the local file., ob sich die lokale Datei oder das Ziel während des Vorgangs geändert hat. Verwenden Sie dann den reinen Verifizierungsmodus mit dem vorgesehenen Paar. Eine verweigerte Verbindung oder ein vorübergehender HTTP-Fehler nach Ausschöpfen der Retries führt zu einem Status ungleich null. Prüfen Sie die Erreichbarkeit, bevor Sie es erneut versuchen. Ein fehlgeschlagener Aufruf ist kein Beleg dafür, dass kein Objekt gespeichert wurde.
