XPages中第二个下载按钮点击存在15-20秒延迟问题求助
连续点击两个下载按钮出现15-20秒延迟的原因分析
我有一个表单,包含两个用于存储附件的富文本字段;对应的XPage上有两个文件下载控件(分别对应每个字段)和两个按钮。按钮调用Java类中的方法,生成对应字段附件的Zip文件供浏览器下载。所有功能正常,但存在一个问题:无论先下载哪个Zip文件,点击第二个按钮后都需要等待15-20秒才会响应。
XPage代码
<?xml version="1.0" encoding="UTF-8"?> <xp:view xmlns:xp="http://www.ibm.com/xsp/core"> <xp:this.data> <xp:dominoDocument var="document1" formName="Document"></xp:dominoDocument> </xp:this.data> <xp:panel tagName="div" style="margin-left:auto; margin-right:auto; width:50%"> <xp:br></xp:br> <xp:fileDownload rows="30" id="fileDownload1" displayLastModified="false" value="#{document1.Body1}"> </xp:fileDownload> <xp:button value="Download Zipfile 1" id="button1"> <xp:eventHandler event="onclick" submit="true" refreshMode="complete"> <xp:this.script><![CDATA[console.log("Button1 click")]]></xp:this.script> <xp:this.action><![CDATA[#{javascript:uk.co.mp.DownloadAllAttachments.downloadFromField(context.getUrlParameter("documentId"), "Body1"); }]]></xp:this.action> </xp:eventHandler> </xp:button> <xp:br></xp:br> <xp:fileDownload rows="30" id="fileDownload2" displayLastModified="false" value="#{document1.Body2}"> </xp:fileDownload> <xp:button value="Download Zipfile 2" id="button2"> <xp:eventHandler event="onclick" submit="true" refreshMode="complete"> <xp:this.script><![CDATA[console.log("Button2 click")]]></xp:this.script> <xp:this.action><![CDATA[#{javascript:uk.co.mp.DownloadAllAttachments.downloadFromField(context.getUrlParameter("documentId"), "Body2"); }]]></xp:this.action> </xp:eventHandler> </xp:button> </xp:panel> </xp:view>
Java类代码
package uk.co.mp; import java.io.BufferedInputStream; import java.io.OutputStream; import java.util.Iterator; import java.util.Map; import java.util.Vector; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import javax.faces.component.UIViewRoot; import javax.faces.context.FacesContext; import javax.servlet.http.HttpServletResponse; import lotus.domino.Document; import lotus.domino.EmbeddedObject; import lotus.domino.RichTextItem; import com.ibm.xsp.designer.context.XSPContext; import com.ibm.xsp.model.domino.DominoUtils; import lotus.domino.Session; import lotus.domino.Database; import com.ibm.xsp.extlib.util.ExtLibUtil; public class DownloadAllAttachments { @SuppressWarnings({ "unchecked", "rawtypes" }) public static void downloadFromField(String documentUNID, String fieldName) { // Initialize global XPage objects FacesContext facesContext = FacesContext.getCurrentInstance(); XSPContext context = XSPContext.getXSPContext(facesContext); UIViewRoot view = facesContext.getViewRoot(); Map viewScope = view.getViewMap(); try { if (documentUNID == null || documentUNID.trim().equals("")) { viewScope.put("errorString", "URL parameter \"documentUNID\" or \"u\" is required."); view.setRendered(true); return; } Session s = ExtLibUtil.getCurrentSessionAsSigner(); Database db = s.getCurrentDatabase(); // Get and validate the document from which attachments would be zipped Document downloadDocument = null; downloadDocument = db.getDocumentByUNID(documentUNID); // Get and validate zip file name from URL String zipFileName = fieldName + ".zip"; boolean bAnyAttachments = false; EmbeddedObject embeddedObj = null; Vector attachments = null; // ...check for attachments in the field supplied and collect the names of the attachments RichTextItem rtitem = (RichTextItem)downloadDocument.getFirstItem(fieldName); Vector vec = rtitem.getEmbeddedObjects(); attachments = new Vector(vec.size()); if (!vec.isEmpty()) { Iterator it = vec.iterator(); while(it.hasNext()){ embeddedObj = (EmbeddedObject)it.next(); if (embeddedObj != null) { attachments.add(embeddedObj.getName()); bAnyAttachments = true; } } } //By this point we should have a list of the attachment names we want to include in the zip file //If we have not found any attachments then return a message if (!bAnyAttachments) { viewScope.put("errorString", "<center><h3>No attachments found in the Document.</h3></center>"); return; } // Set response header values HttpServletResponse response = (HttpServletResponse) facesContext.getExternalContext().getResponse(); response.setHeader("Cache-Control", "no-cache"); response.setDateHeader("Expires", -1); response.setContentType("application/zip"); response.setHeader("Content-Disposition", "attachment; filename=" + zipFileName); // Get stream related objects OutputStream outStream = response.getOutputStream(); ZipOutputStream zipOutStream = new ZipOutputStream(outStream); BufferedInputStream bufferInStream = null; // Get all attachment names and loop through all of them adding to zip for (int i = 0; i < attachments.size(); i++) { embeddedObj = downloadDocument.getAttachment(attachments.get(i).toString()); if (embeddedObj != null) { bufferInStream = new BufferedInputStream(embeddedObj.getInputStream()); int bufferLength = bufferInStream.available(); byte data[] = new byte[bufferLength]; bufferInStream.read(data, 0, bufferLength); // Read the attachment data ZipEntry entry = new ZipEntry(embeddedObj.getName()); zipOutStream.putNextEntry(entry); zipOutStream.write(data); // Write attachment into Zip file bufferInStream.close(); embeddedObj.recycle(); } } // Clean up and close objects downloadDocument.recycle(); zipOutStream.flush(); zipOutStream.close(); outStream.flush(); outStream.close(); facesContext.responseComplete(); } catch (Exception e) { viewScope.put("errorString", "Error while zipping attachments.<br>" + e.toString()); e.printStackTrace(); } } }
问题原因
延迟的核心来自两个层面的阻塞:
- 浏览器同源请求串行限制:主流浏览器对同一域名的会话绑定请求会进行串行处理(而非并发),第一个下载请求占用连接后,第二个请求会被浏览器排队,直到第一个请求的连接释放。
- Domino会话资源锁定:Domino的XPage会话默认会对同一用户的请求串行化,避免并发操作引发的资源冲突。第一个下载请求在处理时会占用会话资源,第二个请求必须等待第一个请求完成所有流操作、资源回收后才能启动。
- 资源回收不及时:代码中
downloadDocument.recycle()放在流关闭之后,若第一个下载的文件较大或网络延迟,会话资源会被长时间占用,进一步拉长等待时间。
解决方案
1. 改用独立窗口异步下载
修改按钮提交逻辑,通过新窗口发起下载请求,避免阻塞原页面会话:
<xp:button value="Download Zipfile 1" id="button1"> <xp:eventHandler event="onclick" submit="false"> <xp:this.script><![CDATA[ const docId = context.getUrlParameter("documentId"); window.open(`/yourApp.nsf/DownloadZip.xsp?documentId=${docId}&field=Body1`, "_blank"); ]]></xp:this.script> </xp:eventHandler> </xp:button>
同时创建DownloadZip.xsp专门处理下载请求,在页面的beforePageLoad事件中调用Java下载方法,让每个请求独立占用会话资源,互不阻塞。
2. 优化Java代码的资源管理
- 提前回收非必要的Domino对象,比如收集完附件名称后立即回收
RichTextItem和临时的EmbeddedObject:
// 收集附件名称时立即回收对象 while(it.hasNext()){ embeddedObj = (EmbeddedObject)it.next(); if (embeddedObj != null) { attachments.add(embeddedObj.getName()); bAnyAttachments = true; embeddedObj.recycle(); // 提前回收 } } rtitem.recycle(); // 回收富文本项
- 改用小缓冲区读取文件,避免一次性加载大文件到内存,加快流处理速度:
// 替换原文件读取逻辑 byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = bufferInStream.read(buffer)) != -1) { zipOutStream.write(buffer, 0, bytesRead); }
3. 调整响应头优化并行处理
修改响应头,允许浏览器并行处理请求(需注意缓存风险):
response.setHeader("Cache-Control", "private, max-age=0"); response.setHeader("Pragma", "public");
内容的提问来源于stack exchange,提问作者Martin Perrie
相关产品推荐
相关产品推荐

