Amazon S3: Zugriff auf S3 per C#

Im Artikel „Amazon S3: Wie wir einen Image-Server realisiert haben“ habe ich bereits über die Vorteile der Amazon S3 berichtet. Nun folgt der Zugriff auf die S3 mittels C#.

Daten zu Amazon S3 hochladen
Amazon selbst stellt eine einfache Oberfläche zur Verfügung. Mit dieser kann man Daten verwalten, hochladen, löschen und auch Rechte und Metadaten verwalten. Da die Daten jedoch maschinell zu S3 hochgeladen werden sollten, musste die Dokumentation studiert werden. Leider bietet Amazon keinen FTP-Zugriff aus die S3. Von Sicherheitsbedenken mal abgesehen wäre dies für uns der einfachste Weg gewesen.

Erste Schritte mit S3
amazon-s3-management-consoleZuerst sollte man sich mit S3 über die Oberfläche vertraut machen. Man sieht sehr schnell, dass Amazon bewusste die S3 nur mit einem Minimum an Funktionen ausgestattet hat. Man sollte sich mit „Buckets“ (Eimer) auseinandersetzen. In diesem werden die Daten in der S3 gespeichert. Ein besonderes Augenmerk sollte auf die Rechtevergabe fallen, denn alle Daten, die von einem User hochgeladen werden, sind per Default auch nur für diesen zum Download zugänglich. Für einen Image-Server natürlich nicht sinnvoll.
Zudem sollten die Meta-Daten betrachtet werden. Bilder werden bereits mit dem richtigen Header ausgestattet. Um ein Expire-Date zu setzen, muss unter den Meta-Daten dies angegeben werden. Eine allgemeingültige Angabe wie im Apache-Header ist leider nicht möglich. Im Beispielcode unten wird beim Hochladen neben allgemeinen Lese-Rechten auch ein Expires-Date jedem Image vergeben.

Amazon .Net-DLL für .NET
Um die Amazon S3 mittels C# ansprechen zu können, benötigen wir die Amazon DLL „AWSSDK.dll“, die bereits stolze 2.995 KB groß ist. Sie beinhaltet jedoch alles, was man zur Kommunikation mit Amazon benötigt. Also auch weitere Schnittstellen, die über unsere C#-Kommunikation mit S3 hinaus geht. Eingebunden in ein .NET-Projekt, binden wir die DLL per „using Amazon.S3;“ ein. Je nach Anwendung erfolgen noch weitere Using-Anweisungen.

Die S3 ersetzt einen FTP-Server
Untenstehend folgt ein C#-Beispiel samt C#-Code, wie man einen S3-Bucket alternativ eines FTP-Servers nutzen kann. Wir prüfen hier, ob ein Ordner in einem Bucket exisitert und legen gegebenenfalls den Ordner in der Amazon S3 an.
Dann erfolgt der Upload einer Datei. Wir begnügen uns hier, Datei für Datei in die Amazon-Wolke hochzuladen. Die Amazon-DLL bietet auch die Möglichkeit, ganze Ordner in der S3 abzulegen. Hierzu einfach einmal die Amazon-Doku studieren.

S3: Dateien öffentlich lesbar machen
Dateien, die von einem Benutzer hochgeladen werden, sind per default auch nur von diesem lesbar. In unserem Falle möchten wir jedoch, dass alle Besucher die Dateien lesen können. Die Rechte können per Hand über die S3-Oberfläche gesetzt werden. Wenn wir jedoch per C# die Dateien hochladen, können wir den entsprechenden Header-Eintrag gleich setzen:
req.AddHeader(„x-amz-acl“, „public-read“);

S3: Dateien mit einem Expire Date versehen
Um Bandbreite zu sparen und Zugriffe zu minimieren, ist es sinnvoll, Dateien mit einem Expire Date zu versehen. Der lokale Browser wird dadurch angewiesen, bei einem zweiten Aufruf die Datei aus seinem lokalen Cache dem Kunden auszuliefern. Der Vorteil liegt auf der Hand: Die Datei muss nicht erneut angefordert werden und die Kosten für den Webbetreiber werden minimiert, da keine erneute Auslieferung der Datei aus der Cloud erfolgt.
Gerade bei Bildern, die sich selten bis nie ändern, macht es Sinn, das Expire-Date zu setzen. Welches Datum bzw. welchen Zeitraum man wählt, ist Ansichtssache. Es gibt Webmaster, die den Zeitraum nahezu bis ins unendliche ausdehnen. In unserem Beispiel erhöhen wir das aktuelle Datum (Upload-Datum) um fünf Jahre. Eine lange Zeit; die wenigsten Rechner bleiben so lange aktiv bzw. die wenigsten Besucher werden ihren lokalen Internet-Cache in diesem Zeitraum nicht leeren. Doch so haben wir auch genügend Zeit, das Bild irgendwann auszutauschen.

