Importar arquivos do MinIO em Java
Baixe um objeto do MinIO para um arquivo temporário e publique-o no destino solicitado depois de ler e fechar a resposta. Este comando Java mantém um destino existente intacto e encerra com status 1 se a importação falhar. Uma demonstração local primeiro faz upload de bytes binários conhecidos, para que você possa conferir o que o importador realmente salvou.
Configurar seu ambiente Java
Este tutorial usa o SDK Java do MinIO com um endpoint auto-hospedado e uma única chave exata de objeto. Para saber sobre credenciais da AWS, regiões e o comportamento do gerenciador de transferências, consulte o guia de Amazon S3 com Java (English).
Use Linux, Bash, OpenJDK 21.0.12.1 e Maven 3.9.16. O exemplo usa o
MinIO Java SDK 9.0.3. O Maven baixa o SDK e os plugins de build do
Maven Central. Defina JAVA_HOME com o caminho da sua instalação do JDK se o
Maven usar uma versão diferente do Java.
Para a demonstração local descartável, tenha também o cURL com suporte a AWS Signature V4 e um
binário do servidor minio no PATH. A reexecução
usou cURL 8.22.0 e MinIO compilado a partir da revisão 9e49d5e7a648.
O MinIO Community Edition foi arquivado e não recebe mais manutenção.
Este ambiente de teste serve para aprendizado local, não é uma recomendação para uma nova
implantação em produção. Se você já gerencia um serviço MinIO, pode usar o importador diretamente
com esse serviço.
Escolha um diretório pai com permissão de escrita. Cole este bloco nele; o bloco cria um novo
projeto e mantém seu shell no diretório pai. Se minio-import já existir, ele
interrompe a execução antes de gravar qualquer coisa dentro desse diretório.
(
set -eu
mkdir minio-import
cd minio-import
mkdir -p .mvn src/main/java
printf '<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"/>\n' > settings.xml
)
Salve os arquivos a seguir dentro de minio-import. O diretório
.mvn próprio do projeto impede que o Maven use a configuração
.mvn de um projeto que o englobe. O script de inicialização usa
configurações vazias e um repositório .m2 local ao projeto, portanto
não atualiza seu cache habitual de dependências do Maven.
Integrar o SDK Java do MinIO
Salve este pom.xml completo. A versão-alvo do compilador e as versões dos
plugins são explícitas; não é necessário um POM pai de um projeto que o englobe.
O OkHttp 5 exige que seu artefato JVM seja incluído explicitamente no Maven;
okhttp-jvm aqui corresponde à versão usada pelo SDK.
<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>minio-import</artifactId>
<version>1.0</version>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>9.0.3</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp-jvm</artifactId>
<version>5.3.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.1</version>
</plugin>
<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.9.0</version>
</plugin>
</plugins>
</build>
</project>
Importar arquivos do MinIO: um guia passo a passo
Salve este arquivo como src/main/java/MinIOFileImporter.java. Os argumentos são o bucket, a chave exata
do objeto e o destino local. Passe a chave como ela foi armazenada, por exemplo,
reports/April report.bin; não a transforme em uma URL nem aplique codificação percentual
por conta própria.
import io.minio.GetObjectArgs;
import io.minio.GetObjectResponse;
import io.minio.MinioClient;
import io.minio.errors.ErrorResponseException;
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
public class MinIOFileImporter {
public static void main(String[] args) {
try {
long bytes = download(args);
System.out.println("Imported " + bytes + " bytes.");
} catch (FileAlreadyExistsException e) {
System.err.println("Destination exists; choose a new path.");
System.exit(1);
} catch (ErrorResponseException e) {
System.err.println("Storage refused the download (HTTP " + e.response().code() + ").");
System.exit(1);
} catch (Exception e) {
System.err.println("Import failed (" + e.getClass().getSimpleName() + "). Check configuration, network, and disk.");
System.exit(1);
}
}
private static String required(String name) {
String value = System.getenv(name);
if (value == null || value.isBlank()) {
throw new IllegalArgumentException("Missing " + name);
}
return value;
}
private static long download(String[] args) throws Exception {
if (args.length != 3 || args[0].isBlank() || args[1].isEmpty() || args[2].isBlank()) {
throw new IllegalArgumentException("Expected bucket, object key, and destination");
}
String endpoint = required("MINIO_ENDPOINT");
String region = required("MINIO_REGION");
String accessKey = required("MINIO_ACCESS_KEY");
String secretKey = required("MINIO_SECRET_KEY");
Path destination = Path.of(args[2]).toAbsolutePath();
if (Files.exists(destination, LinkOption.NOFOLLOW_LINKS)) {
throw new FileAlreadyExistsException(destination.toString());
}
if (!Files.isDirectory(destination.getParent())) {
throw new IOException("Destination parent must already exist");
}
Path staged = Files.createTempFile(destination.getParent(), ".minio-", ".part");
try {
long copied;
try (MinioClient client = MinioClient.builder()
.endpoint(endpoint).region(region).credentials(accessKey, secretKey).build()) {
client.setTimeout(10_000, 30_000, 30_000);
try (GetObjectResponse response = client.getObject(
GetObjectArgs.builder().bucket(args[0]).object(args[1]).build())) {
String length = response.headers().get("Content-Length");
if (length == null) throw new IOException("Missing Content-Length");
long expected = Long.parseLong(length);
copied = Files.copy(response, staged, StandardCopyOption.REPLACE_EXISTING);
if (expected < 0 || copied != expected) {
throw new IOException("Incomplete response body");
}
}
}
Files.move(staged, destination);
return copied;
} finally {
Files.deleteIfExists(staged);
}
}
}
O contrato de getObject do SDK exige que a
resposta retornada seja fechada. Aqui, tanto a resposta quanto o cliente são fechados antes da
publicação. Um objeto vazio é válido e produz um arquivo de zero bytes. A comparação de tamanho
detecta um corpo incompleto; ela não é uma soma de verificação independente nem uma prova de que
o servidor armazenou o conteúdo pretendido.
Salve download.sh na raiz do projeto. Sempre execute esse script a partir
desse diretório. Ele compila a classe publicada e copia as dependências de execução antes de
iniciar o Java. O shell interrompe a execução se o build falhar, mesmo que uma classe compilada
anteriormente permaneça no local.
#!/usr/bin/env bash
set -eu
if [ ! -d .mvn ] || [ ! -f settings.xml ] || [ ! -f src/main/java/MinIOFileImporter.java ]; then
printf 'Run download.sh from the minio-import project root.\n' >&2
exit 1
fi
if [ "$#" -ne 3 ]; then
printf 'Usage: bash download.sh BUCKET OBJECT_KEY DESTINATION\n' >&2
exit 1
fi
MAVEN_SKIP_RC=1 MAVEN_ARGS= MAVEN_OPTS= MAVEN_BASEDIR="$PWD" \
mvn --batch-mode --no-transfer-progress --settings settings.xml \
--global-settings settings.xml -Dmaven.repo.local="$PWD/.m2" \
compile dependency:copy-dependencies -DincludeScope=runtime
java -cp 'target/classes:target/dependency/*' MinIOFileImporter "$@"
Use um diretório local que você controla e execute um importador por destino. A operação de mover
omite REPLACE_EXISTING, seguindo a
política de movimentação do Java. Este procedimento sequencial
não promete publicação concorrente nem durabilidade em caso de interrupção abrupta. Quando uma
falha de transferência é tratada, os arquivos temporários são removidos e o destino permanece
inexistente. Uma JVM encerrada à força pode deixar um arquivo .minio-*.part;
remova-o somente depois de confirmar que o importador parou. Uma falha no sistema de arquivos
durante a publicação ou a limpeza pode exigir que você inspecione o diretório antes de tentar
novamente.
Verificar um objeto local do upload ao download
Salve demo.sh na raiz do projeto. Ele inicia um servidor privado em
loopback com credenciais descartáveis, faz upload de um arquivo binário de teste de oito bytes
pelo cURL e invoca download.sh. Ele mantém o arquivo de teste, o log e os dados
do servidor e o arquivo baixado para inspeção, e para seu servidor ao encerrar. Ele não contata um
endpoint público de demonstração.
#!/usr/bin/env bash
set -eu
port=${1:-19000}
case "$port" in ''|*[!0-9]*) printf 'Supply a numeric local port.\n' >&2; exit 1;; esac
if [ "$port" -lt 1024 ] || [ "$port" -gt 65535 ]; then
printf 'Use a local port from 1024 through 65535.\n' >&2
exit 1
fi
if [ ! -f download.sh ] || [ ! -d .mvn ]; then
printf 'Run demo.sh from the minio-import project root.\n' >&2
exit 1
fi
command -v minio >/dev/null
command -v curl >/dev/null
mkdir demo-data
mkdir demo-certs
export MINIO_ENDPOINT="http://127.0.0.1:$port" MINIO_REGION=us-east-1
export MINIO_ACCESS_KEY=local-demo MINIO_SECRET_KEY=local-demo-secret
MINIO_ROOT_USER="$MINIO_ACCESS_KEY" MINIO_ROOT_PASSWORD="$MINIO_SECRET_KEY" \
MINIO_BROWSER=off minio server demo-data --certs-dir demo-certs \
--address "127.0.0.1:$port" \
> demo-server.log 2>&1 &
server_pid=$!
stop_server() {
kill "$server_pid" 2>/dev/null || :
wait "$server_pid" 2>/dev/null || :
}
trap stop_server EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
ready=0
for attempt in {1..100}; do
if ! kill -0 "$server_pid" 2>/dev/null; then
printf 'MinIO startup failed; inspect demo-server.log.\n' >&2
exit 1
fi
if grep -q '^API:' demo-server.log && \
curl -fsS --max-time 1 "$MINIO_ENDPOINT/minio/health/ready" >/dev/null 2>&1; then
ready=1
break
fi
sleep 0.1
done
if [ "$ready" -ne 1 ]; then
printf 'MinIO did not become ready; inspect demo-server.log.\n' >&2
exit 1
fi
printf '\000\377MinIO\n' > sample.bin
curl -fsS --max-time 10 --aws-sigv4 'aws:amz:us-east-1:s3' \
--user "$MINIO_ACCESS_KEY:$MINIO_SECRET_KEY" -X PUT "$MINIO_ENDPOINT/demo-bucket"
curl -fsS --max-time 10 --aws-sigv4 'aws:amz:us-east-1:s3' \
--user "$MINIO_ACCESS_KEY:$MINIO_SECRET_KEY" --upload-file sample.bin \
"$MINIO_ENDPOINT/demo-bucket/fixtures/sample.bin"
bash download.sh demo-bucket 'fixtures/sample.bin' ./downloaded.bin
cmp sample.bin downloaded.bin
printf 'Verified downloaded.bin against sample.bin.\n'
A opção --aws-sigv4 do cURL assina as
requisições de criação de bucket e upload do ambiente de teste independentemente do programa
Java que faz o download. Escolha uma porta local que não esteja em uso e cole este bloco a partir
do diretório pai:
(cd minio-import && bash demo.sh 19000)
Após a saída do build do Maven, espere estas linhas e o status de saída 0:
Imported 8 bytes.
Verified downloaded.bin against sample.bin.
Repetir a demonstração completa interrompe a execução em mkdir demo-data e
retorna falha, preservando os arquivos anteriores. Use um novo diretório de projeto para outra
demonstração completa. O próprio importador também recusa um downloaded.bin já
existente; ele nunca remove esse arquivo para que uma nova tentativa tenha sucesso.
Usar seu endpoint MinIO existente
Forneça MINIO_ENDPOINT, MINIO_REGION,
MINIO_ACCESS_KEY e MINIO_SECRET_KEY por meio do ambiente do seu
processo, usando credenciais com permissão s3:GetObject para o objeto
selecionado. Use o endpoint da API S3, não o endereço do console web. Mantenha a região compatível
com seu servidor. O HTTP e as credenciais de root acima se destinam apenas ao ambiente de teste
em loopback; use HTTPS e o mecanismo de credenciais da sua organização para o serviço que você
gerencia. Não desative a verificação de certificados para contornar um erro no repositório de
certificados confiáveis.
Depois de configurar o ambiente, cole este bloco a partir da raiz do projeto, substituindo o bucket e a chave pelos de um objeto que já exista:
bash download.sh my-bucket 'reports/April report.bin' './April report.bin'
Tratar exceções comuns
O status 0 significa que a resposta foi lida por completo, os recursos foram fechados e o destino foi publicado. O status 1 significa que o comando falhou; o diagnóstico omite deliberadamente credenciais, corpos de resposta e rastreamentos de pilha. Um HTTP 404 pode indicar que o bucket ou a chave exata não existe. Um HTTP 403 pode indicar credenciais inválidas ou acesso negado ao objeto. Erros no caminho local e transferências incompletas também causam falha.
O tempo limite de conexão é de 10 segundos, e os tempos limite de leitura e escrita são de
30 segundos, usando os
parâmetros de tempo limite em milissegundos do SDK. Esses limites
se aplicam às operações, não ao download inteiro. Não há um loop de novas tentativas na
aplicação: após uma falha transitória, execute novamente o comando inteiro com um destino que
não exista. Cada tentativa começa com um novo arquivo temporário. Adicionar uma nova tentativa
apenas em torno de getObject não cobriria falhas durante a leitura do corpo
da resposta.
Solução de problemas
Conexão recusada
Confira o host e a porta da API, se o servidor está em execução e se sua máquina consegue
acessá-lo. Para a demonstração local, inspecione demo-server.log; se a porta
estiver ocupada, ela deve ser alterada antes de iniciar outro ambiente de teste. Se o build
falhar, nenhum download será executado, então resolva primeiro o problema indicado pelo
diagnóstico do Maven.
Acesso negado
Confira a permissão da conta para o objeto, a região e o uso exato de maiúsculas, minúsculas e pontuação na chave. O importador não lista um prefixo nem escolhe o primeiro objeto correspondente. Se um destino já existir, escolha um novo nome de arquivo local em vez de excluir um resultado anterior que ainda não foi verificado.
Para uma importação que alimente um pipeline de processamento hospedado, o Robot 🤖 /minio/import é uma opção de integração separada. O comando local acima produz um arquivo para seu próprio fluxo de trabalho em Java.
