通过JSF应用程序从任何Web浏览器强制进行“另存为”对话框

11

我创建了一个JSF应用程序,我想在页面中嵌入一个链接,当用户点击链接时,会触发后端bean进行一些XML序列化的操作,并弹出一个保存对话框,让用户选择保存文件的位置。我已经编写了JAXB代码。

这应该怎么做呢?

谢谢。

3个回答

33
将HTTP Content-Disposition头设置为attachment。这将弹出一个“另存为”对话框。您可以使用HttpServletResponse#setHeader()来实现。您可以通过ExternalContext#getResponse()获取JSF下的HTTP servlet响应。
在JSF上下文中,您只需要确保在完成后调用FacesContext#responseComplete()以避免出现IllegalStateException错误。
启动示例:
public void download() throws IOException {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    ExternalContext externalContext = facesContext.getExternalContext();
    HttpServletResponse response = (HttpServletResponse) externalContext.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("application/xml"); // Check http://www.iana.org/assignments/media-types for all types. Use if necessary ServletContext#getMimeType() for auto-detection based on filename.
    response.setHeader("Content-disposition", "attachment; filename=\"name.xml\""); // The Save As popup magic is done here. You can give it any filename you want, this only won't work in MSIE, it will use current request URL as filename instead.

    BufferedInputStream input = null;
    BufferedOutputStream output = null;

    try {
        input = new BufferedInputStream(getYourXmlAsInputStream());
        output = new BufferedOutputStream(response.getOutputStream());

        byte[] buffer = new byte[10240];
        for (int length; (length = input.read(buffer)) > 0;) {
            output.write(buffer, 0, length);
        }
    } finally {
        close(output);
        close(input);
    }

    facesContext.responseComplete(); // Important! Else JSF will attempt to render the response which obviously will fail since it's already written with a file and closed.
}

非常感谢。我已经把代码放进去了,但现在当我点击链接时,浏览器(FF)会替换当前页面为包含文件内容的页面,而不是弹出下载窗口。我可能做错了什么? - volvox
1
请尝试在其他浏览器(IE、Chrome)中打开,或使用干净的配置文件重新启动FF。有时候,Web浏览器被配置为XML文件的默认应用程序,并且当下载时应自动打开XML文件。 - BalusC
1
哦,还要确保这不是异步(ajax)请求,而只是同步(“普通香草”)请求。也就是说,只使用h:commandLinkh:commandButton,但不要使用RichFaces、Ajax4jsf、IceFaces等ajax动力的UICommand组件。 - BalusC
我已经在FF中指定了XML文件类型的保存,并切换到h:commandButton,现在当我点击按钮时,整个页面重新加载,没有出现保存对话框。该应用程序将不支持IE或Chrome(甚至无法测试)。现在不确定该去哪里查找问题。 - volvox
它是否在h:form内?方法是否被实际调用?遵循这个列表来相互检查。 - BalusC
@BalusC 我正在使用 CDI bean,而不是 BufferedInputStream 和 BufferedOutputStream,因为我使用 getWriter() 将我的 XML 字符串写入响应,因为我已经将其保存在 String 变量中。该方法在应用程序运行时被调用,但没有出现下载对话框。我错过了什么吗? - user579674

3
使用HTTP头content-disposition: attachment

0
有时候你需要通过调用response.getWriter().flush();来强制让写入器将内容发送给客户端,然后再关闭写入器。在我的情况下,这会触发“另存为”弹出窗口。

1
只有在请求-响应链包含一个返回损坏的自定义writer的损坏的自定义HttpServletResponseWrapper实现时,才需要这样做。对于标准的Servlet和JSF实现,这绝对不是必需的。解决方案应该是修复包装器/写入器实现,而不是在代码中手动刷新writer。 - BalusC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接