Upload auf Backblaze B2 durch eine Firebase Cloud Function

Wie laden Sie Dateien und Fotos über eine Firebase Google Cloud Funktion in ein Backblaze B2 Bucket hoch?

Einführung

In diesem Artikel zeige, wie man Dateien und Bilder durch eine Serverless Firebase Cloud Funktion empfangen, verarbeiten und dann die Dateien in einem Backblaze B2 Bucket speichern kann.

Diese Funktion kann zum Beispiel für das Hochladen von Profilbildern oder auch von Daten zum Download genutzt werden. Der Sender kann dabei ein einfaches HTML-Formular auf einer Website sein oder auch eine Upload-Funktion in einer APP sein.

Warum Firebase Cloud Funktionen?

Cloud Functions ermöglichen es, Javascript Code im Backend auszuführen, ohne einen Server betreiben zu müssen. Nicht nur spart man viel Einrichtungszeit eines Servers, sondern man muss sich auch nicht um die Sicherheit und Administration des Servers Gedanken machen. Gleiches gilt dann auch bei der Speicherung der Daten in einem Bucket, wobei natürlich trotzdem auf die Sicherheit und Zugriffsmöglichkeit geachtet werden soll.

Ein weiterer Vorteil einer Serverless Function ist die Skalierbarkeit. Cloud Functions sind nur aktiv, wenn sie angesprochen werden. Anders als ein normaler Server, der immer aktiv und erreichbar sein muss. Cloud Functions können dadurch wunderbar Lastspitzen abfangen und bezahlt dabei nur die wirkliche Nutzung.

Warum Backblaze B2

Wieso sollte man zu Backblaze B2 greifen und nicht zu dem schon in Firebase integriertem Google Cloud Storage? Kosten!

Backblaze ist ein US-amerikanisches Unternehmen das durch seine Backup-Software bekannt wurde. Ähnlich wie bei Amazon entschloss man sich die Technologie um diesen Service zu betreiben, auch anderen zu Verfügung zu stellen.

Derzeit kostet die Speicherung eines Gigabytes bei Backblaze 0,005 US-Dollar pro Monat. Bei Google Firebase sind es 0,026 US-Dollar pro GB, also knapp das fünffache von Backblaze B2.

Der Traffic für den Upload ist bei beiden Anbietern kostenlos. Pro 10.000 Upload Operationen werden jedoch bei Firebase $0,004 fällig. Zwar berechnet für den API Upload Calls nichts, dafür wird aber ist die Transaction zur Authentifizierung (b2_authorize_account) berechnet. Pro 1.000 Anfragen sind das $0,004.

Der Download-Traffic wird mit $0,01 (Backblaze) und $0,12 (Firebase) pro Gigabyte berechnet. Dazu kommen jeweils $0,004 pro 10.000 API-Abrufe.

Backblaze hat allerdings ein Free Tier von 2.500 API Calls und 1 GB Traffic pro Tag. Zusätzlich gibt es 10 GB Speicher pro Monat kostenlos.

Firebase kommt auch mit einem Freibetrag. Im Spark Plan sind 5 GB Speicher, 1 GB Download-Traffic pro Tag und 20.000 Download Calls und 50.000 Download Calls pro Tag inklusive.

Die reinen Speicherkosten für 100 GB liegen also bei 0,45 US-Dollar (Backblaze) und 2,47 US-Dollar (Firebase). Die Kosten für eine Speicherung bei Amazon S3 würden im übrigen im Bereich von Firebase liegen.

Bevor du dich für einen der Anbieter entscheidest, solltest du auf jeden Fall genau durchrechnen, welcher für dich am meisten Sinn macht.

Die aktuellen Preise findest du auf der Website von Backblaze und Firebase.

Setup

Natürlich sollte man einen Account bei Backblaze und Firebase besitzen. Außerdem sollte das Commandline-Tool von Firebase installiert sein.

Ein wichtiger Punkt bei Firebase ist das der Abrechnungsplan von Spark auf Blaze gestellt wird. Nur so sind Verbindungen außerhalb von der Google Cloud möglich.

Backblaze API Keys

Zur Authentifizierung brauchen wir einen API Key von Backblaze. Dafür kann der Master Key verwendet werden. Aus Sicherheitsgründen sollte man allerdings speziell für die Funktion einen speziellen Key erzeugen. Dies ist einfach auf der Website unter “App Keys” möglich. Hier kann man den Zugang auf nur ein bestimmtes Bucket eingrenzen.

Nach der Erstellung notiert man sich die keyID und den applicationKey. Diese brauchen wir später. Außerdem gebraucht wird die BucketID. Diese ID findet man in der Infokarte auf der Übersichtsseite der Buckets.

Backblaze-nodejs-api-key-creation
Erstellung des API Keys
b2-cloud-storage-bucketid-backblaze
Die benötigten Zugangsdaten
backblaze-b2-api-key
Die Bucket ID

Code

Der Code für diese Funktion ist eigentlich recht einfach und kann bei Bedarf leicht erweitert werden.

Das node.js module “busboy” parst die HTML Form Data und schreibt die eintreffende Datei in den temporären Speicher der Cloud Function. Anschließend findet die Authentifizierung von B2 statt und die Datei in das angegeben Bucket geschrieben.

Folgend werde ich einige wichtige Teile der Cloud Function durchgehen. Den kompletten Code findest du auf Github.

