JSF streamed download on android device returns .h

2020-04-11 09:23发布

问题:

I'm currently facing a weird problem in my webpage on android devices.

What I want to do is to allow a user to download a pdf-file to his mobile device. Therefore I provide a download button as in the setup described here.

Everything works fine as long as I use my desktop browser *Mozilla Firefox 10.** but as soon as I change to my mobile device (SGS II, Android vers. 2.3.5) the result of the download depends on the browser-app I'm using.

Mozilla and Opera mobile:
Both seem to be able to download the file correctly.

Any other browser-app (built-in, Dolphin HD,...):
Downloads a file either named <filename>.pdf or <filename>.htm which both represent a .htm-file showing the html-source of the page.

What I've tried:

  • Used the StreamedContent method out of the PrimeFaces library

    public StreamedContent getFile() {
        // prepare file for download
        // reference webDAV directory and get file as stream
        this.file = new Helper().getWebDavFile(customerId, fileName);
    
        return file;
    }
    
  • Streamed the file manually to the page as described here. (Thx to BalusC)

    public void download() throws IOException {
    
        byte[] is = new Helper().getWebDavFileManually(customerId, fileName);
    
        FacesContext fc = FacesContext.getCurrentInstance();
        ExternalContext ec = fc.getExternalContext();
    
        ec.responseReset(); 
        ec.setResponseContentType("application/pdf");  
        ec.setResponseHeader("Content-Disposition", "attachment; filename=\"" + fileName.toUpperCase() + "\""); 
    
        OutputStream output = ec.getResponseOutputStream();
        output.write(is);
    
        fc.responseComplete(); 
    }  
    
  • Set an <a href=""> to a local copy of the file.
    (I'm currently using a <p:commandButton> so I've to use a method executing a redirect instead of returning a String but it works in both ways)

    public void goToLink() throws IOException {
    
        // get WebDAV file and save temporarily
        byte[] b = new Helper().getWebDavFileManually(customerId, fileName);
        String path = FacesContext.getCurrentInstance().getExternalContext().getRealPath("/") + fileName;
        File f = new File(path);
        try {
            FileOutputStream fos = new FileOutputStream(f);
            fos.write(b);
            link = "http://someurl/somepage/" + fileName;
        } 
        catch (FileNotFoundException e) {
            e.printStackTrace();
        } 
        catch (IOException e) {
            e.printStackTrace();
        }
    
        // use link
        ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
        ec.redirect(link);
    }
    

    This final approach worked fine even on my android device but I do not want to go this route if I can avoid it because the file gets streamed from a WebDAV an I'd have to save each file to the server. This would produce more IO load and would force me to manually clean-up.

The methods Helper().getWebDavFile and Helper().getWebDavFileManually either return a DefaultStreamedConten used by PrimeFaces or a byte[] for my own approach.

What I know so far:

Unfortunately not the solution for my problem :).
After many hours of using google I found out that there is the possibility of a double-http-post-request. This would cause androids internal download manager (used in case of broken file download) to send an additional post-request in which the state gets lost.

As described in this blog (see section GET, POST, REST [UPDATE 20120208]) there is someone facing the same problem. I've tried all approaches mentioned on this blog but didn't succeed.
On this forum someone analyzed the same behavior with WireShark and got nearly to the same conclusion.
I didn't found any more ressources so I'm stuck at this.

I've also posted on the PrimeFaces forum just to make sure that there aren't any known issues regarding the <p:fileDownload> component.

What I would like to know:

Am I missing something?
Is there any possibility to download a streamed file from an JSF (http-post operating) webpage on an android device?

Any help/suggestion/information would be appreciated!
Thanks in advance!

回答1:

OK, after I've faced some other issues I finally got the time to chek back on this problem.

After some more hours spended on using google I didn't got any new clues as already mentioned in my question.

It still is the fact that some android browsers aren't able to handle POST request and return the appropriate file.

Because of that I choose to give the servlet-approach (as mentioned in the comments and described here) a try and construct my own http-GET-request.

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    ...
    // Initialize servlet response 
    response.reset();
    response.setBufferSize(DEFAULT_BUFFER_SIZE);
    response.setContentType(mime);
    response.setHeader("Content-Length", String.valueOf(data.length));
    response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");

    // write to response
    BufferedOutputStream output = null;
    try {
        output = new BufferedOutputStream(response.getOutputStream());
        output.write(data);
    }
    finally {
        output.close();
    }

And voilà: the download works on any android device no matter which browser is used!

Thanks again @BalusC for pointing me in the right direction.

If I find the time I'll try the JSF-GET approach, too but at first I'm happy with this.

If there is anyone facing the same problem or able to suggest another solution I would appreciate any contribution!

This helped me, I'll have Fun :D!



回答2:

had the same problem. I used the example code from primefaces.org for downloading a pdf with mobile devices. It was working fine "on my machine" but not on a iPad Safari or Android browsers. Problem was the content type in the Example-Controller provided in the showcase:

file = new DefaultStreamedContent(stream, "image/jpg", "some.pdf");

Which works only for images. Well, brain on mode:

file = new DefaultStreamedContent(stream, "application/pdf", "some.pdf"); 

and everything works fine now.