Descarga de un archivo desde controladores Spring

Resuelto MilindaD asked hace 13 años • 16 respuestas

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?

MilindaD avatar Apr 15 '11 13:04 MilindaD
Aceptado
@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");
Infeligo avatar Apr 15 '2011 06:04 Infeligo

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)); 
}
Scott Carlson avatar May 31 '2012 15:05 Scott Carlson

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.

lobster1234 avatar Apr 15 '2011 07:04 lobster1234

Con Spring 3.0 puedes usar el HttpEntityobjeto de devolución. Si usa esto, entonces su controlador no necesita un HttpServletResponseobjeto 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) o
  • FileSystemResource,

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);
}
Ralph avatar Oct 29 '2012 10:10 Ralph

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 @ControllerAdviceexcepciones 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-lengthencabezado 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);
acdcjunior avatar Oct 23 '2014 21:10 acdcjunior