Dateien effizient mit cURL zu Microsoft Azure exportieren
Um eine Datei mit cURL zu Azure Blob Storage zu exportieren, senden Sie eine authentifizierte
Anfrage mit PUT und x-ms-blob-type: BlockBlob. Das folgende Beispiel
signiert diese Anfrage mit einem vorhandenen Speicherkontoschlüssel, verweigert das Ersetzen eines
vorhandenen Blobs und lädt das Ergebnis herunter, um seine Bytes mit der lokalen Datei zu vergleichen.
Voraussetzungen prüfen
Verwenden Sie eine Linux-Shell mit installiertem Node.js und cURL. Dieses Beispiel wurde mit Node.js 26.12.0 und cURL 8.23.0 getestet. Verwenden Sie diese Versionen, um es unter denselben Bedingungen nachzuvollziehen. Node führt das TypeScript-Hilfsprogramm ohne Compiler oder Paketinstallation aus. Die Downloadseite von Node.js und die cURL-Veröffentlichungen stellen die Tools bereit.
Prüfen Sie beide ausführbaren Dateien, bevor Sie das Programm speichern:
node --version && curl --version
Sie benötigen außerdem:
- Ein vorhandenes Speicherkonto und einen privaten Container, die über den öffentlichen
Azure-Endpunkt
https://ACCOUNT.blob.core.windows.neterreichbar sind. Verwenden Sie für ein neues Projekt ein unterstütztes universelles v2-Konto. Die Bereitstellung von Konto und Container ist nicht Teil dieser Anleitung. - Die Berechtigung, Shared Key für dieses Konto zu verwenden, sowie dessen Base64-Kontoschlüssel, den Sie über den Prozess Ihrer Organisation zur Verwaltung sensibler Zugangsdaten erhalten. Halten Sie ihn in einer geschützten Datei bereit, die nur Sie lesen können. Das Programm liest diese Datei über die Standardeingabe ein. Bewahren Sie sie außerhalb Ihres Projekts und der Versionsverwaltung auf.
- Eine reguläre lokale Datei mit einer Größe von null bis 8 MiB. Lassen Sie sie unverändert, bis die Verifizierung abgeschlossen ist. Dieses Beispiel für kleine Dateien puffert die Eingabe und die heruntergeladenen Bytes im Arbeitsspeicher.
Verwenden Sie im vollständigen lokalen Pfad druckbare ASCII-Zeichen. Verwenden Sie für den Namen des entfernten Blobs durch Schrägstriche getrennte Segmente mit Buchstaben, Ziffern, Punkten, Unterstrichen und Bindestrichen. Beginnen Sie jedes Segment mit einem Buchstaben, einer Ziffer, einem Unterstrich oder einem Bindestrich. Das Hilfsprogramm weist andere Namen bewusst zurück, statt das URL-Escaping zu erraten. Eine leere Datei ist gültig.
Anfrage signieren
Shared Key verwendet eine HMAC-SHA256-Signatur über die HTTP-Methode, ausgewählte Anfrageheader und
den Ressourcenpfad. Der Kontoschlüssel wird vor dem Signieren aus Base64 decodiert. Die Reihenfolge der
Header ist entscheidend. Bei einem Upload mit null Bytes bleibt das Content-Length-Feld in der
Signatur leer, obwohl sein HTTP-Header Content-Length auf 0 steht.
Diese Details folgen der Shared-Key-Spezifikation von Microsoft.
Ein Kontoschlüssel gewährt weitreichenden Zugriff. Microsoft empfiehlt nach Möglichkeit Microsoft Entra ID mit verwalteten Identitäten. Diese Anleitung richtet sich ausdrücklich an eine Umgebung, in der die Verwendung von Shared Key bereits autorisiert ist. Ein Bearer-Token oder SAS-Token kann den Kontoschlüssel in diesem Programm nicht ersetzen.
Speichern Sie dieses vollständige Programm als azure-upload.mts. Die Erweiterung
.mts legt sein Modulformat explizit fest, auch innerhalb eines bestehenden
CommonJS-Projekts.
import { spawnSync } from 'node:child_process'
import { createHash, createHmac } from 'node:crypto'
import { readFileSync, statSync } from 'node:fs'
import { resolve } from 'node:path'
const maxBytes = 8 * 1024 * 1024
function main(): void {
const [input, account, container, blob, ...extra] = process.argv.slice(2)
if (!input || !account || !container || !blob || extra.length > 0) {
throw new Error('Usage: node azure-upload.mts FILE ACCOUNT CONTAINER BLOB < KEY_FILE')
}
const file = resolve(input)
if (!/^[\x20-\x7e]+$/.test(file)) throw new Error('Use a printable ASCII local path.')
if (!/^[a-z0-9]{3,24}$/.test(account)) throw new Error('Invalid account name.')
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(container) || container.length < 3 || container.length > 63) {
throw new Error('Invalid container name.')
}
if (blob.length > 1024 || !blob.split('/').every((part) => /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/.test(part))) {
throw new Error('Use simple slash-separated blob-name segments.')
}
const info = statSync(file)
if (!info.isFile() || info.size > maxBytes) throw new Error('Use a regular file of at most 8 MiB.')
const original = readFileSync(file)
if (original.length > maxBytes) throw new Error('The input grew beyond 8 MiB.')
const encodedKey = readFileSync(0, 'utf8').trim()
if (!/^[A-Za-z0-9+/]{86}==$/.test(encodedKey)) throw new Error('Expected a Base64 512-bit account key.')
const key = Buffer.from(encodedKey, 'base64')
const url = `https://${account}.blob.core.windows.net/${container}/${blob}`
function request(method: 'PUT' | 'GET'): Buffer {
const uploading = method === 'PUT'
const length = uploading ? original.length : 0
const contentType = uploading ? 'application/octet-stream' : ''
const azureHeaders = [
...(uploading ? ['x-ms-blob-type:BlockBlob'] : []),
`x-ms-date:${new Date().toUTCString()}`,
'x-ms-version:2023-11-03',
]
const fields = [method, '', '', length === 0 ? '' : String(length), '', contentType,
'', '', '', uploading ? '*' : '', '', '']
const stringToSign = `${fields.join('\n')}\n${azureHeaders.join('\n')}\n/${account}/${container}/${blob}`
const signature = createHmac('sha256', key).update(stringToSign).digest('base64')
const headers = [
...azureHeaders,
`Authorization: SharedKey ${account}:${signature}`,
...(uploading ? [`Content-Length: ${length}`, `Content-Type: ${contentType}`, 'If-None-Match: *'] : []),
]
const result = spawnSync('curl', [
'--disable', '--globoff', '--silent', '--show-error', '--fail', '--proto', '=https',
'--request', method, '--header', '@-',
'--write-out', '%{stderr}HTTP:%{http_code}',
...(uploading ? ['--upload-file', file] : []),
'--output', uploading ? '/dev/null' : '-', url,
], { input: `${headers.join('\n')}\n`, maxBuffer: maxBytes + 65536 })
const status = /HTTP:(\d{3})$/.exec(result.stderr?.toString() ?? '')?.[1] ?? 'unknown'
if (result.status !== 0 || status !== (uploading ? '201' : '200')) {
console.error(`${method} failed: HTTP ${status}; cURL exit ${result.status ?? 'unknown'}.`)
throw new Error('Request failed.')
}
return result.stdout
}
request('PUT')
const downloaded = request('GET')
if (!original.equals(downloaded)) throw new Error('Downloaded bytes differ from the input.')
const sha256 = createHash('sha256').update(original).digest('hex')
console.log(`Verified ${original.length} bytes; SHA-256 ${sha256}`)
}
try {
main()
} catch {
console.error('Export failed. Check the input, account key, destination, and connection.')
process.exitCode = 1
}
Das Hilfsprogramm übergibt den Autorisierungsheader mit --header @- über die
Standardeingabe an cURL. So bleiben Schlüssel und Signatur außerhalb der Befehlsargumente. Es
verhindert außerdem das Laden von .curlrc, damit eine gespeicherte
cURL-Konfiguration keine Retries hinzufügen oder die Ausgabeverarbeitung ändern kann. Vermeiden
Sie Shell-Tracing sowie die Trace- und Verbose-Optionen von cURL beim Umgang mit Zugangsdaten.
Informationen zu diesen Optionen finden Sie im cURL-Handbuch.
Datei hochladen und Bytes prüfen
Ersetzen Sie unten Konto, Container, Zielblobnamen und den Pfad zur Schlüsseldatei. Der Container
muss bereits existieren. Wählen Sie ein Ziel, das noch nicht existiert. Führen Sie diesen Befehl
in dem Verzeichnis aus, das azure-upload.mts und Ihre Eingabedatei enthält:
node azure-upload.mts ./localfile.bin youraccount yourcontainer exports/localfile.bin < /secure/path/azure-account.key
Ein erfolgreicher Put Blob
liefert HTTP 201 zurück. Anschließend verlangt das Programm bei einem
separaten authentifizierten Download HTTP 200 und vergleicht die
vollständige Antwort mit der ursprünglichen Eingabe. Erst wenn beide Prüfungen erfolgreich sind,
gibt es Verified, die Byteanzahl und den
SHA-256-Hashwert aus. Ein erfolgreicher Upload allein erzeugt diese Meldung nicht. Der Blob bleibt
in Ihrem privaten Container. Dieses Programm löscht ihn nicht.
Ablehnung oder ungewisses Ergebnis behandeln
Die Bedingung If-None-Match: * beschränkt den Upload auf
das Erstellen neuer Blobs. Wird der Befehl mit einem vorhandenen Ziel wiederholt, muss er
fehlschlagen und dessen Bytes unverändert lassen. Azure kann 409 mit
BlobAlreadyExists oder einen Bedingungsfehler mit 412 melden.
Deuten Sie nicht jeden Konflikt als Authentifizierungsproblem. Microsoft führt die
Blob-Storage-Fehlercodes auf.
Das Programm meldet nur den HTTP-Status und den cURL-Exit-Code, ohne die Fehlerantworten des
Anbieters auszugeben.
Bei 403 müssen Sie den Zugriff prüfen: Kontrollieren Sie, ob Konto und
Schlüssel zusammengehören, ob Shared Key zulässig ist und ob Ihr Netzwerk auf dieses Konto zugreifen
kann. Prüfen Sie die lokale Datei und den Pfad zur Schlüsseldatei, wenn das Programm fehlschlägt,
bevor es einen HTTP-Status meldet. Automatische Retries sind nicht aktiviert.
Ein Verbindungsabbruch kann das Ergebnis des Schreibvorgangs ungewiss lassen: Azure hat den Blob möglicherweise gespeichert, bevor die Antwort verloren ging. Auch bei einem fehlgeschlagenen Verifizierungsdownload bleibt der hochgeladene Blob bestehen. Untersuchen Sie genau dieses Ziel mit Ihren üblichen authentifizierten Speicherwerkzeugen und vergleichen Sie seine heruntergeladenen Bytes, bevor Sie entscheiden, ob Sie es behalten oder entfernen. Eine Wiederholung dieses Befehls, der ausschließlich neue Blobs erstellt, kann den Inhalt eines vorhandenen Blobs nicht bestätigen und ersetzt ihn nicht. Löschen Sie kein vorhandenes Ziel, nur damit ein erneuter Versuch gelingt.
Für größere Übertragungen ein anderes Tool wählen
Die Obergrenze von 8 MiB gilt für dieses Beispiel mit Pufferung im Arbeitsspeicher, nicht für den
Azure-Dienst. Es verwendet eine einzelne Anfrage mit Put Blob, statt Blöcke
zwischenzuspeichern und zu bestätigen. Verwenden Sie für größere Dateien, fortsetzbare Übertragungen
oder Stapelautomatisierung einen Speicherclient, der diese Übertragungen verwaltet. Dieses Beispiel
gibt keine Garantie für die Übertragung mehrerer Blöcke oder den Durchsatz. Die
Azure-Blob-Clientbibliotheken sind ein sinnvoller nächster Schritt,
wenn ein kleiner REST-Export nicht mehr ausreicht.
