Як забезпечити завантаження файлу з резервного файлу JSF?


91

Чи є спосіб забезпечити завантаження файлу за допомогою методу дії резервного компонента JSF? Я багато чого пробував. Основна проблема полягає в тому, що я не можу зрозуміти, як отримати OutputStreamвідповідь, щоб написати вміст файлу. Я знаю, як це зробити за допомогою Servlet, але це не можна викликати з форми JSF і вимагає нового запиту.

Як я можу отримати OutputStreamвідповідь від поточного FacesContext?

Відповіді:


238

Вступ

Ви можете все пройти 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 / не використовуйте віддалену команду!

Вам потрібно лише переконатися, що метод дії не викликається запитом 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();

Зверніть увагу, що тут ви не можете встановити довжину вмісту. Тому вам потрібно видалити рядок, щоб встановити довжину вмісту відповіді. Це технічно не проблема, єдиним недоліком є ​​те, що кінцевому користувачеві буде представлений невідомий хід завантаження. Якщо це важливо, то вам дійсно потрібно спочатку написати в локальний (тимчасовий) файл, а потім надати його, як показано в попередньому розділі.

Корисний метод

Якщо ви використовуєте службову бібліотеку JSF OmniFaces , ви можете скористатися одним із трьох зручних Faces#sendFile()методів, використовуючи або a File, або an InputStream, або a byte[], і вказавши, чи слід завантажувати файл як вкладення ( true) або вбудований ( false).

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

Так, цей код є повним як є. Не потрібно посилатися responseComplete()і так далі на себе. Цей метод також належним чином обробляє заголовки, специфічні для IE, та імена файлів UTF-8. Ви можете знайти вихідний код тут .


1
Так легко! Мені цікаво, як зробити завантаження доступним для PrimeFaces відповідно до їх вітрини, тому що для цього потрібна InputStreamінфраструктура p:fileDownload, і я не зумів перетворити OutputStreamна InputStream. Тепер зрозуміло, що навіть прослуховувач дій може змінити тип вмісту відповіді, і тоді відповідь все одно буде поважатись як завантаження файлу на стороні агента користувача. Дякую!
Любомир Шайдарів

1
Чи є спосіб зробити це, використовуючи HTTP GET замість HTTP POST (h: commandButton і h: commandLink)?
Альфредо Осоріо

@Alfredo: так, використовуючи preRenderViewслухач у безмаркетному вигляді. З таким запитанням для завантаження (ну, обслуговуючи) JSON відповідає тут: stackoverflow.com/questions/8358006 / ...
BalusC

w3schools.com/media/media_mimeref.asp посилання порушено. Можливо, цей підходить: iana.org/assignments/media-types
Захар

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 робочі дні це вирішило мою проблему з невеликими змінами :) велике спасибі.
ÖMER TAŞCI

@ ÖMERTAŞCI: Що змінюється,
Кукельтьє

-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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.