如何通过Ajax请求接收服务器延迟10秒至最长1分钟的响应?解决请求立即返回空数据问题
问题根源
你的代码之所以会立刻弹出空alert,核心是两个致命错误:
- Servlet默认单例 + 容器同步请求处理机制:
doGet方法执行完所有同步代码后,Tomcat会直接判定请求处理结束,把尚未写入任何内容的空响应直接返回给客户端,你后续在update方法中写入的内容根本没有机会被发到原请求上。 - 线程安全问题:你把
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
相关产品推荐
相关产品推荐

