Dateien aus OpenStack Swift in Java importieren
Laden Sie ein OpenStack-Swift-Objekt mit OpenStack4j in eine lokale Datei herunter. Das vollständige Java-Programm unten akzeptiert leere Objekte, prüft die HTTP-Antwort und die heruntergeladene Bytezahl und lässt das Ziel unverändert, bis der Download erfolgreich abgeschlossen ist.
Ihre Java-Umgebung einrichten
Dieses Beispiel verwendet eine POSIX-Shell unter Linux, eine UTF-8-Locale und ein lokales Dateisystem, das atomare Verschiebungen unterstützt. Die Locale bestimmt, wie die JVM Dateinamen aus der Befehlszeile decodiert. Installieren Sie ein aktiv gepflegtes JDK 21 und Apache Maven 3.10 separat. Der folgende Ablauf wurde mit OpenJDK 21.0.12.1 und Maven 3.10.0 getestet; auch Maven 3.9.11 bestand die Tests.
Sie benötigen außerdem ein Keystone-v3-Konto mit projektbezogenem Zugriff auf einen vorhandenen Swift-Container und ein darin enthaltenes Objekt. Stellen Sie diese Umgebungsvariablen über die Zugangsdatenverwaltung Ihres Anbieters oder einen Secret-Manager bereit:
| Variable | Wert |
|---|---|
OS_AUTH_URL | Keystone-URL mit der Endung /v3 |
OS_USERNAME, OS_PASSWORD | Ihr Benutzername und Passwort |
OS_USER_DOMAIN_NAME | Domain, zu der der Benutzer gehört |
OS_PROJECT_NAME, OS_PROJECT_DOMAIN_NAME | Projekt und dessen Domain |
OS_REGION_NAME | Region mit dem öffentlichen Objektspeicher-Endpunkt |
Benutzer und Projekt können unterschiedlichen Domains angehören. Verwenden Sie HTTPS sowohl für Keystone als auch für den Swift-Endpunkt in dessen Dienstkatalog, mit Zertifikaten, denen die JVM vertraut. Nehmen Sie Passwörter nicht in Commits auf und setzen Sie sie nicht in kopierte Shell-Befehle ein. Diese Anleitung liest ein vorhandenes Objekt; sie erstellt keine Container und ändert keine Zugriffsrichtlinien.
Prüfen Sie jedes ausführbare Programm, bevor Sie das Projekt erstellen. Fügen Sie diesen Block in
Ihre bestehende Shell ein; die Subshell lässt deren Arbeitsverzeichnis und Optionen unverändert.
Ist das Verzeichnis swift-import bereits vorhanden, schlägt die Einrichtung fehl,
ohne dessen Inhalt zu ändern.
(
cd -P . &&
swift_parent=$PWD &&
while :; do
if [ -e "$swift_parent/.mvn" ] || [ -L "$swift_parent/.mvn" ]; then
printf 'Run setup outside an existing Maven project.\n' >&2
exit 1
fi
[ "$swift_parent" = / ] && break
swift_parent=${swift_parent%/*}
[ -n "$swift_parent" ] || swift_parent=/
done &&
command -v java >/dev/null &&
command -v javac >/dev/null &&
command -v mvn >/dev/null &&
command -v locale >/dev/null &&
[ "$(locale charmap)" = UTF-8 ] &&
mkdir swift-import &&
cd swift-import &&
mkdir -p src/main/java
)
Speichern Sie die folgende vollständige Datei als swift-import/pom.xml. OpenStack4j 3.12 ist
die veröffentlichte Release-Version in Maven Central.
Dieses Artefakt enthält den RESTEasy-Konnektor.
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>swift-import</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.github.openstack4j.core</groupId>
<artifactId>openstack4j</artifactId>
<version>3.12</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.14.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.8.1</version>
</plugin>
</plugins>
</build>
</project>
Der Schutzmechanismus für die Einrichtung lehnt eine Konfiguration in
.mvn in übergeordneten Verzeichnissen ab, bevor er Dateien erstellt. Maven
kann Startoptionen aus einem umgebenden Projekt übernehmen; prüfen Sie diese, bevor Sie dieses
Beispiel in eine bestehende Anwendung integrieren. Falls die Auflösung der Abhängigkeiten
fehlschlägt, beheben Sie Probleme beim Netzwerk- oder Repository-Zugriff und wiederholen Sie den
Build-Befehl unten. Behalten Sie den gespeicherten Quellcode und pom.xml.
Mit OpenStack Swift verbinden
Speichern Sie dieses Programm als swift-import/src/main/java/SwiftImport.java. Es authentifiziert sich bei Keystone,
legt den Geltungsbereich des Tokens auf das benannte Projekt und dessen Domain fest und wählt die
angeforderte Region aus. Dies sind getrennte Entscheidungen im
Identitätsmodell von Keystone.
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import org.openstack4j.api.OSClient.OSClientV3;
import org.openstack4j.api.exceptions.ResponseException;
import org.openstack4j.core.transport.Config;
import org.openstack4j.core.transport.HttpResponse;
import org.openstack4j.model.common.Identifier;
import org.openstack4j.model.storage.object.SwiftObject;
import org.openstack4j.openstack.OSFactory;
public class SwiftImport {
private static String encodeName(String value, boolean object) {
String[] segments = value.split("/", -1);
if (!object && segments.length != 1) {
throw new IllegalArgumentException("A container name cannot contain a slash");
}
for (int index = 0; index < segments.length; index++) {
String segment = segments[index];
if (segment.isEmpty() || segment.equals(".") || segment.equals("..")) {
throw new IllegalArgumentException("Empty and dot path segments are unsupported");
}
segments[index] = URLEncoder.encode(segment, StandardCharsets.UTF_8).replace("+", "%20");
}
return String.join("/", segments);
}
private static String required(String name) {
String value = System.getenv(name);
if (value == null || value.isBlank()) {
throw new IllegalArgumentException("Missing environment variable: " + name);
}
return value;
}
private static long download(OSClientV3 os, String container, String object,
Path destination) throws IOException {
SwiftObject remote = os.objectStorage().objects().get(container, object);
if (remote == null) {
throw new IOException("Object not found");
}
long expected = remote.getSizeInBytes();
Path partial = Files.createTempFile(destination.getParent(), ".swift-import-", ".part");
try {
long written;
// The download API exposes its response without mapping HTTP errors to exceptions.
try (HttpResponse response = remote.download().getHttpResponse()) {
if (response.getStatus() != 200) {
throw new IOException("Download rejected (HTTP " + response.getStatus() + ")");
}
try (InputStream input = response.getInputStream()) {
if (input == null) {
throw new IOException("Missing download stream");
}
written = Files.copy(input, partial, StandardCopyOption.REPLACE_EXISTING);
}
}
if (written != expected) {
throw new IOException("Downloaded byte count does not match object metadata");
}
Files.move(partial, destination,
StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
return written;
} finally {
Files.deleteIfExists(partial);
}
}
public static void main(String[] args) {
if (args.length != 3) {
System.err.println("Usage: SwiftImport CONTAINER OBJECT DESTINATION");
System.exit(2);
}
try {
String container = encodeName(args[0], false);
String object = encodeName(args[1], true);
Config config = Config.newConfig().withConnectionTimeout(10_000);
OSClientV3 os = OSFactory.builderV3()
.endpoint(required("OS_AUTH_URL"))
.credentials(required("OS_USERNAME"), required("OS_PASSWORD"),
Identifier.byName(required("OS_USER_DOMAIN_NAME")))
.scopeToProject(Identifier.byName(required("OS_PROJECT_NAME")),
Identifier.byName(required("OS_PROJECT_DOMAIN_NAME")))
.withConfig(config)
.authenticate()
.useRegion(required("OS_REGION_NAME"));
Path destination = Path.of(args[2]).toAbsolutePath();
long written = download(os, container, object, destination);
System.out.println("Imported " + written + " bytes to " + destination);
} catch (ResponseException error) {
System.err.println("OpenStack request failed (HTTP " + error.getStatus() + ").");
System.exit(1);
} catch (Exception error) {
System.err.println("Import failed (" + error.getClass().getSimpleName()
+ "). Check configuration, object access, and local storage before retrying.");
System.exit(1);
}
}
}
Dateien aus Swift importieren
Fügen Sie den folgenden Block im Verzeichnis ein, das swift-import enthält. Ersetzen
Sie exports und reports/report.bin durch Ihren vorhandenen Container
und den exakten Objektnamen. Das lokale Ziel ist relativ zu swift-import, und sein
übergeordnetes Verzeichnis muss bereits vorhanden sein.
Übergeben Sie unveränderte Container- und Objektnamen in UTF-8, einschließlich Leerzeichen oder
wörtlicher Prozentzeichen. Das Programm führt vor dem Aufruf von OpenStack4j für jedes
URL-Pfadsegment eine URL-Kodierung durch. Containernamen dürfen / nicht
enthalten; Objektpfade dürfen in diesem Beispiel keine leeren Segmente und keine Segmente mit
. oder .. enthalten. Auch lokale Zielpfade
müssen gültiges UTF-8 verwenden.
(
cd swift-import &&
mvn -B package dependency:copy-dependencies &&
java -cp 'target/classes:target/dependency/*' SwiftImport \
'exports' 'reports/report.bin' './download.bin'
)
Bei Erfolg werden die importierte Bytezahl und der absolute Zielpfad ausgegeben. Ein leeres Objekt
erzeugt eine Datei mit null Bytes und meldet null importierte Bytes. Ein fehlgeschlagener Build
verhindert die Ausführung des Java-Befehls, auch wenn eine ältere kompilierte Klasse in
target/ verbleibt.
Das Programm ermittelt die erwartete Größe anhand der HEAD-Antwort von Swift, verlangt einen erfolgreichen vollständigen GET-Abruf und vergleicht die kopierte Bytezahl auch dann, wenn die erwartete Größe null beträgt. Swift dokumentiert diese Vorgänge in seiner Objekt-API-Referenz. Lassen Sie das entfernte Objekt während des Imports unverändert. Ein Größenvergleich kann Inhaltsänderungen bei gleicher Größe nicht erkennen; vergleichen Sie separat eine vertrauenswürdige Prüfsumme des Herausgebers, wenn Sie diese Prüfung benötigen.
Ein vorhandenes Ziel wird nur durch einen vollständig heruntergeladenen und auf seine Größe geprüften Download ersetzt. Die temporäre Datei liegt im selben Verzeichnis, damit sie durch eine atomare Verschiebung bereitgestellt werden kann. Kann das Dateisystem diese Verschiebung nicht ausführen, schlägt der Import fehl, ohne auf eine nicht atomare Kopie zurückzugreifen. In Java ist das Verhalten beim atomaren Ersetzen dateisystemabhängig; der hier verwendete Ablauf zum Ersetzen wurde unter Linux getestet. Verwenden Sie einen Importer pro Ziel und behalten Sie die Kontrolle über dessen übergeordnetes Verzeichnis.
Große Dateien effizient verarbeiten
Der standardmäßige RESTEasy-Konnektor in OpenStack4j 3.12 puffert die vollständige Antwort, bevor er einen Eingabestream bereitstellt. Wählen Sie Objekte, die problemlos in den verfügbaren Arbeitsspeicher der JVM passen. Ein größerer Kopierpuffer oder Fortschrittsausgaben aus der Kopierschleife beseitigen diese Speicherbelegung nicht.
Fehlerbehandlung und Retries implementieren
Der Befehl gibt bei fehlenden Objekten, abgelehnten Lesezugriffen, unvollständigen Übertragungen und
lokalen Schreibfehlern einen Status ungleich null zurück. Er unternimmt einen Versuch auf
Anwendungsebene und fordert ein Verbindungszeitlimit von 10 Sekunden an. Für die gesamte Übertragung
gibt es kein Zeitlimit. In dieser RESTEasy-Version
konfiguriert withReadTimeout das Beziehen einer Verbindung aus dem Pool
anstelle eines Zeitlimits für das Lesen vom Socket und wird daher hier weggelassen. Bei einer
stockenden Antwort kann das Warten andauern, bis die Verbindung geschlossen wird. Verwenden Sie
einen überwachten Prozess, wenn Ihre Anwendung eine Begrenzung der verstrichenen Zeit benötigt.
Beheben Sie nach einem behandelten Fehler dessen Ursache und wiederholen Sie denselben Block zum
Bauen und Ausführen. Wenn ein Download fehlschlägt, wird die unvollständige Datei entfernt und ein
vorheriges Ziel bleibt intakt. Ein erzwungenes Beenden des Prozesses oder ein Rechnerabsturz kann
eine Datei .swift-import-*.part hinterlassen; entfernen Sie nur die zurückgelassene Datei,
die zu Ihrem gestoppten Import gehört. Löschen Sie temporäre Dateien nicht pauschal, während ein
anderer Importer aktiv ist.
Hinweise zur Fehlerbehandlung
- Prüfen Sie bei einem Authentifizierungsfehler die Keystone-v3-URL, die Benutzerdomain, die Projektdomain und die Zugangsdaten.
- Prüfen Sie bei einem fehlenden Objekt oder verweigerten Zugriff dessen exakten Namen und den Lesezugriff Ihres Projekts. Eine erfolgreiche Anmeldung bedeutet keine Berechtigung zum Download.
- Prüfen Sie bei einem lokalen E/A-Fehler das vorhandene übergeordnete Verzeichnis des Ziels, den Schreibzugriff, den freien Speicherplatz und die Unterstützung für atomare Verschiebungen. Vermeiden Sie es, ein vorheriges Ziel zu löschen, um einen fehlgeschlagenen Download zu umgehen.
Tipps zur Leistungsoptimierung
Führen Sie Authentifizierung und Downloads auf demselben Thread aus. OpenStack4j speichert die aktive Sitzung in einem threadlokalen Wert; seine Threading-Anleitung erklärt, wie Sie auf einem anderen Thread einen Client aus einem Token ableiten. Dieser Befehl für ein einzelnes Objekt verwendet einen Thread und implementiert weder einen Worker-Pool noch automatische Retries.
Halten Sie bei der Integration in eine Anwendung Download, Byteprüfung und Bereitstellung zusammen.
Geben Sie den lokalen Pfad erst an den weiterverarbeitenden Code zurück, nachdem
download erfolgreich ausgeführt wurde.
