Importer des fichiers Amazon S3 en Java
Pour importer un objet Amazon S3 dans un fichier local, utilisez getObject
d’AWS SDK for Java v2 avec un transformateur de réponse vers un fichier. L’importateur ci-dessous
télécharge dans un emplacement temporaire, vérifie une somme de contrôle SHA-256 de confiance et
ne publie le fichier de destination qu’après validation du téléchargement complet. Il prend
également en charge le client asynchrone standard, le client AWS CRT et S3 Transfer Manager.
Introduction à Amazon S3 et Java
Cet exemple récupère une clé connue dans un bucket S3 à usage général. Vous avez besoin de la région du bucket, de l’autorisation de lire cet objet et d’une somme de contrôle SHA-256 fournie par son éditeur ou calculée à partir du fichier d’origine avant son téléversement. Conservez l’objet inchangé pendant le téléchargement. La somme de contrôle identifie les octets que vous souhaitez importer, y compris ceux d’un objet vide ou d’un fichier binaire.
Configurer AWS SDK v2
Les commandes utilisent Bash sous Linux, la locale C.UTF-8 et un JDK et Maven
installés séparément. La locale UTF-8 préserve les arguments contenant des caractères non ASCII
dans les clés et les noms de fichiers. Les versions testées sont OpenJDK 21.0.12.1,
Maven 3.10.0, AWS SDK v2 2.55.13 et AWS CRT 0.48.5. Les versions des
dépendances ci-dessous sont figées pour rendre les tests reproductibles ; consultez les
versions d’AWS SDK lorsque vous les mettez à jour.
Créez un nouveau projet en dehors d’une copie de travail Maven contenant un répertoire
.mvn. Maven peut hériter des options JVM, des extensions et des options de
commande de ce répertoire. Ce bloc de commandes vérifie les outils et refuse cette configuration
héritée avant de créer des fichiers ; il laisse votre shell dans son répertoire actuel. Si
s3-import existe déjà, utilisez un autre nouveau répertoire au lieu de supprimer
ses fichiers.
(
for tool in java javac mvn; do
command -v "$tool" >/dev/null || {
printf 'Missing prerequisite: %s\n' "$tool" >&2
exit 1
}
done
parent="$PWD"
while :; do
[ ! -e "$parent/.mvn" ] || {
printf 'Use a directory outside the enclosing Maven configuration.\n' >&2
exit 1
}
[ "$parent" != / ] || break
parent="${parent%/*}"
[ -n "$parent" ] || parent=/
done
mkdir s3-import && mkdir -p s3-import/src/main/java
)
Enregistrez ce fichier pom.xml complet dans s3-import/.
La BOM maintient les modules du SDK à la même version ; CRT possède sa propre version. Le plugin de
gestion des dépendances copie les JAR nécessaires à l’exécution par le lanceur Java.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>s3-import</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>bom</artifactId>
<version>2.55.13</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3-transfer-manager</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk.crt</groupId>
<artifactId>aws-crt</artifactId>
<version>0.48.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.36</version>
<scope>runtime</scope>
</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>
<executions>
<execution>
<phase>package</phase>
<goals><goal>copy-dependencies</goal></goals>
<configuration><includeScope>runtime</includeScope></configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Configurer les informations d’accès AWS
Configurez les informations d’accès au moyen de la connexion AWS existante de votre organisation ou
du rôle de l’environnement d’exécution. Ce programme utilise la
chaîne de fournisseurs d’informations d’accès par défaut du SDK,
qui vérifie les propriétés système Java, les variables d’environnement, les profils partagés et les
rôles de l’environnement d’exécution. Les informations d’accès temporaires fournies par
l’environnement nécessitent les trois valeurs : AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY et AWS_SESSION_TOKEN. Ne les placez pas dans le code source
Java ni dans les fichiers Maven.
Les dépendances de l’exemple prennent en charge les informations d’accès fournies par
l’environnement et les profils partagés de base. Un profil qui obtient des informations d’accès
via SSO ou AssumeRole nécessite également les modules SDK correspondants, tels que
sso, ssooidc ou sts, à la
version définie par la BOM. Effectuez cette connexion avant d’exécuter l’importateur.
Configurer la région AWS
Transmettez la région du bucket comme deuxième argument du programme, par exemple
us-east-1. Chaque client utilise Region.of(regionName) et ne dépend donc
pas d’une région définie dans votre shell ou votre profil partagé. L’argument de clé est la clé S3
littérale, et non une URL s3:// ; n’encodez pas vous-même les espaces ou les
signes de pourcentage pour une URL.
Importer des fichiers depuis S3
Enregistrez ce code dans s3-import/src/main/java/S3FileImporter.java :
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.util.HexFormat;
import java.util.Set;
import java.util.concurrent.CompletionException;
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
import software.amazon.awssdk.core.sync.ResponseTransformer;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.DownloadFileRequest;
public class S3FileImporter {
public static void main(String[] args) {
try {
run(args);
} catch (Exception error) {
Throwable cause = error;
while (cause instanceof CompletionException && cause.getCause() != null) {
cause = cause.getCause();
}
if (cause instanceof S3Exception s3Error) {
System.err.println("S3 download failed (HTTP " + s3Error.statusCode() + ").");
} else if (cause instanceof IOException || cause instanceof IllegalArgumentException) {
System.err.println(cause.getMessage());
} else {
System.err.println("Download failed; check credentials, connectivity, and local disk space.");
}
System.exit(1);
}
}
private static void run(String[] args) throws Exception {
if (args.length != 6 || !Set.of("sync", "async", "crt", "transfer").contains(args[0])) {
throw new IllegalArgumentException(
"Usage: S3FileImporter <sync|async|crt|transfer> <region> <bucket> <key> <destination> <sha256>");
}
String expectedHash = args[5];
if (!expectedHash.matches("[0-9a-fA-F]{64}")) {
throw new IllegalArgumentException("Supply the original object's 64-digit SHA-256.");
}
Path destination = Path.of(args[4]).toAbsolutePath();
if (Files.exists(destination, LinkOption.NOFOLLOW_LINKS)) {
throw new FileAlreadyExistsException("Destination already exists: " + destination);
}
GetObjectRequest request = GetObjectRequest.builder().bucket(args[2]).key(args[3]).build();
Region region = Region.of(args[1]);
Path stagingDirectory = Files.createTempDirectory(destination.getParent(), ".s3-import-");
Path stagedFile = stagingDirectory.resolve("object.bin");
try {
download(args[0], region, request, stagedFile);
if (!sha256(stagedFile).equalsIgnoreCase(expectedHash)) {
throw new IOException("SHA-256 mismatch; destination was not published.");
}
Files.move(stagedFile, destination);
System.out.println("Downloaded " + Files.size(destination) + " bytes to " + destination);
} finally {
Files.deleteIfExists(stagedFile);
Files.delete(stagingDirectory);
}
}
private static void download(String mode, Region region, GetObjectRequest request, Path file) {
if (mode.equals("sync")) {
try (S3Client client = S3Client.builder().region(region)
.overrideConfiguration(c -> c.retryStrategy(r -> r.maxAttempts(1))).build()) {
client.getObject(request, ResponseTransformer.toFile(file));
}
return;
}
if (mode.equals("async")) {
try (S3AsyncClient client = S3AsyncClient.builder().region(region)
.overrideConfiguration(c -> c.retryStrategy(r -> r.maxAttempts(1))).build()) {
client.getObject(request, AsyncResponseTransformer.toFile(file)).join();
}
return;
}
try (S3AsyncClient client = S3AsyncClient.crtBuilder().region(region)
.minimumPartSizeInBytes(8L * 1024 * 1024).maxConcurrency(2)
.retryConfiguration(r -> r.numRetries(0)).build()) {
if (mode.equals("crt")) {
client.getObject(request, AsyncResponseTransformer.toFile(file)).join();
} else {
try (S3TransferManager manager = S3TransferManager.builder().s3Client(client).build()) {
manager.downloadFile(DownloadFileRequest.builder()
.getObjectRequest(request).destination(file).build()).completionFuture().join();
}
}
}
}
private static String sha256(Path file) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
try (InputStream input = Files.newInputStream(file)) {
byte[] buffer = new byte[8192];
int count;
while ((count = input.read(buffer)) != -1) {
digest.update(buffer, 0, count);
}
}
return HexFormat.of().formatHex(digest.digest());
}
}
Depuis s3-import/, compilez et exécutez le mode synchrone. Remplacez le bucket,
la clé, la région et la somme de contrôle par les valeurs de votre objet. Le répertoire parent de la
destination doit exister, et la destination elle-même doit être nouvelle. Le chemin de classes entre
guillemets conserve le caractère générique pour que Java le développe.
mvn -B package &&
LC_ALL=C.UTF-8 java -cp 'target/classes:target/dependency/*' S3FileImporter \
sync us-east-1 your-bucket-name 'path/to/your/file.bin' \
'./downloaded-file.bin' 'REPLACE_WITH_ORIGINAL_SHA256'
Une commande réussie affiche le nombre d’octets téléchargés et la destination, puis se termine avec
un code de sortie égal à zéro. La vérification de la somme de contrôle s’exécute après que le SDK a
terminé le fichier et avant que Files.move le publie. Elle lit le fichier complet ;
son existence, un préfixe non vide ou un résultat asynchrone achevé ne suffisent donc pas. N’obtenez
pas la somme de contrôle attendue en calculant le hachage du fichier téléchargé lui-même.
Gérer les exceptions
La commande se termine avec un code de sortie égal à un si la destination est refusée, si la somme de contrôle est incorrecte, si le SDK échoue ou si une erreur d’E/S locale survient. Un échec de téléchargement ou de vérification de la somme de contrôle intercepté supprime le fichier temporaire et ne laisse aucune nouvelle destination finale. Une destination existante est préservée. Exécutez les téléchargements successivement pour une destination donnée ; cet exemple ne coordonne pas les publications concurrentes.
Pour une clé manquante, S3 renvoie 404 lorsque l’appelant dispose également de
s3:ListBucket, et peut renvoyer 403 sans cette autorisation. Un code 403 ne permet
donc pas de distinguer une clé inexistante d’un accès refusé. Vérifiez la clé exacte et les
autorisations à l’aide des règles d’autorisation de GetObject.
En cas de somme de contrôle différente, confirmez le hachage fourni par l’éditeur et vérifiez que
l’objet n’a pas changé avant de réessayer avec une nouvelle destination. En cas d’échec lié aux
informations d’accès ou au réseau, corrigez ce prérequis et relancez la commande. Ne considérez pas
une exception affichée comme une importation réussie.
Types de clients S3
Choisissez le mode en modifiant uniquement le premier argument du programme et en utilisant une nouvelle destination.
Client synchrone (S3Client)
sync bloque dans getObject jusqu’à ce que le
transformateur de réponse vers un fichier ait terminé. C’est un point de départ utile pour une
commande qui importe un objet.
Client asynchrone (S3AsyncClient)
async utilise le client asynchrone Java standard. Dans une application, son
résultat asynchrone permet à d’autres tâches de se poursuivre pendant le téléchargement. Cette
commande appelle join() avant de vérifier le fichier, de fermer le client ou
de se terminer ; sinon, le processus pourrait se terminer avant la fin de l’importation.
Client aux performances améliorées (AWS CRT)
crt utilise le client S3 basé sur AWS CRT,
une implémentation de S3AsyncClient qui peut récupérer de grands objets par plages
d’octets. Sa dépendance native est incluse dans la compilation Maven. Il possède moins d’options de
configuration que le client standard. Mesurez votre charge de travail avant de le choisir pour ses
performances ; ces commandes ne promettent aucun débit de transfert particulier.
Optimiser les performances
Écrire en continu sur le disque
Les quatre modes utilisent des fichiers de destination au lieu de charger l’objet entier dans un
tableau d’octets Java. La passe de calcul SHA-256 lit également les données dans des tampons de
taille fixe. Prévoyez de l’espace disque pour l’objet entier et utilisez un répertoire de
destination accessible en écriture. Si le processus est arrêté de force, inspectez et supprimez
le répertoire .s3-import-* qu’il a laissé avant de réessayer ; le bloc Java
finally ne peut pas effectuer le nettoyage d’un processus arrêté de force.
Téléchargements multiparties
transfer utilise S3 Transfer Manager
avec le client CRT explicitement fourni. Les téléchargements volumineux peuvent utiliser des
requêtes parallèles par plages d’octets ; l’exemple définit une taille minimale de partie de 8 MiB
et un maximum de deux connexions. Il s’agit de plages de téléchargement, et non de téléversements
multiparties S3. Le programme attend le résultat asynchrone de fin du gestionnaire avant de vérifier
la somme de contrôle, et ferme le gestionnaire avant son client.
Les nouvelles tentatives sont désactivées dans cette démonstration afin qu’une requête échouée soit signalée à l’appelant. Dans une application, choisissez une politique de nouvelles tentatives limitée et des délais d’expiration pour le client sélectionné, sans supposer que les constructeurs standard et CRT acceptent des paramètres identiques. Sinon, un téléchargement peut rester en attente sur une connexion lente ; ce programme ne définit pas de délai maximal pour le transfert complet.
Autorisations IAM
Pour la version actuelle d’une clé connue dans un bucket à usage général, accordez
s3:GetObject sur l’ARN de l’objet. Lister le contenu du bucket n’est pas nécessaire
pour cet importateur. Par exemple, demandez à votre administrateur AWS d’adapter cette politique à
l’objet dont vous avez besoin :
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::your-bucket-name/path/to/your/file.bin"
}
]
}
Ce guide suppose que l’objet est immédiatement récupérable et chiffré avec des clés gérées par S3. Les objets archivés peuvent nécessiter une restauration, et le chiffrement KMS ajoute des exigences d’autorisation sur les clés. Consultez la correspondance des autorisations du guide utilisateur S3 pour ces cas. Ce programme ne demande pas d’identifiant de version particulier et ne gère pas les clés SSE-C.
Bonnes pratiques
Conservez une somme de contrôle de confiance à côté de chaque objet que vous souhaitez importer. Si vous intégrez ensuite cette commande dans une tâche, transmettez son code de sortie non nul à l’ordonnanceur et autorisez le traitement en aval à lire uniquement la destination finale vérifiée. Sélectionnez des clés immuables ou ajoutez une récupération tenant compte des versions lorsque votre éditeur remplace les objets sur place.
Pour importer des objets S3 dans un flux de traitement de fichiers géré, consultez le S3 Import Robot (English) et le SDK Java de Transloadit.
