【问题标题】:HttpServletResponse seems to periodically send prematurelyHttpServletResponse 似乎会定期过早发送
【发布时间】:2012-02-16 18:27:06
【问题描述】:

我正在研究一个将 http 请求(用于测试目的的 GET)发送到 java servlet 的设置。它的工作原理是,ser 让从浏览器获取请求,对其进行解析并通过 TCP 套接字将其发送到“主”服务器,该服务器处理请求并发送回响应。然后,servlet 提取之前存储在 ConcurrentHashMap 中的 HttpServletResponse,打开 PrintWriter,然后发回响应。一切都很顺利,除了 HttpServletResponse 并不总是将写入 PrintWriter 的信息发回。浏览器每次都会收到“OK”响应,但响应通常不包含我尝试编写的任何信息。

下面是初始 doGet 的代码,它传递 HttpServletResponse 实例,然后是写入响应缓冲区的方法。之后包括浏览器收到的响应。之后,对如何可靠地获得预期结果进行一些观察,以防有助于确定问题。

请注意,唯一的变量似乎是响应是否被写入;我已经倾注了输出日志,并且找不到按预期写入响应的时间与未按预期写入的时间之间的任何其他差异。我写的 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 问题,但似乎没有涉及到这个特定问题,除非我错过了连接。

【问题讨论】:

  • sc.send() 调用是非阻塞的吗?如果是这样,那么如果 servlet 容器将请求/响应对象池化(大多数情况下都是这样),那么您可能会遇到问题。
  • 它确实是非阻塞的,我想你和下面的@tomasz 已经死了,我会尽快更新这个:)

标签: java servlets glassfish


【解决方案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);
    
  3. 使用 Servlet 3.0 异步支持,在您的用例中这是一个更好的选择,并且更改的范围将非常有限。

    doGet():

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

    完成后在HTTPServletResponseListener 中:

    asyncContext.complete();
    

    startAsync() 的额外调用告诉容器:即使我已经从doGet() 返回,我还没有完成这个请求。请按住。我前段时间写了一篇关于 Servlet 3.0 的article

【讨论】:

  • 嗯,我认为你一针见血。在我有机会尝试您今晚提供的异步解决方案之前,我将推迟将其标记为已回答,但您的解释与我所看到的完全吻合。感谢您的回复!
  • @DaveH: async 支持特别诱人,因为它不会阻塞 HTTP 线程,提供更好的可扩展性和更简洁的代码。我迫不及待想知道这是否是问题所在。
  • 有机会试用异步解决方案,它就像一个魅力!再次感谢您的彻底回复,它非常很有帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-05-31
  • 1970-01-01
  • 2016-12-21
  • 1970-01-01
  • 1970-01-01
  • 2020-07-28
  • 1970-01-01
相关资源
最近更新 更多