上个星期,要做一个利用struts1.x作文件下载功能的网页,要求点击链接后,开始弹出下载对话框,下载成功后,转向成功信息页面,失败后,转向error信息页面。
于是找啊找,单纯的文件下载功能还是挺容易的,既可以自己写,也可以用类似smartupload这类现成的包。 至于为什么不用直接的链接,是为了防止服务器文件结构泄漏,有心人其他文件也可以随意下载,那岂不是麻烦?
如果要自己写得话,action的写法大致有三 1, 用输入流,输出流 2, 用dispatcher 3, 用downloadAction
方法3是struts推荐的官方写http://wiki.apache.org/struts/StrutsFileDownload 写的时候,只要继承downloadAction类,然后重写getStreamInfo方法就可以了。但是看一下这个类的src http://www.docjar.org/html/api/org/apache/struts/actions/DownloadAction.java.html 就明白,这个类的getStreamInfo会把结果传给execute方法,而execute方法的最后却被注明
// Tell Struts that we are done with the response. return null;
意味着根本不可能做任何转向。至于试着再重写一下execute方法,好像没有人这么办,在google.com上以关键字downloadAction redirect 或者downloadAction foward,也没有任何可以有用的结果,所以首先放弃了这个方法。不过这个方法还是最简单最可靠的,仿照它给的例子,几行代码就完事,真好。
方法2 以前单独在jsp中,这种方法可能挺流行的,不过在action中一样行不通。 例如代码
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { FileDownLoadForm fdform =(FileDownLoadForm)form; String path = fdform.getFilePath(); response.setContentType("application/x-download"); String filenamedownload = path; String filenamedisplay = path.substring(path.lastIndexOf("/")+1); try { filenamedisplay = URLEncoder.encode(filenamedisplay,"UTF-8"); response.addHeader("Content-Disposition","attachment;filename=" + filenamedisplay); RequestDispatcher dispatcher = request.getRequestDispatcher(filenamedownload); if(dispatcher != null) { dispatcher.forward(request,response); } response.flushBuffer(); return null ; // return mapping.findForward("success"); }catch(FileNotFoundException e){ e.printStackTrace(); return mapping.findForward("error"); }catch(Exception e){ e.printStackTrace(); return null; } }
解释1 为什么成功后return null 因为前面response 已经提交过,没办法再forward,否则报错
解释2 为什么FileNotFoundException转到error画面,因为对话框中,用户如果点取消按钮,tomcat会报ClientAbortException
假如连这个exception都抓掉,然后进行forward,一样会报response已经提交过的错误,根本无法转向。所以干脆就留
着它好了。
方法1 其实根方法3一样,输入流,输出流,有兴趣地可以用IOUtils.copy(in, response.getOutputStream());
没兴趣的可以参照以下代码,我也是参考论坛上一位兄弟的
public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { FileDownLoadForm fdform =(FileDownLoadForm)form; String path = fdform.getFilePath(); BufferedInputStream bis = null; BufferedOutputStream bos = null; InputStream fis = null; OutputStream fos = null; try{ File uploadFile = new File(path); fis = new FileInputStream(uploadFile); bis = new BufferedInputStream(fis); fos = response.getOutputStream(); bos = new BufferedOutputStream(fos); response.reset(); response.setHeader("Content-disposition", "attachment;filename =" + URLEncoder.encode(path.substring(path.lastIndexOf("/")+1), "utf-8")); int bytesRead = 0; byte[] buffer = new byte[4096]; while((bytesRead = bis.read(buffer,0,4096)) != -1){ bos.write(buffer, 0, bytesRead); } bos.flush(); fis.close(); bis.close(); fos.close(); bos.close(); //return mapping.findForward("success"); return null; }catch(FileNotFoundException e){ e.printStackTrace(); return mapping.findForward("error"); }catch(Exception e){ e.printStackTrace(); return null; } }
解释1 为什么要response.reset(); 假如这个方法是写在一个图片的链接上,如果不reset,就没办法多次下载,
不过对我来说现在没什么影响
解释2 对话框那块为什么只显示文件名 开始的时候我把整个路径都写进去了,结果倒也能下载,可使整个文件的路径都暴露了,跟直接用链接没什么区别了,所以一定要只写文件名
以上3个方法都不能实现成功后转向成功页面的问题,在javaeye和csdn上询问也没有什么结果,不过我的确是见过类似的网页,不知道他们是怎么完成的?始终疑问阿。
没有办法,只好考虑作个假的成功画面,用DispatchAction
一个action1 ,实现转向成功页面功能 , 一个action 2 ,实现下载功能
工作流程为 点击链接, 甭管文件存在不存在,直接进入成功画面,在成功画面打开的同时,执行action2
<script type="text/javascript"> function download() { window.location ="download.do?method=action2&filePath="+"<bean:write name="filePath" />"; } </script> </head> <body onload=download()>
如果文件找不到,则成功画面会转向到失败画面,否则会留在成功画面并弹出对话框