Как обеспечить загрузку файла из поддерживающего компонента JSF? - PullRequest
84 голосов
/ 22 февраля 2012

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

Как я могу получить OutputStream ответа от текущего FacesContext?

Ответы [ 4 ]

214 голосов
/ 22 февраля 2012

Введение

Вы можете получить все через 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, вам нужно вложить <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);

Commonпример динамического файла

В случае, если вам нужно передать динамически сгенерированный файл, такой как 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() методов, принимающих либо File, либо InputStream, либо byte[] и указаниедолжен ли файл быть загружен как вложение (true) или встроенный (false).

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

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

4 голосов
/ 03 октября 2016
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 голосов
/ 28 апреля 2017

Вот что у меня сработало:

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();
}
0 голосов
/ 05 февраля 2013

Вот полный фрагмент кода 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();
   }
 }

Вы можете изменить логику чтения файла, если хотите, чтобы файл генерировался во время выполнения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...