Mengunduh file dari pengontrol pegas

387

Saya memiliki persyaratan di mana saya perlu mengunduh PDF dari situs web. PDF perlu dibuat dalam kode, yang saya pikir akan menjadi kombinasi freemarker dan kerangka kerja generasi PDF seperti iText. Ada cara yang lebih baik?

Namun, masalah utama saya adalah bagaimana cara saya mengizinkan pengguna mengunduh file melalui Spring Controller?

MilindaD
sumber
2
Patut disebutkan bahwa Spring Framework banyak berubah sejak 2011, sehingga Anda dapat melakukannya dengan cara yang reaktif - di sini adalah contohnya
Krzysztof Skrzynecki

Jawaban:

397
@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");
    }

}

Secara umum, ketika sudah response.getOutputStream(), Anda dapat menulis apa pun di sana. Anda dapat melewatkan aliran output ini sebagai tempat untuk meletakkan PDF yang dihasilkan ke generator Anda. Juga, jika Anda tahu jenis file apa yang Anda kirim, Anda dapat mengatur

response.setContentType("application/pdf");
Infeligo
sumber
4
Ini cukup banyak tentang apa yang akan saya katakan, tetapi Anda mungkin juga harus mengatur header jenis respons ke sesuatu yang sesuai untuk file.
GaryF
2
Ya, baru saja mengedit posting. Saya memiliki berbagai jenis file yang dihasilkan, jadi saya menyerahkannya ke browser untuk menentukan jenis konten file dengan ekstensi.
Infeligo
Lupa flushBuffer, terima kasih untuk posting Anda, saya melihat mengapa milik saya tidak berfungsi :-)
Jan Vladimir Mostert
35
Ada alasan khusus untuk menggunakan Apache, IOUtilsbukan Spring FileCopyUtils?
Powerlord
3
Ini adalah solusi yang lebih baik: stackoverflow.com/questions/16652760/…
Dmytro Plekhotkin
290

Saya dapat melakukan streaming garis ini dengan menggunakan dukungan bawaan di Spring dengan ResourceHttpMessageConverter itu. Ini akan mengatur panjang konten dan tipe konten jika dapat menentukan tipe 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
sumber
10
Ini bekerja. Tetapi file (file .csv) ditampilkan di browser dan tidak diunduh - bagaimana saya bisa memaksa browser untuk mengunduh?
chzbrgla
41
Anda dapat menambahkan hasil = MediaType.APPLICATION_OCTET_STREAM_VALUE ke @RequestMapping untuk memaksa unduhan
David Kago
8
Anda juga harus menambahkan <bean class = "org.springframework.http.converter.ResourceHttpMessageConverter" /> ke messageConverters list (<mvc: annotation-driven> <mvc: message-converter>)
Sllouyssgort
4
Apakah ada cara untuk mengatur Content-Dispositiontajuk dengan cara ini?
Ralph
8
Saya tidak membutuhkan itu, tapi saya pikir Anda bisa menambahkan HttpResponse sebagai parameter ke metode, dan kemudian "response.setHeader (" Content-Disposition "," lampiran; nama file = somefile.pdf ");"
Scott Carlson
82

Anda harus dapat menulis file pada jawaban secara langsung. Sesuatu seperti

response.setContentType("application/pdf");      
response.setHeader("Content-Disposition", "attachment; filename=\"somefile.pdf\""); 

dan kemudian tulis file tersebut sebagai aliran biner response.getOutputStream(). Ingatlah untuk melakukan response.flush()di akhir dan itu harus dilakukan.

lobster1234
sumber
8
bukankah cara 'Spring' untuk mengatur tipe konten seperti ini? @RequestMapping(value = "/foo/bar", produces = "application/pdf")
Hitam
4
@ Francis bagaimana jika aplikasi Anda mengunduh jenis file yang berbeda? Jawaban Lobster1234 memungkinkan Anda untuk mengatur disposisi konten secara dinamis.
Mawar
2
itu benar @Rose, tapi saya percaya ini akan menjadi praktik yang lebih baik untuk menentukan titik akhir yang berbeda per format
Hitam
3
Saya kira tidak, karena itu tidak dapat diskalakan. Kami saat ini mendukung selusin jenis sumber daya. Kami mungkin mendukung lebih banyak jenis file berdasarkan apa yang ingin diunggah pengguna dalam hal ini kami mungkin berakhir dengan begitu banyak titik akhir yang pada dasarnya melakukan hal yang sama. IMHO harus ada hanya satu titik akhir unduhan dan menangani banyak jenis file. @ Francis
Rose
3
itu benar-benar "scalable", tetapi kita dapat setuju untuk tidak setuju apakah ini praktik terbaik
Black
74

Dengan Spring 3.0 Anda dapat menggunakan HttpEntityobjek kembali. Jika Anda menggunakan ini, maka pengontrol Anda tidak memerlukan HttpServletResponseobjek, dan karenanya lebih mudah untuk diuji. Kecuali ini, jawaban ini relatif sama dengan jawaban Infeligo .

