Descarga de un archivo desde controladores Spring
Tengo un requisito en el que necesito descargar un PDF del sitio web. El PDF debe generarse dentro del código, lo que pensé que sería una combinación de un marcador libre y un marco de generación de PDF como iText
. ¿Hay alguna forma mejor?
Sin embargo, mi principal problema es ¿cómo permito a los usuarios descargar el archivo a través de un Spring Controller?
@RequestMapping(value = "/files/{file_name}", method = RequestMethod.GET)
public void getFile(
@PathVariable("file_name") String fileName,
HttpServletResponse response) {
try {
// get your file as InputStream
InputStream is = ...;
// copy it to response's OutputStream
org.apache.commons.io.IOUtils.copy(is, response.getOutputStream());
response.flushBuffer();
} catch (IOException ex) {
log.info("Error writing file to output stream. Filename was '{}'", fileName, ex);
throw new RuntimeException("IOError writing file to output stream");
}
}
En términos generales, cuando tengas response.getOutputStream()
, puedes escribir cualquier cosa allí. Puede pasar este flujo de salida como un lugar para colocar el PDF generado en su generador. Además, si sabe qué tipo de archivo está enviando, puede configurar
response.setContentType("application/pdf");
Pude simplificar esto utilizando el soporte integrado en Spring con su ResourceHttpMessageConverter. Esto establecerá la longitud y el tipo de contenido si puede determinar el tipo mime.
@RequestMapping(value = "/files/{file_name}", method = RequestMethod.GET)
@ResponseBody
public FileSystemResource getFile(@PathVariable("file_name") String fileName) {
return new FileSystemResource(myService.getFileFor(fileName));
}
Debería poder escribir el archivo en la respuesta directamente. Algo como
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=\"somefile.pdf\"");
y luego escriba el archivo como una secuencia binaria en response.getOutputStream()
. Recuerda hacerlo response.flush()
al final y eso debería ser suficiente.
Con Spring 3.0 puedes usar el HttpEntity
objeto de devolución. Si usa esto, entonces su controlador no necesita un HttpServletResponse
objeto y, por lo tanto, es más fácil de probar.
Excepto esto, esta respuesta es relativamente igual a la de Infeligo .
Si el valor de retorno de su marco pdf es una matriz de bytes (lea la segunda parte de mi respuesta para conocer otros valores de retorno) :
@RequestMapping(value = "/files/{fileName}", method = RequestMethod.GET)
public HttpEntity<byte[]> createPdf(
@PathVariable("fileName") String fileName) throws IOException {
byte[] documentBody = this.pdfFramework.createPdf(filename);
HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.APPLICATION_PDF);
header.set(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=" + fileName.replace(" ", "_"));
header.setContentLength(documentBody.length);
return new HttpEntity<byte[]>(documentBody, header);
}
Si el tipo de retorno de su PDF Framework ( documentBbody
) aún no es una matriz de bytes (y tampoco ByteArrayInputStream
), entonces sería prudente NO convertirlo primero en una matriz de bytes. En su lugar es mejor utilizar:
InputStreamResource
,PathResource
(desde la primavera 4.0) oFileSystemResource
,
ejemplo con FileSystemResource
:
@RequestMapping(value = "/files/{fileName}", method = RequestMethod.GET)
public HttpEntity<byte[]> createPdf(
@PathVariable("fileName") String fileName) throws IOException {
File document = this.pdfFramework.createPdf(filename);
HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.APPLICATION_PDF);
header.set(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=" + fileName.replace(" ", "_"));
header.setContentLength(document.length());
return new HttpEntity<byte[]>(new FileSystemResource(document),
header);
}
Si usted:
- No quiero cargar el archivo completo en un
byte[]
antes de enviar la respuesta; - Quiere/necesita enviarlo/descargarlo a través de
InputStream
; - Quiere tener control total del tipo Mime y del nombre del archivo enviado;
- Tenga otras
@ControllerAdvice
excepciones de recogida para usted (o no).
El siguiente código es lo que necesita:
@RequestMapping(value = "/stuff/{stuffId}", method = RequestMethod.GET)
public ResponseEntity<FileSystemResource> downloadStuff(@PathVariable int stuffId)
throws IOException {
String fullPath = stuffService.figureOutFileNameFor(stuffId);
File file = new File(fullPath);
long fileLength = file.length(); // this is ok, but see note below
HttpHeaders respHeaders = new HttpHeaders();
respHeaders.setContentType("application/pdf");
respHeaders.setContentLength(fileLength);
respHeaders.setContentDispositionFormData("attachment", "fileNameIwant.pdf");
return new ResponseEntity<FileSystemResource>(
new FileSystemResource(file), respHeaders, HttpStatus.OK
);
}
Más información setContentLength()
: En primer lugar, el content-length
encabezado es opcional según el RFC HTTP 1.1 . Aún así, si puedes aportar un valor, es mejor. Para obtener dicho valor, sepa que File#length()
debería ser lo suficientemente bueno en el caso general, por lo que es una opción predeterminada segura.
Sin embargo, en escenarios muy específicos puede ser lento , en cuyo caso deberías tenerlo almacenado previamente (por ejemplo, en la base de datos), no calcularlo sobre la marcha. Los escenarios lentos incluyen: si el archivo es muy grande, especialmente si está en un sistema remoto o algo más elaborado como eso, tal vez una base de datos.
InputStreamResource
Si su recurso no es un archivo, por ejemplo, recoge los datos de la base de datos, debe utilizar InputStreamResource
. Ejemplo:
InputStreamResource isr = new InputStreamResource(...);
return new ResponseEntity<InputStreamResource>(isr, respHeaders, HttpStatus.OK);