小编典典

HttpServletResponse似乎会定期过早发送

java

我正在进行一个将http请求(用于测试目的的GET)发送到Java
Servlet的安装程序。它的工作方式是,服务程序从浏览器获取请求,进行解析,然后通过TCP套接字将其发送到“主”服务器,由“主”服务器处理请求并发送回响应。然后,该Servlet提取先前存储在ConcurrentHashMap中的HttpServletResponse,打开PrintWriter,然后将响应发送回去。一切都进行得很顺利,除了HttpServletResponse并不总是将写回信息的信息发送回PrintWriter。浏览器每次都会收到一个“
OK”响应,但是响应通常不包含我尝试编写的任何信息。

下面是初始化HttpServletResponse实例的初始doGet的代码,然后是写入Response缓冲区的方法。之后,还包括浏览器收到的响应。之后,对如何可靠地获得预期结果的一些观察,以帮助解决问题。

注意,唯一的变量似乎是响应是否被写入。我已经遍历了输出日志,并且在找到按预期方式编写响应的时间与未编写响应的时间之间找不到任何其他差异。我编写的HTTPServletResponseListener每次都会收到响应。

[使用Glassfish 3.1.1]

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

String js = request.getParameter("json");
// Omitting try-catch for space
Message msg = this.parser.parseToMessage(js);
this.sc.send(msg, new HTTPServletResponseListener(response));
}

以及在响应时调用的HTTPServletResponseListener方法(除了仅将本地HttpServletResponse分配给本地字段的构造函数,这是唯一的方法)

public void handleResponse(ResponseMessage response) {
  DataParser parser = new JSONParser();
  String temp = parser.parseToString(response);
  httpResponse.setContentType("application/json");
  httpResponse.addHeader("Hmm","yup");
  try {
     PrintWriter out = httpResponse.getWriter();
     out.println(temp);
     }  catch (IOException ex) {
        Logger.getLogger(HTTPServletReponseListener.class.getName()).log(Level.SEVERE,null,ex);         
     }
}

响应和浏览器收到它们:

当它按预期工作时:

当响应为空时:

HTTP/1.1 200 OK
X-Powered-By: Servlet/3.0 JSP/2.2 (GlassFish Server Open Source Edition 3.1.1 Java/Sun         Microsystems Inc./1.6)
Server: GlassFish Server Open Source Edition 3.1.1
Content-Length: 0
Date: Thu, 16 Feb 2012 15:26:35 GMT

如果响应是预期的:

HTTP/1.1 200 OK
Hmm: yup
X-Powered-By: Servlet/3.0 JSP/2.2 (GlassFish Server Open Source Edition 3.1.1 Java/Sun  Microsystems Inc./1.6)
Server: GlassFish Server Open Source Edition 3.1.1
Content-Type: application/json;charset=ISO-8859-1
Content-Length: 126
Date: Thu, 16 Feb 2012 15:27:30 GMT

观察结果:

通过执行以下步骤,我可以在95%的时间内获得响应。…否则,其成功率约为50%。

在浏览器上单击刷新…部署后的前几个请求往往更频繁地工作,至少等待5秒钟,然后再次单击刷新(发送另一个测试请求),这往往可以可靠地运行。如果失败,则必须等待15秒钟,然后它才能再次正常工作。

如果在等待响应之前编写了HttpServletResponse,则每次都可以使用。

非常感谢您抽出宝贵的时间阅读本文。我已经阅读了其他Stackoverflow问题,但是似乎没有任何东西可以解决这个特定问题,除非我缺少连接。


阅读 218

收藏
2020-11-01

共1个答案

小编典典

这是最有趣的一行:

this.sc.send(msg, new HTTPServletResponseListener(response));

我怀疑这sc是您正在呼叫的外部TCP服务器,并且您还会传递一个侦听器,以便在响应到达时得到通知。现在是一个重要的假设: TCP服务器异步发送响应
,在 另一个线程中 通知您的侦听器,对吗?

在这种情况下,它可以说明您的行为。在3.0之前的servlet中,您必须在中处理整个请求doGet。一旦您的代码离开doGet(),servlet容器就会假定整个请求已被处理并丢弃该请求。

您引入了一个 竞争条件
doGet()不向输出流写入任何内容而返回。容器需要几毫秒来处理响应并将其发送回。如果您的外部TCP服务器在短时间内返回数​​据并通知侦听器,则数据将通过。但是,如果服务器速度稍慢,则您正在发送对已处理的连接的响应。

可以这样考虑:浏览器进行呼叫,然后doGet()又调用后端服务器。该doGet()回报和servlet容器假定你做。它发回(空)响应,而忽略了该请求。毫秒甚至几秒后的响应从后端服务器返回。但是连接不见了,它已经被发送,套接字被关闭,浏览器呈现了响应。您没有在告诉您的容器:
嘿,等等,我还没有完成回复!

解决方案

从最坏到最好:

  1. 积极等待/轮询中的响应doGet()

  2. 阻止您的外部TCP服务器调用。更改TCP服务器外观,使其 返回 ResponseMessage并将侦听器代码移至doGet()。例:

         ResponseMessage responseMsg = this.sc.send(msg);
     DataParser parser = new JSONParser();
     String temp = parser.parseToString(responseMsg);
     httpResponse.setContentType("application/json");
     httpResponse.addHeader("Hmm","yup");
     PrintWriter out = httpResponse.getWriter();
     out.println(temp);
  1. 使用Servlet 3.0异步支持,这在您的用例中是一个更好的选择,更改范围将非常有限。

doGet()

    final AsyncContext asyncContext = request.startAsync(request, response);

并在HTTPServletResponseListener一旦你完成:

    asyncContext.complete();

额外的调用startAsync()告诉容器:
即使我从回来了doGet(),我也没有完成此请求。请稍等。我前段时间写了一篇关于Servlet 3.0
文章

2020-11-01