Jika nilai pengembalian kerangka pdf Anda adalah array byte (baca bagian kedua dari jawaban saya untuk nilai pengembalian lainnya) :

@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);
}

Jika jenis kembalinya PDF Anda Framework ( documentBbody) belum menjadi array byte (dan juga tidak ada ByteArrayInputStream) maka akan bijaksana tidak untuk membuat array byte pertama. Sebaliknya lebih baik menggunakan:

contoh dengan 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);
}
Muntah
sumber
11
-1 ini akan secara tidak resmi memuat seluruh file dalam memori dengan mudah dapat menyebabkan OutOfMemoryErrors.
Faisal Feroz
1
@FaisalFeroz: ya ini benar, tetapi dokumen file tetap dibuat dalam memori (lihat pertanyaan: "PDF harus dibuat dalam kode"). Lagi pula - apa solusi Anda yang mengatasi masalah ini?
Ralph
1
Anda juga dapat menggunakan ResponseEntity yang merupakan super dari HttpEntity yang memungkinkan Anda menentukan kode status http respons. Contoh:return new ResponseEntity<byte[]>(documentBody, headers, HttpStatus.CREATED)
Amr Mostafa
@Arm Mostafa: ResponseEntityadalah subkelas dari HttpEntity(tapi saya mengerti) di sisi lain 201 DICIPTAKAN bukan yang akan saya gunakan ketika saya kembali hanya melihat ke data. (lihat w3.org/Protocols/rfc2616/rfc2616-sec10.html untuk 201 DICIPTAKAN)
Ralph
1
Apakah ada alasan mengapa Anda mengganti spasi putih dengan garis bawah pada nama file? Anda dapat membungkusnya dengan tanda kutip untuk mengirim nama sebenarnya.
Alexandru Severin
63

Jika kamu:

  • Tidak ingin memuat seluruh file ke dalam byte[] sebelum mengirim ke respons;
  • Ingin / perlu mengirim / mengunduhnya via InputStream ;
  • Ingin memiliki kontrol penuh dari Mime Type dan nama file yang dikirim;
  • Memiliki @ControllerAdvicepengecualian lain untuk Anda (atau tidak).

Kode di bawah ini yang Anda butuhkan:

@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
    );
}

Tentang bagian panjang file : File#length()harus cukup baik dalam kasus umum, tapi saya pikir saya akan membuat pengamatan ini karena memang bisa lambat , dalam hal ini Anda harus menyimpannya sebelumnya (misalnya dalam DB). Kasing bisa lambat termasuk: jika file besar, khususnya jika file tersebut dalam sistem jarak jauh atau sesuatu yang lebih rumit seperti itu - database, mungkin.



InputStreamResource

Jika sumber daya Anda bukan file, misalnya Anda mengambil data dari DB, Anda harus menggunakan InputStreamResource. Contoh:

    InputStreamResource isr = new InputStreamResource(new FileInputStream(file));
    return new ResponseEntity<InputStreamResource>(isr, respHeaders, HttpStatus.OK);
acdcjunior
sumber
Anda tidak menyarankan untuk penggunaan kelas FileSystemResource?
Stephane
Sebenarnya, saya percaya tidak apa-apa menggunakan di FileSystemResourcesana. Bahkan disarankan jika sumber daya Anda adalah file . Dalam sampel ini, FileSystemResourcedapat digunakan di mana InputStreamResource.
acdcjunior
Tentang bagian perhitungan panjang file: Jika Anda khawatir, jangan khawatir. File#length()harus cukup baik dalam kasus umum. Saya hanya menyebutkannya karena memang bisa lambat , khususnya jika file tersebut dalam sistem jarak jauh atau sesuatu yang lebih rumit seperti itu - database, mungkin ?. Tetapi hanya khawatir jika itu menjadi masalah (atau jika Anda memiliki bukti keras itu akan menjadi satu), bukan sebelumnya. Poin utamanya adalah: Anda berupaya untuk melakukan streaming file, jika Anda harus melakukan preload semua file sebelumnya, maka streaming tidak ada bedanya, eh?
acdcjunior
mengapa kode di atas tidak bekerja untuk saya? Ini mengunduh file 0 byte. Saya memeriksa dan memastikan konverter ByteArray & ResourceMessage ada di sana. Apakah saya melewatkan sesuatu?
coding_idiot
Mengapa Anda mengkhawatirkan pengonversi ByteArray & ResourceMessage?
acdcjunior
20

Kode ini berfungsi dengan baik untuk mengunduh file secara otomatis dari pengontrol pegas pada mengklik tautan di jsp.

@RequestMapping(value="/downloadLogFile")
public void getLogFile(HttpSession session,HttpServletResponse response) throws Exception {
    try {
        String filePathToBeServed = //complete file name with path;
        File fileToDownload = new File(filePathToBeServed);
        InputStream inputStream = new FileInputStream(fileToDownload);
        response.setContentType("application/force-download");
        response.setHeader("Content-Disposition", "attachment; filename="+fileName+".txt"); 
        IOUtils.copy(inputStream, response.getOutputStream());
        response.flushBuffer();
        inputStream.close();
    } catch (Exception e){
        LOGGER.debug("Request could not be completed at this moment. Please try again.");
        e.printStackTrace();
    }

}
Sunil
sumber
14