Mit folgendem C#-Beispiel-Code setzen Sie das Expire Date für die Amazon S3-Cloud:

//Optional: Expire Date setzen - für Images sinnvoll
CultureInfo ci = new CultureInfo("en-US");
string sExpire = DateTime.Now.AddYears(5).ToString("ddd, dd MMM yyyy hh:mm:ss", ci) + " GTM";
req.AddHeader("Expires", sExpire);

Der gesamte C#-Beispielcode mit entsprechenden Kommentare:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

using Amazon.S3;
using Amazon.S3.Model;
using Amazon.S3.Transfer; //Upload
using Amazon.S3.Util;//Upload
using System.Web; //Header
using System.Globalization;

namespace ConsoleApplication1
{
    class Program
    {

        //Amazon S3
        private const string AWS_ACCESS_KEY = "<mein AWS_ACCESS_KEY>";
        private const string AWS_SECRET_KEY = "<mein AWS_SECRET_KEY>";
        private const string BUCKET_NAME = "<mein BUCKET_NAME>";
        private const string S3_KEY = "<mein S3_KEY>";

        public static AmazonS3Client client = new AmazonS3Client(AWS_ACCESS_KEY, AWS_SECRET_KEY);

        static void Main(string[] args)
        {

            //Prüfen, ob ein Ordner in S3 exitiert, ansonsten Ordner anlegen
            if (!DoesFolderExist("mein Ordner")) CreateFolder("mein Ordner");

            //Hochladen von Dateien in die S3
            if (Upload("<Zukünftiger Dateiname in der S3>", "<ordner>", "<lokaler Filepfad>")) Console.WriteLine("Upload in die S3 erfolgreich.");
            else Console.WriteLine("Datei-Upload leider nicht erfolgreich!");

        }

        private static bool Upload(string filename, string ordner, string sFilepfad)
        {
            //Upload - Hochladen von Dateien zu Amazon S3
            bool bRes = false;

            //Ordnerstuktur: Basisornder/unterordner_ordner1
            string basisverzeichnis = "basisordner/" + ordner;

            //Existiert der Ordner in S3?
            if (!DoesFolderExist(basisverzeichnis)) CreateFolder(basisverzeichnis);

            try
            {
                //Upload Datei in Folder
                string key = string.Format("{0}/{1}", basisverzeichnis, filename);

                Amazon.S3.Model.PutObjectRequest req = new Amazon.S3.Model.PutObjectRequest().WithBucketName(BUCKET_NAME);
                req.FilePath = sFilepfad; // Lokaler vollqualifizierter Pfad inkl. Dateiname zur lokalen Quelldatei
                req.Key = key;
                req.Timeout = int.MaxValue; //Timeout nach Wunsch
                req.AddHeader("x-amz-acl", "public-read"); //Optional: Hier werden öffentliche Leserechte gesetzt

                //Optional: Expire Date setzen - für Images sinnvoll
                CultureInfo ci = new CultureInfo("en-US");
                string sExpire = DateTime.Now.AddYears(5).ToString("ddd, dd MMM yyyy hh:mm:ss", ci) + " GTM";
                req.AddHeader("Expires", sExpire);

                client.PutObject(req);

                bRes = true;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message, "Upload Error. File: " + filename);

            }
            return bRes;
        }

        public static bool DoesFolderExist(string folderName)
        {
            //Amazon S3: Prüft, ob ein Ordner vorhanden ist
            try
            {
                ListObjectsRequest request = new ListObjectsRequest();
                request.BucketName = BUCKET_NAME;
                request.WithPrefix(folderName + "/");
                request.MaxKeys = 1;

                using (ListObjectsResponse response = client.ListObjects(request))
                {
                    if (response.S3Objects.Count > 0) return true;
                }
            }
            catch (Amazon.S3.AmazonS3Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            return false;
        }

        public static void CreateFolder(string folderName)
        {
            //Neuanlage eines Ordners in S3
            try
            {
                Console.WriteLine("Lege Ordner an: " + folderName);

                //Amazon S3: Anlegen eines Ordners
                var key = string.Format(@"{0}/", folderName);
                var request = new PutObjectRequest().WithBucketName(BUCKET_NAME).WithKey(key);
                request.InputStream = new MemoryStream();
                client.PutObject(request);
            }
            catch (Amazon.S3.AmazonS3Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

UPDATE 22.08.2012: Danke an den Leser „Andreas“, der festgestellt hat, dass der hier angelegte „S3-Key“ im Beispielprojekt nicht verwendet wird. Somit wird der Key auch nicht benötigt.

Schreibe einen Kommentar