我正在开发一个设置,它将一个http请求(用于测试目的的GET)带到java servlet。工作原理是,ser let从浏览器接收请求,解析它并通过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问题,但似乎没有任何关于这个特定问题,除非我错过了连接。
答案 0 :(得分:3)
这是最有趣的一句话:
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的article。