You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();
        }
    }
}

问题原因

延迟的核心来自两个层面的阻塞:

  1. 浏览器同源请求串行限制:主流浏览器对同一域名的会话绑定请求会进行串行处理(而非并发),第一个下载请求占用连接后,第二个请求会被浏览器排队,直到第一个请求的连接释放。
  2. Domino会话资源锁定:Domino的XPage会话默认会对同一用户的请求串行化,避免并发操作引发的资源冲突。第一个下载请求在处理时会占用会话资源,第二个请求必须等待第一个请求完成所有流操作、资源回收后才能启动。
  3. 资源回收不及时:代码中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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 03:17:03