Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
338 views
in Technique[技术] by (71.8m points)

java - HttpServletResponse seems to periodically send prematurely

I'm working on a setup that takes an http request (GET for testing purposes) to java servlet. How it works is, the ser let takes a request from the browser, parses it and sends it via TCP socket to the 'main' server, which processes the request and sends a response back. The servlet then pulls the HttpServletResponse that was previously stored in an ConcurrentHashMap, opens up the PrintWriter, and sends a response back. Everything's going smoothly, except that the HttpServletResponse doesn't always send back the information written to the PrintWriter. The browser receives an "OK" response every time, but often the response doesn't contain any of the information I try to write.

Below I have the code for the initial doGet that hands off the HttpServletResponse instance,then the method that writes to the Response buffer. Included after that are the responses as the browser receives them. After that, some observations on how to reliably get the expected result, in case that helps pin down the problem.

Note that the only variable seems to be whether or not the Response gets written; I've poured over output logs, and can't find any other differences between the times where the Response is written as expected, and the times it isn't. The HTTPServletResponseListener I wrote receives the response every time.

[using 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));
}

And the HTTPServletResponseListener method that is called on response (other than the constructor which only assigns the local HttpServletResponse to a local field, this is the only method)

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);         
     }
}

Responses and the browser receives them:

When it works as intended:

When the response is empty:

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

When the response is as intended:

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

Observations:

i can get a response 95% of the time by following these steps.... otherwise, its around 50% success rate.

hit refresh on browser... the first couple requests after deploying tend to work more often wait at least 5 seconds before hitting refresh again (sending another test request) this tends to work reliably. If it fails, you have to wait 15 seconds, and then it tends to work again.

if the HttpServletResponse is written BEFORE waiting for the response, it works every time.

Thank you so much for taking the time to read this. I've read other Stackoverflow questions, but nothing seems to touch on this particular problem, unless I'm missing a connection.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This is the most interesting line:

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

I suspect the sc is this external TCP server you are calling and you are also passing a listener to be notified when the response arrives. Now the important assumption: am I right that the TCP server sends response asynchronously, notifying your listener in a different thread?

If this is the case, it explains your behaviour. In pre-3.0 servlets you had to handle the whole request within doGet. Once your code leaves doGet(), servlet container assumes the whole request has been handled and discards the request.

You have introduced a race condition: doGet() returns without writing anything to the output stream. It takes few milliseconds for the container to handle the response and send it back. If during this short period of time your external TCP server returns data and notifies listener, the data will go through. But if the server is a bit slower, you are sending response to a connection that has already been handled.

Think about it this way: browser makes a call, doGet() is called which in turns calls backend server. The doGet() returns and servlet container assumes you are done. It sends back (empty) response and forgets about this request. Milliseconds or even seconds later response comes back from the back-end server. But the connection is gone, it has already been sent, sockets were closed, browser rendered the response. You are not telling your container: hey, wait, I haven't finished with that response!.

Solutions

From worst to best:

  1. Actively wait/poll for response in doGet().

  2. Make your external TCP server call blocking. Change the TCP server facade so that it returns ResponseMessage and move listener code to doGet(). Example:

     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. Use Servlet 3.0 asynchronous support, it is a much better choice in your use case and the scope of changes will be very limited.

    In doGet():

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

    and in the HTTPServletResponseListener once you're done:

    asyncContext.complete();
    

    The extra call to startAsync() tells the container: even though I have returned from doGet(), I haven't finished with this request. Please hold. I wrote an article about Servlet 3.0 some time ago.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...