Dies ist wohl der wichtigste Teil der Funktion. Hier musst du API Daten von Backblaze angeben.

// Initiate B2 with auth keys
const b2 = new b2CloudStorage({
  auth: {
    accountId: "<accountId>", // NOTE: This is the keyID unique to the key
    applicationKey: "<applicationKey>",
  },
});
const B2bucketId = "";

In diesem Teil wird der Name der Datei festgelegt. Als Beispiel wird hier einfach eine zufällige Zahlenkombination verwendet. Natürlich kann man ganz einfach ein eigenes Schema verwenden.

// Get file extension
const fileExtension = filename.split(".")[filename.split(".").length - 1];

// Select filename / Random number with file extension
newFileName = `${Math.round(Math.random() * 1000000000000).toString()}.${fileExtension}`;

Dieser Teil ist verantwortlich für den Upload der Datei. Möchte man die Datei in ein bestimmtes Verzeichnis einsortieren, muss dies in diesem Teil geschehen.

b2.uploadFile(tempFile.filepath, {
          bucketId: B2bucketId,
          fileName: tempFile.newFileName,
          // Upload to a directory
          //   fileName: "userfiles/" + thumbFileName,
          contentType: tempFile.mimetype,
        },

Nach dem erfolgreichen Upload schickt dieses Beispiel eine simple HTTP-Antwort zurück. Selbstverständlich könnte man hier noch andere Aktionen integrieren. Zum Beispiel die Daten in der Firestore Datenbank speichern oder den Filename zurückschicken.

Möchte man die gespeicherte Version nochmal mit der temporären Datei vergleichen ist das auch möglich. Als Antwort sendet Backblaze auch den MD5 und SHA1 Hash.

  function (err, results) {
          if (err) return res.status(500).json({ error: err });

          return res.status(201).json({ message: "File uploaded!" });
        }

Im kompletten Code findet sich auch noch eine spezielle Funktion für den Upload. Diese prüft anhand des MIME-Typs, ob es sich um ein Bild handelt. Ist dies nicht der Fall, sendet sie einen Error an den Anfragenden.

 if (
      mimetype !== "image/jpeg" &&
      mimetype !== "image/png" &&
      mimetype !== "image/gif"
    ) {
      return res.status(400).json({
        error: "Wrong file type",
      });
    }

Extras

Natürlich lässt sich dieses Funktion noch in vielen Aspekten erweitern und anpassen. So ist es auch möglich als Trigger ein anderes Event zu verwenden. Zum Beispiel kann bei der Erstellung einer Datei in Firebase Storage, die Datei direkt auf Backblaze als Backup gespeichert werden.

Umgebungsvariablen

Den API-Key und die Bucket ID sollte man nach Möglichkeit nicht direkt im Code speichern. Hierfür kann man die Umgebungsvariablen (Environment Variables) von Firebase verwenden.

Dazu muss man diesen Code, mit den entsprechenden Werten, in der Kommandozeile laufen lassen.
firebase functions:config:set b2.keyid="{keyId}" b2.appkey="{applicationKey}" b2.bucketid="{BucketID}"

Anschließend kann man die Werte entsprechend abrufen.

const b2 = new B2({
  auth: {
    accountId: functions.config().b2.keyid,
    applicationKey: functions.config().b2.appkey,
  },
});
const B2bucketId = functions.config().b2.bucketid;

Region auswählen

Sowohl Firebase als auch Backblaze bieten die Verteilung und Speicherung in verschieden Regionen der Welt an.

Bewegründe für die Wahl können der Preis (in diesem Fall macht dies keinen Unterschied) oder Jurisdiktion und Vorgaben sein. In dem meisten Fällen wird der zugrundeliegende Gedanke allerdings Speed sein.

Der Speicherort sollte nach Möglichkeit in der Nähe der eigenen Zielgruppe sein.

Backblaze bietet derzeit nur zwei Locations an. US-West, dort liegen die Daten in Sacramento (California) und Phoenix (Arizona) und EU-Central, mit dem Standort Amsterdam.

Bei Google Firebase hat man einige mehr Optionen. Zur Wahl stehen derzeit us-central1 (Iowa), us-east1 (South Carolina), us-east4 (Northern Virginia), europe-west1 (Belgium), europe-west2 (London), europe-west3 (Frankfurt), asia-east2 (Hong Kong) und asia-northeast1 (Tokyo).

Die gewählte Region gibt man direkt im Code an. Für Frankfurt wäre das zum Beispiel dieser Abschnitt.
exports.uploadFile = functions.region("europe-west1").https.onRequest((req, res) => {

Natürlich sollte man nicht nur die Nähe der Cloud Function zu den Besuchern bedenken, sondern auch zum Backblaze B2 Bucket. Je nach Location kann es zu einer höheren Latenz beim Upload kommen.

Bilder verändern

Arbeitet man mit Bildern, wird man diese vermutlich vor dem Upload verarbeiten wollen. Hierzu kann man zu dem Node.js module “sharp” greifen. Dieses kann ohne Weiteres Bilddateien in alle gewünschten Größen vergrößern und verkleinern. Auf Wunsch ist es auch möglich, das Bild in ein andere Format, wie WebP zu verwandeln.

Download

Den kompletten Code findest du kostenlos auf Github.