จะดาวน์โหลดไฟล์จาก JSF backing bean ได้อย่างไร


91

มีวิธีใดในการดาวน์โหลดไฟล์จากวิธีการดำเนินการแบ็คกิ้ง bean JSF หรือไม่ ฉันลองมาหลายอย่างแล้ว ปัญหาหลักคือฉันไม่สามารถหาวิธีรับOutputStreamการตอบสนองเพื่อเขียนเนื้อหาไฟล์ไปยัง ฉันรู้วิธีดำเนินการกับ a Servletแต่ไม่สามารถเรียกใช้จากแบบฟอร์ม JSF และต้องมีการร้องขอใหม่

ฉันจะได้รับOutputStreamการตอบสนองจากกระแสFacesContext?

คำตอบ:


239

บทนำ

คุณสามารถผ่านทุกอย่างExternalContextได้ ใน JSF 1.x คุณจะได้รับดิบวัตถุโดยHttpServletResponse ExternalContext#getResponse()ใน JSF 2.x คุณสามารถใช้วิธีการมอบสิทธิ์ใหม่มากมายExternalContext#getResponseOutputStream()โดยไม่จำเป็นต้องคว้าHttpServletResponseจากใต้ฝากระโปรง JSF

ในการตอบกลับคุณควรตั้งค่าContent-Typeส่วนหัวเพื่อให้ไคลเอนต์ทราบว่าจะเชื่อมโยงแอปพลิเคชันใดกับไฟล์ที่ให้มา และคุณควรตั้งค่าContent-Lengthส่วนหัวเพื่อให้ไคลเอนต์สามารถคำนวณความคืบหน้าในการดาวน์โหลดมิฉะนั้นจะไม่ทราบ และคุณควรตั้งค่าContent-Dispositionส่วนหัวเป็นattachmentถ้าคุณต้องการกล่องโต้ตอบบันทึกเป็นมิฉะนั้นไคลเอนต์จะพยายามแสดงในบรรทัด สุดท้ายเพียงแค่เขียนเนื้อหาไฟล์ไปยังสตรีมเอาต์พุตการตอบสนอง

ส่วนที่สำคัญที่สุดคือการโทรFacesContext#responseComplete()แจ้ง JSF ว่าไม่ควรดำเนินการนำทางและการแสดงผลหลังจากที่คุณเขียนไฟล์ไปยังการตอบกลับมิฉะนั้นการสิ้นสุดของการตอบกลับจะทำให้เนื้อหา HTML ของหน้านั้นเสียไปหรือในเวอร์ชัน JSF ที่เก่ากว่า คุณจะได้รับIllegalStateExceptionข้อความเช่นgetoutputstream() has already been called for this responseเมื่อการใช้งาน JSF เรียกร้องgetWriter()ให้แสดง HTML

ปิด ajax / อย่าใช้คำสั่งระยะไกล!

คุณจะต้องตรวจสอบให้แน่ใจว่าวิธีการดำเนินการไม่เรียกตามคำขออาแจ็กซ์ แต่มันถูกเรียกโดยการร้องขอปกติเมื่อคุณยิงด้วยและ<h:commandLink> <h:commandButton>คำขอ Ajax และคำสั่งระยะไกลได้รับการจัดการโดย JavaScript ซึ่งในทางกลับกันเนื่องจากเหตุผลด้านความปลอดภัยไม่มีสิ่งอำนวยความสะดวกใด ๆ ที่จะบังคับให้กล่องโต้ตอบบันทึกเป็นกับเนื้อหาของการตอบสนองของ ajax

ในกรณีที่คุณใช้เช่น PrimeFaces <p:commandXxx>คุณต้องแน่ใจว่าคุณได้ปิด ajax ผ่านajax="false"แอตทริบิวต์อย่างชัดเจน ในกรณีที่คุณใช้ ICEfaces คุณจะต้องซ้อน a <f:ajax disabled="true" />ในคอมโพเนนต์คำสั่ง

ตัวอย่าง JSF 2.x ทั่วไป

public void download() throws IOException {
    FacesContext fc = FacesContext.getCurrentInstance();
    ExternalContext ec = fc.getExternalContext();

    ec.responseReset(); // Some JSF component library or some Filter might have set some headers in the buffer beforehand. We want to get rid of them, else it may collide.
    ec.setResponseContentType(contentType); // Check http://www.iana.org/assignments/media-types for all types. Use if necessary ExternalContext#getMimeType() for auto-detection based on filename.
    ec.setResponseContentLength(contentLength); // Set it with the file size. This header is optional. It will work if it's omitted, but the download progress will be unknown.
    ec.setResponseHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // The Save As popup magic is done here. You can give it any file name you want, this only won't work in MSIE, it will use current request URL as file name instead.

    OutputStream output = ec.getResponseOutputStream();
    // Now you can write the InputStream of the file to the above OutputStream the usual way.
    // ...

    fc.responseComplete(); // Important! Otherwise JSF will attempt to render the response which obviously will fail since it's already written with a file and closed.
}

