Как обеспечить загрузку файла из компонента поддержки JSF?

91

Есть ли способ обеспечить загрузку файла из метода действия компонента поддержки JSF? Я много чего перепробовал. Основная проблема в том, что я не могу понять, как получить OutputStreamответ, чтобы записать содержимое файла. Я знаю, как это сделать с помощью a 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) или inline ( false).

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

Да, этот код является полным как есть. Не нужно призывать себя responseComplete()и тд. Этот метод также правильно работает с заголовками IE и именами файлов UTF-8. Вы можете найти исходный код здесь .

BalusC
источник
1
Так просто! Мне было интересно, как сделать загрузку доступной для PrimeFaces в соответствии с их демонстрацией, потому что для этого требуется InputStreamинфраструктура p:fileDownload, и я не сумел преобразовать OutputStreamв InputStream. Теперь ясно, что даже прослушиватель действий может изменить тип содержимого ответа, и тогда ответ в любом случае будет учитываться как загрузка файла на стороне пользовательского агента. Спасибо!
Любомир Шайдарев
1
Есть ли способ сделать это с помощью HTTP GET вместо HTTP POST (h: commandButton и h: commandLink)?
Альфредо Осорио,
@Alfredo: да, используя preRenderViewслушателя в безупречном виде.
Ответ на
Ссылка w3schools.com/media/media_mimeref.asp не работает. Может это подойдет: iana.org/assignments/media-types
Захар
2
@BalusC, вы освещаете все темы jsf - спасибо, что облегчили мне жизнь, сэр!
Баттингер Ксавер,
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: Что изменится,
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