JSF destek çekirdeğinden nasıl dosya indirilir?


91

JSF destekli fasulye eylem yönteminden dosya indirme sağlamanın herhangi bir yolu var mı? Bir çok şeyi denedim. Ana sorun, OutputStreamdosya içeriğini yazmak için yanıtı nasıl alacağımı çözemememdir. A ile nasıl yapılacağını biliyorum Servlet, ancak bu bir JSF formundan çağrılamaz ve yeni bir istek gerektirir.

Akımdan OutputStreamcevabı nasıl alabilirim FacesContext?

Yanıtlar:


238

Giriş

Her şeyi halledebilirsiniz ExternalContext. JSF 1.x'te ham HttpServletResponsenesneyi ExternalContext#getResponse(). JSF 2.x'de, JSF davlumbazlarının altından ExternalContext#getResponseOutputStream()kapmaya gerek kalmadan bir dizi yeni delege yöntemini kullanabilirsiniz HttpServletResponse.

Yanıtta, Content-Typeistemcinin sağlanan dosyayla hangi uygulamayı ilişkilendireceğini bilmesi için üstbilgiyi ayarlamalısınız . Content-LengthÜstbilgiyi istemcinin indirme ilerlemesini hesaplayabileceği şekilde ayarlamalısınız , aksi takdirde bilinmeyecektir. Ve, Farklı Kaydet iletişim kutusu istiyorsanız Content-Dispositionbaşlığı olarak ayarlamalısınız , aksi takdirde istemci bunu satır içi olarak görüntülemeye çalışacaktır. Son olarak, dosya içeriğini yanıt çıktı akışına yazın.attachment

En önemli kısım, FacesContext#responseComplete()dosyayı yanıta yazdıktan sonra gezinme ve işleme gerçekleştirmemesi gerektiğini JSF'ye bildirmektir, aksi takdirde yanıtın sonu sayfanın HTML içeriğiyle veya eski JSF sürümlerinde kirlenir. , bir alacak IllegalStateExceptiongibi bir mesaj ile getoutputstream() has already been called for this responseJSF uygulama çağrıları yaparken getWriter()HTML işlemek için.

Ajax'ı kapatın / uzaktan kumanda kullanmayın!

Yalnızca eylem yöntemi olduğundan emin olmak gerekir değil bir ajax isteği tarafından çağrılan, ancak birlikte ateş gibi normal bir istek tarafından çağrılmasını <h:commandLink>ve <h:commandButton>. Ajax istekleri ve uzak komutlar JavaScript tarafından işlenir ve bu da güvenlik nedenleriyle ajax yanıtının içeriğiyle Farklı Kaydet diyaloğunu zorlayacak hiçbir tesise sahip değildir.

Örneğin PrimeFaces kullanıyorsanız <p:commandXxx>, ajax="false"öznitelik aracılığıyla ajax'ı açıkça kapattığınızdan emin olmanız gerekir . ICEfaces kullanıyorsanız <f:ajax disabled="true" />, komut bileşenine bir yerleştirmeniz gerekir .

Genel JSF 2.x örneği

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.
}

Genel JSF 1.x örneği

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.
}

Yaygın statik dosya örneği

Yerel disk dosya sisteminden statik bir dosya yayınlamanız gerekirse, kodu aşağıdaki gibi değiştirin:

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

Ortak dinamik dosya örneği

PDF veya XLS gibi dinamik olarak oluşturulmuş bir dosyayı yayınlamanız outputgerekirse, kullanılan API'nin bir OutputStream.

Örneğin 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();

Örneğin 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();

Burada içerik uzunluğunu ayarlayamayacağınızı unutmayın. Bu nedenle, yanıt içeriği uzunluğunu ayarlamak için satırı kaldırmanız gerekir. Bu teknik olarak sorun değil, tek dezavantajı, son kullanıcıya bilinmeyen bir indirme ilerlemesi sunulacak olmasıdır. Bunun önemli olması durumunda, gerçekten önce yerel (geçici) bir dosyaya yazmanız ve ardından önceki bölümde gösterildiği gibi sağlamanız gerekir.

Fayda yöntemi

JSF yardımcı program kitaplığı OmniFaces kullanıyorsanız , Faces#sendFile()a File, veya bir InputStreamveya a alarak byte[]ve dosyanın ek ( true) veya satır içi ( false) olarak indirilip indirilmeyeceğini belirleyen üç uygun yöntemden birini kullanabilirsiniz .

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

Evet, bu kod olduğu gibi tamamlandı. responseComplete()Kendinizi çağırmanıza gerek yok . Bu yöntem aynı zamanda IE'ye özgü üstbilgiler ve UTF-8 dosya adlarıyla da düzgün bir şekilde ilgilenir. Kaynak kodunu burada bulabilirsiniz .


1
Çok kolay! Bunun gerektirdiği için, onların vitrin göre PrimeFaces için indirme kullanılabilir hale getirmek için nasıl merak ettik InputStreamaltyapısını p:fileDownloadve ben dönüştürmek nasıl yönetilen değil OutputStreametmek InputStream. Artık bir eylem dinleyicisinin bile yanıt içerik türünü değiştirebileceği ve ardından yanıt, kullanıcı aracısı tarafında bir dosya indirmesi olarak kabul edileceği açıktır. Teşekkür ederim!
Lyubomyr Shaydariv

1
HTTP POST (h: commandButton ve h: commandLink) yerine HTTP GET kullanarak bunu yapmanın bir yolu var mı?
Alfredo Osorio

@Alfredo: evet, preRenderViewişaretsiz bir görünümde dinleyiciyi kullanmak .
JSON'u


2
@BalusC her jsf konusunu ele alıyorsunuz - hayatımı kolaylaştırdığınız için teşekkür ederim efendim!
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

Bu benim için çalıştı:

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 iş günü sonra ufak bir değişiklikle sorunumu çözdü :) çok teşekkür ederim.
ÖMER TAŞCI

@ ÖMERTAŞCI: Ne değişiyor,
Kukeltje

-3

işte tam kod pasajı 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();
   }
 }

Dosyanın çalışma zamanında oluşturulmasını istiyorsanız, dosya okuma mantığını değiştirebilirsiniz.


Bu, 1024 bayttan büyükse, girdi dosyasının yalnızca bir bölümünü alacaktır!
hinneLinks
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.