HCL Portal 9.5中PDF下载按钮触发Servlet重复调用问题问询
在HCL Portal 9.5中PDF Servlet被重复调用的问题排查与解决
问题描述
我在HCL Portal 9.5上部署了一款包含PDF Servlet的JSF Portlet,该Servlet用于在新标签页打开PDF文件。目前遇到的问题是:点击新标签页中的下载按钮时,该Servlet会被再次调用。请问这是什么原因?能否避免该现象?
Servlet类代码
public class PdfDownloadServlet extends HttpServlet { private static final long serialVersionUID = 1L; // 存储SNMP错误 private static HashMap<SnmpNapakeEnum, Date> snmpMap = new HashMap<SnmpNapakeEnum, Date>(); @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { processRequest(req, resp); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { processRequest(req, resp); } protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String param1 = ... // 读取URL参数 try { writeOutputStream(response); // 注:此处方法调用缺少param1参数,与方法定义不匹配,请检查 } catch (Exception e) { LOG.error(e.getMessage(), e); OutputStream os = null; try { os = response.getOutputStream(); os.write(e.getMessage().getBytes("UTF-8")); } finally { if (ObjectUtils.isNotEmpty(os)) { os.close(); os = null; } } } } private void writeOutputStream(HttpServletResponse response, String param1) throws IOException, ParseException { OutputStream os = null; try { byte[] data = ... // 调用EJB方法获取PDF字节数组 response.setContentType("application/pdf"); response.setContentLength(data.length); response.setHeader("Content-Disposition", "inline;filename=Sample.pdf"); os = response.getOutputStream(); os.write(data); } finally { if (ObjectUtils.isNotEmpty(os)) { os.close(); os = null; } } } }
web.xml配置
<servlet> <description>Pdf download</description> <display-name>PdfDownloadServlet</display-name> <servlet-name>PdfDownloadServlet</servlet-name> <servlet-class>si.nijz.rizddz.portlet.skupno.servlets.ObvestiloOStDelavcaServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>PdfDownloadServlet</servlet-name> <url-pattern>/PdfDownloadServlet</url-pattern> </servlet-mapping>
补充实现代码
后台Bean动作监听器
public void onBtnGeneratePdf() { String date1AsStr = "2020-02-13"; PrimeFaces.current().executeScript("generatePdf('" + date1AsStr + "');"); }
JavaScript触发函数
generatePdf : function(date1AsStr) { try { var path = $('[id$=hidContextPath]').val() + '/PdfDownloadServlet?date1AsStr=' + date1AsStr; window.open(path); } catch (e) { ... } return true; }
JSF按钮代码
<p:commandButton id="btnGenerate" icon="ui-icon-document" actionListener="#{myBean.onBtnGeneratePdf}" value="Generate" />
原因分析
- 浏览器PDF查看器默认行为:当通过
Content-Disposition: inline在新标签页打开PDF时,浏览器内置PDF查看器的下载按钮本质是重新向服务器发起资源请求,因此会再次调用Servlet。 - 缺少缓存控制配置:当前Servlet未设置任何缓存响应头,浏览器无法复用已下载的PDF内容,每次点击下载都会重新请求服务器。
解决方案
方案一:添加缓存响应头(推荐,保留原打开方式)
在writeOutputStream方法中添加缓存控制头,让浏览器缓存PDF内容,点击下载时直接读取本地缓存,不再触发Servlet调用:
private void writeOutputStream(HttpServletResponse response, String param1) throws IOException, ParseException { OutputStream os = null; try { byte[] data = ... // 调用EJB方法获取PDF字节数组 response.setContentType("application/pdf"); response.setContentLength(data.length); // 设置缓存有效期为1小时,可根据需求调整 response.setHeader("Cache-Control", "public, max-age=3600"); response.setHeader("Expires", new Date(System.currentTimeMillis() + 3600 * 1000).toString()); response.setHeader("Last-Modified", new Date().toString()); response.setHeader("Content-Disposition", "inline;filename=Sample.pdf"); os = response.getOutputStream(); os.write(data); } finally { if (ObjectUtils.isNotEmpty(os)) { os.close(); os = null; } } }
注意:若PDF内容随请求参数变化,需确保缓存能区分不同参数的PDF,或调整缓存有效期避免用户获取旧内容。
方案二:直接触发下载(改变打开方式)
将Content-Disposition改为attachment,点击按钮后直接下载PDF,无需在新标签页打开,自然不会触发后续请求:
response.setHeader("Content-Disposition", "attachment;filename=Sample.pdf");
方案三:预生成静态PDF资源
生成PDF后保存到服务器静态资源目录或缓存中,新标签页打开静态文件URL,点击下载时请求静态资源,不会触发Servlet。需额外处理:
- 生成唯一文件名区分不同参数的PDF
- 定时清理过期临时文件
内容的提问来源于stack exchange,提问作者peterremec
相关产品推荐
相关产品推荐

