You need to enable JavaScript to run this app. 如何通过Ajax请求接收服务器延迟10秒至最长1分钟的响应?解决请求立即返回空数据问题
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Ajax请求接收服务器延迟10秒至最长1分钟的响应?解决请求立即返回空数据问题

问题根源

你的代码之所以会立刻弹出空alert,核心是两个致命错误:

  1. Servlet默认单例 + 容器同步请求处理机制:doGet方法执行完所有同步代码后,Tomcat会直接判定请求处理结束,把尚未写入任何内容的空响应直接返回给客户端,你后续在update方法中写入的内容根本没有机会被发到原请求上。
  2. 线程安全问题:你把HttpServletResponse存在Servlet的成员变量里,Servlet是多请求共享的单实例,多个并发请求会直接覆盖这个变量;同时让Servlet本身作为Observer的设计也完全不适用——所有请求触发的update都会操作同一个实例,逻辑完全混乱。

正确实现方案

需要使用Servlet 3.0+提供的异步请求上下文挂起请求,直到游戏开始事件触发再提交响应,同时彻底摒弃成员变量存请求对象的错误设计:

1. 服务端Servlet改造

  • 开启异步支持,设置最长1分钟的异步超时
  • 每个请求绑定独立的Observer实例,持有异步上下文而非共享Servlet实例
  • 监听异步超时/断开事件,及时移除Observer防止内存泄漏
@WebServlet(name = "GameStartWaitServlet", value = "/Servlet", asyncSupported = true) // 必须开启异步支持
public class GameStartWaitServlet extends HttpServlet {
    // 不要存任何request/response到成员变量,Servlet是单例多线程共享的
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 安全获取cookie,避免硬索引越界
        int gameId = -1;
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie c : cookies) {
                if ("gameId".equals(c.getName())) { // 替换为你实际的cookie名
                    gameId = Integer.parseInt(c.getValue());
                    break;
                }
            }
        }
        if (gameId == -1) {
            response.setStatus(400);
            response.getWriter().write("缺少有效游戏ID");
            return;
        }

        Maze maze = register.gamePool.getGame(gameId);
        GameStatus gameStatus = maze.getGameStatus();

        // 启动异步上下文,挂起请求,最长等待60秒(符合你要求的1分钟上限)
        AsyncContext asyncContext = request.startAsync(request, response);
        asyncContext.setTimeout(60 * 1000);

        // 为当前请求创建独立的Observer,而非用Servlet本身当Observer
        Observer gameStartObserver = new Observer() {
            @Override
            public void update(Observable o, Object arg) {
                GameStatus status = (GameStatus) o;
                try {
                    if (asyncContext.getResponse().isCommitted()) return;
                    // 游戏开始触发时写入响应内容
                    if (status.isGameStart()) {
                        String json = new Gson().toJson(status.isGameStart());
                        asyncContext.getResponse().setContentType("application/json;charset=utf-8");
                        asyncContext.getResponse().getWriter().write(json + "here is response in json");
                    } else {
                        // 超时未开始的响应
                        asyncContext.getResponse().setStatus(503);
                        asyncContext.getResponse().getWriter().write("等待游戏开始超时");
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    // 完成异步请求,提交响应
                    asyncContext.complete();
                    // 移除Observer,防止内存泄漏
                    o.deleteObserver(this);
                }
            }
        };

        // 给异步上下文加监听器,处理用户提前断开、超时的场景,及时移除Observer
        asyncContext.addListener(new AsyncListener() {
            @Override
            public void onComplete(AsyncEvent event) throws IOException {
                gameStatus.deleteObserver(gameStartObserver);
            }
            @Override
            public void onTimeout(AsyncEvent event) throws IOException {
                gameStartObserver.update(gameStatus, null);
            }
            @Override
            public void onError(AsyncEvent event) throws IOException {
                gameStatus.deleteObserver(gameStartObserver);
            }
            @Override
            public void onStartAsync(AsyncEvent event) throws IOException {}
        });

        // 把独立的Observer绑定到当前游戏状态上
        gameStatus.addObserver(gameStartObserver);
    }
}

2. 前端Ajax微调

jQuery Ajax默认无超时限制,只要补充超时回调处理即可,不需要修改核心请求逻辑:

$(document).ready(function () {
    $.ajax({
        type: "GET",
        url: "/WNA_MazeGame_Online_war_exploded/Servlet",
        timeout: 65 * 1000, // 比服务端60秒多留5秒网络缓冲
        success: function (data) {
            alert("data:" + data + "\n");
        },
        error: function (xhr) {
            // 处理超时、连接断开等错误
            alert("请求失败:" + (xhr.responseText || "超时或网络错误"));
        }
    });
});

注意事项
  • 绝对不要在Servlet、Filter等全局共享组件的成员变量中存储HttpServletRequest/HttpServletResponse实例,线程安全完全无法保障
  • 异步请求的资源占用要做好上限控制,避免大量挂起请求把服务端线程池占满
  • 硬索引取cookie(比如你之前写的cookie[2])非常容易出现越界异常,必须按cookie名匹配取值

内容的提问来源于stack exchange,提问作者noob

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.03 06:15:04