Ao configurar o MinIO para uploads através de uma rede interna (intranet), mas necessitando exibir as imagens para usuários em uma rede externa (internet), surgem desafios de acesso. A aplicação backend geralmente se comunica com o MinIO usando seu endpoint interno. Portanto, as URLs geradas para os objetos (imagens) apontam para esse endereço interno, que é inacessível diretamente da rede externa, resultando em imagens quebradas.
Uma abordagem inicial seria configurar o endpoint público diretamente no cliente MinIO do back end, mas isso pode não ser viável ou seguro em todos os cenários de infraestrutura. Abaixo, exploramos três métodos práticos para resovler essa questão.
Método 1: Proxy via Backend (Server-Side Proxying)
Esta abordagem mantém o acesso ao MinIO restrito à rede interna. O frontend solicita a imagem ao seu próprio backend, que a busca no MinIO e a transmite como um stream para o cliente.
Implementação Backend (Java/Spring)
public void streamImagem(String nomeBucket, String nomeArquivo, HttpServletResponse resposta) throws IOException {
InputStream fluxoEntrada = null;
try {
fluxoEntrada = minioClient.getObject(
GetObjectArgs.builder()
.bucket(nomeBucket)
.object(nomeArquivo)
.build()
);
} catch (Exception e) {
// Log do erro
}
if (fluxoEntrada == null) {
resposta.sendError(HttpServletResponse.SC_NOT_FOUND, "Arquivo de imagem não encontrado.");
return;
}
// Determinação do tipo de conteúdo
String extensao = "";
int pontoIndex = nomeArquivo.lastIndexOf('.');
if (pontoIndex > 0) {
extensao = nomeArquivo.substring(pontoIndex + 1).toLowerCase();
}
String mimeType = "image/" + extensao;
if (extensao.isEmpty()) {
mimeType = "application/octet-stream";
}
resposta.setContentType(mimeType);
resposta.setHeader("Cache-Control", "max-age=86400"); // Cache por 1 dia
try (ServletOutputStream fluxoSaida = resposta.getOutputStream()) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = fluxoEntrada.read(buffer)) != -1) {
fluxoSaida.write(buffer, 0, bytesRead);
}
fluxoSaida.flush();
} finally {
if (fluxoEntrada != null) {
fluxoEntrada.close();
}
}
}
Implementação Frontend (Vue.js)
// Supondo uma lista de nomes de arquivo vinda de uma API
const nomesArquivos = response.data.listaImagens;
// Constrói as URLs para o endpoint do backend
this.urlsImagens = nomesArquivos.map(arquivo => {
const nomeCodificado = encodeURIComponent(arquivo);
return `/api/imagens/stream?bucket=${this.bucketNome}&arquivo=${nomeCodificado}`;
});
// No template
// <img v-for="url in urlsImagens" :key="url" :src="url" />
Observação: Este método aumenta a carga no servidor backend, pois todo o tráfego de imagens passa por ele. É eficaz e seguro, mantendo o MinIO protegido atrás da rede interna.
Método 2: Proxy Reverso via Nginx
Configurar um servidor Nginx (ou similar) como proxy reverso. O Nginx escuta em uma porta pública e encaminha as requisições para o endpoint interno do MinIO. O backend então substitui o host interno nas URLs do MinIO pelo host público do Nginx.
Configuração do Nginx
server {
listen 80;
server_name seudominio.com;
location /minio/ {
proxy_pass http://ENDERECO_INTERNO_MINIO:PORTA_WEB_CONSOLA/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Configurações para lidar com CORS, se necessário
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
}
}
Lógica Backend para gerar URLs públicas
public List<String> gerarUrlsPublicas(String nomeBucket, String listaNomesJson) {
List<String> nomesArquivos = JsonArrayToList(listaNomesJson);
String endpointPublico = "https://seudominio.com/minio";
List<String> urlsPublicas = new ArrayList<>();
for (String nomeArquivo : nomesArquivos) {
String urlOriginal = getObjectUrl(nomeBucket, nomeArquivo);
// Assume que a URL interna segue o padrão http://INTERNO:PORTA/bucket/arquivo
String caminhoObjeto = "/" + nomeBucket + "/" + URLEncoder.encode(nomeArquivo, StandardCharsets.UTF_8);
urlsPublicas.add(endpointPublico + caminhoObjeto);
}
return urlsPublicas;
}
Observação: Este método descarrega o tráfego de imagens do back end para o Nginx. Requer configuração adicional de infraestrutura e cuidado com permissões de acesso no MinIO, pois as URLs geradas continuarão a ser públicas.
Método 3: Streaming de Imagem via Backend (Abordagem Recomendada)
Uma versão mais robusta e segura do Método 1. O backend lida com toda a lógica de obtenção e transmissão da imagem, incluindo cabeçalhos HTTP apropriados para cache, segurança e compatibilidade.
Implementação Backend Aprimorada
public void transmitirImagem(String nomeBucket, String nomeArquivo, HttpServletResponse resposta) throws IOException {
try (InputStream entrada = minioClient.getObject(
GetObjectArgs.builder()
.bucket(nomeBucket)
.object(nomeArquivo)
.build()
)) {
String nomeArquivoSeguro = Paths.get(nomeArquivo).getFileName().toString();
String tipoConteudo = determinarTipoMime(nomeArquivoSeguro);
// Cabeçalhos de segurança e cache
resposta.setHeader("Content-Type", tipoConteudo);
resposta.setHeader("X-Content-Type-Options", "nosniff");
resposta.setHeader("Cache-Control", "public, max-age=31536000"); // Cache por 1 ano
resposta.setHeader("Content-Disposition", "inline; filename=\"" + URLEncoder.encode(nomeArquivoSeguro, "UTF-8") + "\"");
// Transmite os bytes
byte[] buffer = new byte[4096];
int lidos;
while ((lidos = entrada.read(buffer)) != -1) {
resposta.getOutputStream().write(buffer, 0, lidos);
}
resposta.getOutputStream().flush();
} catch (Exception e) {
resposta.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
// Log do erro detalhado
}
}
private String determinarTipoMime(String nomeArquivo) {
String extensao = "";
int i = nomeArquivo.lastIndexOf('.');
if (i > 0) {
extensao = nomeArquivo.substring(i + 1).toLowerCase();
}
switch (extensao) {
case "jpg": case "jpeg": return "image/jpeg";
case "png": return "image/png";
case "gif": return "image/gif";
case "webp": return "image/webp";
default: return "application/octet-stream";
}
}
O frontend utiliza a URL gerada diretamente no atributo src de uma tag <img>. O tratamento completo no backend garante controle total sobre o acesso, formatação da resposta e otimização de desempenho através de cache.