Kode di bawah ini berfungsi untuk saya menghasilkan dan mengunduh file teks.

@RequestMapping(value = "/download", method = RequestMethod.GET)
public ResponseEntity<byte[]> getDownloadData() throws Exception {

    String regData = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";
    byte[] output = regData.getBytes();

    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.set("charset", "utf-8");
    responseHeaders.setContentType(MediaType.valueOf("text/html"));
    responseHeaders.setContentLength(output.length);
    responseHeaders.set("Content-disposition", "attachment; filename=filename.txt");

    return new ResponseEntity<byte[]>(output, responseHeaders, HttpStatus.OK);
}
Siva Kumar
sumber
5

Apa yang dapat saya pikirkan dengan cepat adalah, menghasilkan pdf dan menyimpannya di webapp / unduhan / <RANDOM-FILENAME> .pdf dari kode dan mengirimkan pengalihan ke file ini menggunakan HttpServletRequest

request.getRequestDispatcher("/downloads/<RANDOM-FILENAME>.pdf").forward(request, response);

atau jika Anda dapat mengonfigurasi resolver tampilan Anda seperti,

  <bean id="pdfViewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass"
              value="org.springframework.web.servlet.view.JstlView" />
    <property name="order" value=”2″/>
    <property name="prefix" value="/downloads/" />
    <property name="suffix" value=".pdf" />
  </bean>

lalu kembali saja

return "RANDOM-FILENAME";
kalyan
sumber
1
Jika saya memerlukan dua resolver view, bagaimana saya bisa mengembalikan nama resolver atau memilihnya di controller ??
azerafati
3

Solusi berikut ini cocok untuk saya

    @RequestMapping(value="/download")
    public void getLogFile(HttpSession session,HttpServletResponse response) throws Exception {
        try {

            String fileName="archivo demo.pdf";
            String filePathToBeServed = "C:\\software\\Tomcat 7.0\\tmpFiles\\";
            File fileToDownload = new File(filePathToBeServed+fileName);

            InputStream inputStream = new FileInputStream(fileToDownload);
            response.setContentType("application/force-download");
            response.setHeader("Content-Disposition", "attachment; filename="+fileName); 
            IOUtils.copy(inputStream, response.getOutputStream());
            response.flushBuffer();
            inputStream.close();
        } catch (Exception exception){
            System.out.println(exception.getMessage());
        }

    }
Jorge Santos Neill
sumber
2

sesuatu seperti di bawah ini

@RequestMapping(value = "/download", method = RequestMethod.GET)
public void getFile(HttpServletResponse response) {
    try {
        DefaultResourceLoader loader = new DefaultResourceLoader();
        InputStream is = loader.getResource("classpath:META-INF/resources/Accepted.pdf").getInputStream();
        IOUtils.copy(is, response.getOutputStream());
        response.setHeader("Content-Disposition", "attachment; filename=Accepted.pdf");
        response.flushBuffer();
    } catch (IOException ex) {
        throw new RuntimeException("IOError writing file to output stream");
    }
}

Anda dapat menampilkan PDF atau mengunduhnya contoh di sini

xxy
sumber
1

Jika itu membantu siapa pun. Anda dapat melakukan apa yang disarankan oleh Infeligo, tetapi cukup masukkan kode tambahan ini untuk unduhan paksa.

response.setContentType("application/force-download");
Sagar Nair
sumber
0

Ini bisa menjadi jawaban yang bermanfaat.

Apakah boleh mengekspor data sebagai format pdf di frontend?

Meluas ke ini, menambahkan disposisi konten sebagai lampiran (default) akan mengunduh file. Jika Anda ingin melihatnya, Anda harus mengaturnya agar sejajar.

theNextBigThing
sumber
0

Dalam kasus saya, saya membuat beberapa file berdasarkan permintaan, demikian juga url harus dihasilkan.

Bagi saya bekerja seperti itu:

@RequestMapping(value = "/files/{filename:.+}", method = RequestMethod.GET, produces = "text/csv")
@ResponseBody
public FileSystemResource getFile(@PathVariable String filename) {
    String path = dataProvider.getFullPath(filename);
    return new FileSystemResource(new File(path));
}

Sangat penting adalah ketik mime producesdan juga bahwa, nama file tersebut adalah bagian dari tautan sehingga Anda harus menggunakannya @PathVariable.

Kode HTML terlihat seperti itu:

<a th:href="@{|/dbreport/files/${file_name}|}">Download</a>

Di mana ${file_name}dihasilkan oleh Thymeleaf dalam controller dan yaitu: result_20200225.csv, sehingga seluruh url behing link adalah: example.com/aplication/dbreport/files/result_20200225.csv.

Setelah mengklik tautan, browser bertanya kepada saya apa yang harus saya lakukan dengan file - simpan atau buka.

Tomasz Dzięcielewski
sumber