HttpServletResponse似乎会过早定期发送
我正在进行一个设置,它将一个http请求(用于测试目的的GET)带到java servlet。 工作原理是,ser从浏览器接收请求,解析它并通过TCP套接字将其发送到’main’服务器,后者处理请求并发回响应。 然后,servlet拉出先前存储在ConcurrentHashMap中的HttpServletResponse,打开PrintWriter,然后发回响应。 一切顺利,除了HttpServletResponse并不总是发回写入PrintWriter的信息。 浏览器每次都会收到“OK”响应,但通常响应不包含我尝试编写的任何信息。
下面我给出了初始的doGet的代码,它传递了HttpServletResponse实例,然后是写入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问题,但似乎没有任何关于这个特定问题,除非我错过了一个连接。
这是最有趣的一句话:
this.sc.send(msg, new HTTPServletResponseListener(response));
我怀疑sc
是你正在调用的外部TCP服务器,并且你也传递了一个监听器,以便在响应到达时得到通知。 现在重要的假设是: 我是对的,TCP服务器异步发送响应 ,通知另一个线程的监听器?
如果是这种情况,它会解释您的行为。 在3.0之前的servlet中,你必须在doGet
处理整个请求。 一旦你的代码离开了doGet()
,servlet容器就会假定已经处理了整个请求并丢弃了请求。
您已经引入了竞争条件 : doGet()
返回而不向输出流写入任何内容。 容器处理响应并将其发回需要几毫秒。 如果在此短时间内外部TCP服务器返回数据并通知侦听器,则数据将通过。 但是,如果服务器速度稍慢,则会向已处理的连接发送响应。
以这种方式思考:浏览器进行调用,调用doGet()
,然后调用后端服务器。 doGet()
返回并且servlet容器假定您已完成。 它发送回(空)响应并忘记此请求。 毫秒甚至几秒后,响应从后端服务器返回。 但连接已经消失,它已经被发送,套接字被关闭,浏览器呈现响应。 你没有告诉你的容器: 嘿,等等,我还没有完成那个回复! 。
解决方案
从最差到最好:
-
在
doGet()
主动等待/轮询响应。 -
使您的外部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);
-
使用Servlet 3.0异步支持,在您的用例中它是一个更好的选择,并且更改的范围将非常有限。
在
doGet()
:final AsyncContext asyncContext = request.startAsync(request, response);
一旦完成,就在
HTTPServletResponseListener
:asyncContext.complete();
对
startAsync()
的额外调用告诉容器: 即使我从doGet()
返回,我还没有完成这个请求。 请等一下 。 我不久前写了一篇关于Servlet 3.0的文章 。