ตัวอย่าง JSF 1.x ทั่วไป

public void download() throws IOException {
    FacesContext fc = FacesContext.getCurrentInstance();
    HttpServletResponse response = (HttpServletResponse) fc.getExternalContext().getResponse();

    response.reset(); // Some JSF component library or some Filter might have set some headers in the buffer beforehand. We want to get rid of them, else it may collide.
    response.setContentType(contentType); // Check http://www.iana.org/assignments/media-types for all types. Use if necessary ServletContext#getMimeType() for auto-detection based on filename.
    response.setContentLength(contentLength); // Set it with the file size. This header is optional. It will work if it's omitted, but the download progress will be unknown.
    response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); // The Save As popup magic is done here. You can give it any file name you want, this only won't work in MSIE, it will use current request URL as file name instead.

    OutputStream output = response.getOutputStream();
    // Now you can write the InputStream of the file to the above OutputStream the usual way.
    // ...

    fc.responseComplete(); // Important! Otherwise JSF will attempt to render the response which obviously will fail since it's already written with a file and closed.
}

ตัวอย่างไฟล์คงที่ทั่วไป

ในกรณีที่คุณต้องการสตรีมไฟล์แบบคงที่จากระบบไฟล์โลคัลดิสก์ให้เปลี่ยนโค้ดดังต่อไปนี้:

File file = new File("/path/to/file.ext");
String fileName = file.getName();
String contentType = ec.getMimeType(fileName); // JSF 1.x: ((ServletContext) ec.getContext()).getMimeType(fileName);
int contentLength = (int) file.length();

// ...

Files.copy(file.toPath(), output);

ตัวอย่างไฟล์ไดนามิกทั่วไป

ในกรณีที่คุณต้องการสตรีมไฟล์ที่สร้างแบบไดนามิกเช่น PDF หรือ XLS จากนั้นให้ระบุoutputที่ที่ API ที่ใช้คาดว่าจะมีOutputStreamไฟล์.

เช่น iText PDF:

String fileName = "dynamic.pdf";
String contentType = "application/pdf";

// ...

Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, output);
document.open();
// Build PDF content here.
document.close();

เช่น Apache POI HSSF:

String fileName = "dynamic.xls";
String contentType = "application/vnd.ms-excel";

// ...

HSSFWorkbook workbook = new HSSFWorkbook();
// Build XLS content here.
workbook.write(output);
workbook.close();

โปรดทราบว่าคุณไม่สามารถกำหนดความยาวของเนื้อหาได้ที่นี่ ดังนั้นคุณต้องลบบรรทัดเพื่อตั้งค่าความยาวของเนื้อหาตอบกลับ นี่ไม่ใช่ปัญหาในทางเทคนิคข้อเสียเพียงอย่างเดียวคือ enduser จะแสดงความคืบหน้าในการดาวน์โหลดที่ไม่รู้จัก ในกรณีนี้มีความสำคัญคุณต้องเขียนลงในไฟล์ภายใน (ชั่วคราว) ก่อนจากนั้นจึงจัดเตรียมตามที่แสดงในบทก่อน

วิธียูทิลิตี้

หากคุณใช้ไลบรารียูทิลิตี้ JSF OmniFacesคุณสามารถใช้หนึ่งในสามFaces#sendFile()วิธีที่สะดวกโดยใช้ a Fileหรือ an InputStreamหรือ a byte[]และระบุว่าควรดาวน์โหลดไฟล์เป็นไฟล์แนบ ( true) หรือ inline ( false)

public void download() throws IOException {
    Faces.sendFile(file, true);
}

ใช่รหัสนี้สมบูรณ์ตามที่เป็นอยู่ คุณไม่จำเป็นต้องเรียกร้องresponseComplete()และอื่น ๆ ด้วยตัวคุณเอง วิธีนี้ยังเกี่ยวข้องกับส่วนหัวเฉพาะของ IE และชื่อไฟล์ UTF-8 อย่างเหมาะสม คุณสามารถค้นหารหัสที่มาที่นี่


1
ง่ายมาก! ฉันสงสัยว่าจะทำให้การดาวน์โหลดพร้อมใช้งานสำหรับ PrimeFaces ตามการจัดแสดงของพวกเขาได้อย่างไรเพราะต้องใช้InputStreamโครงสร้างพื้นฐานสำหรับp:fileDownloadและฉันไม่ได้จัดการวิธีการแปลงOutputStreamเป็นInputStreamไฟล์. ตอนนี้เป็นที่ชัดเจนว่าแม้แต่ผู้ฟังการดำเนินการก็สามารถเปลี่ยนประเภทเนื้อหาการตอบกลับได้จากนั้นการตอบกลับจะได้รับการยอมรับเป็นการดาวน์โหลดไฟล์ที่ฝั่งตัวแทนผู้ใช้ ขอขอบคุณ!
Lyubomyr Shaydariv

1
มีวิธีดำเนินการโดยใช้ HTTP GET แทน HTTP POST หรือไม่ (h: commandButton และ h: commandLink)
Alfredo Osorio

@Alfredo: ใช่โดยใช้preRenderViewListener ในมุมมองที่ไม่มีมาร์กอัป คำถามที่คล้ายกันสำหรับการดาวน์โหลด (ดีใช้ได้) JSON มีคำตอบที่นี่: stackoverflow.com/questions/8358006/…
BalusC

ลิงก์w3schools.com/media/media_mimeref.asp ใช้งานไม่ได้ อันนี้อาจจะเหมาะ: iana.org/assignments/media-types
Zakhar

2
@BalusC คุณครอบคลุมทุกหัวข้อ jsf - ขอบคุณที่ทำให้ชีวิตฉันง่ายขึ้นครับ!
Buttinger Xaver

5
public void download() throws IOException
{

    File file = new File("file.txt");

    FacesContext facesContext = FacesContext.getCurrentInstance();

    HttpServletResponse response = 
            (HttpServletResponse) facesContext.getExternalContext().getResponse();

    response.reset();
    response.setHeader("Content-Type", "application/octet-stream");
    response.setHeader("Content-Disposition", "attachment;filename=file.txt");

    OutputStream responseOutputStream = response.getOutputStream();

    InputStream fileInputStream = new FileInputStream(file);

    byte[] bytesBuffer = new byte[2048];
    int bytesRead;
    while ((bytesRead = fileInputStream.read(bytesBuffer)) > 0) 
    {
        responseOutputStream.write(bytesBuffer, 0, bytesRead);
    }

    responseOutputStream.flush();

    fileInputStream.close();
    responseOutputStream.close();

    facesContext.responseComplete();

}

3

นี่คือสิ่งที่ใช้ได้ผลสำหรับฉัน:

public void downloadFile(String filename) throws IOException {
    final FacesContext fc = FacesContext.getCurrentInstance();
    final ExternalContext externalContext = fc.getExternalContext();

    final File file = new File(filename);

    externalContext.responseReset();
    externalContext.setResponseContentType(ContentType.APPLICATION_OCTET_STREAM.getMimeType());
    externalContext.setResponseContentLength(Long.valueOf(file.lastModified()).intValue());
    externalContext.setResponseHeader("Content-Disposition", "attachment;filename=" + file.getName());

    final HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();

    FileInputStream input = new FileInputStream(file);
    byte[] buffer = new byte[1024];
    final ServletOutputStream out = response.getOutputStream();

    while ((input.read(buffer)) != -1) {
        out.write(buffer);
    }

    out.flush();
    fc.responseComplete();
}

1
หลังจาก 2 วันทำการสิ่งนี้ช่วยแก้ปัญหาของฉันได้ด้วยการเปลี่ยนแปลงเล็กน้อย :) ขอบคุณมาก
ÖMERTAŞCI

@ ÖMERTAŞCI: มีอะไรเปลี่ยนแปลงบ้าง
Kukeltje

-3

นี่คือข้อมูลโค้ดที่สมบูรณ์http://bharatonjava.wordpress.com/2013/02/01/downloading-file-in-jsf-2/

 @ManagedBean(name = "formBean")
 @SessionScoped
 public class FormBean implements Serializable
 {
   private static final long serialVersionUID = 1L;

   /**
    * Download file.
    */
   public void downloadFile() throws IOException
   {
      File file = new File("C:\\docs\\instructions.txt");
      InputStream fis = new FileInputStream(file);
      byte[] buf = new byte[1024];
      int offset = 0;
      int numRead = 0;
      while ((offset < buf.length) && ((numRead = fis.read(buf, offset, buf.length -offset)) >= 0)) 
      {
        offset += numRead;
      }
      fis.close();
      HttpServletResponse response =
         (HttpServletResponse) FacesContext.getCurrentInstance()
        .getExternalContext().getResponse();

     response.setContentType("application/octet-stream");
     response.setHeader("Content-Disposition", "attachment;filename=instructions.txt");
     response.getOutputStream().write(buf);
     response.getOutputStream().flush();
     response.getOutputStream().close();
     FacesContext.getCurrentInstance().responseComplete();
   }
 }

คุณสามารถเปลี่ยนตรรกะการอ่านไฟล์ได้ในกรณีที่คุณต้องการสร้างไฟล์ในขณะรันไทม์


สิ่งนี้จะทำให้คุณได้รับส่วนหนึ่งของไฟล์อินพุตเท่านั้นหากมีขนาดใหญ่กว่า 1024 ไบต์!
hinneLinks
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.