HttpServletResponse, кажется, периодически отправлять преждевременно - PullRequest
2 голосов
/ 16 февраля 2012

Я работаю над установкой, которая принимает http-запрос (GET для целей тестирования) к сервлету Java.Это работает так: сервер принимает запрос от браузера, анализирует его и отправляет через сокет TCP на «главный» сервер, который обрабатывает запрос и отправляет ответ обратно.Затем сервлет извлекает HttpServletResponse, который ранее был сохранен в ConcurrentHashMap, открывает PrintWriter и отправляет ответ обратно.Все идет гладко, за исключением того, что HttpServletResponse не всегда отправляет обратно информацию, записанную в PrintWriter.Браузер каждый раз получает ответ «ОК», но часто ответ не содержит никакой информации, которую я пытаюсь написать.

Ниже у меня есть код для первоначального 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, который вызывается по ответу (кроме конструктора, который назначает толькоlocal 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, но, кажется, ничто не касается этой конкретной проблемы, если только я не пропускаю соединение.

1 Ответ

3 голосов
/ 16 февраля 2012

Это самая интересная строка:

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

Я подозреваю, что sc это тот внешний TCP-сервер, который вы вызываете, и вы также передаете прослушиватель, который будет уведомлен о получении ответа.Теперь важное предположение: Прав ли я, что сервер TCP отправляет ответ асинхронно , уведомляя вашего слушателя в другом потоке ?

Если это так, это объясняетВаше поведение.В сервлетах до версии 3.0 вам приходилось обрабатывать весь запрос в doGet.Как только ваш код покидает doGet(), контейнер сервлета предполагает, что весь запрос был обработан, и отклоняет запрос.

Вы ввели условие гонки : doGet() возвращается без записи чего-либо ввыходной поток.Контейнеру требуется несколько миллисекунд, чтобы обработать ответ и отправить его обратно.Если в течение этого короткого периода времени ваш внешний TCP-сервер вернет данные и уведомит слушателя, данные пройдут.Но если сервер работает немного медленнее, вы отправляете ответ на соединение, которое уже было обработано.

Подумайте об этом следующим образом: браузер выполняет вызов, вызывается doGet(), который, в свою очередь, вызывает внутренний сервер,doGet() возвращает и контейнер сервлета предполагает, что вы сделали.Он отправляет обратно (пустой) ответ и забывает об этом запросе.Миллисекунды или даже секунды спустя ответ возвращается с внутреннего сервера.Но соединение пропало, оно уже отправлено, сокеты были закрыты, браузер обработал ответ.Вы не говорите своему контейнеру: эй, подождите, я еще не закончил с этим ответом! .

Решения

От худшего к лучшему:

  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, это гораздо лучший выбор в вашем случае использования, и область изменений будет очень ограниченной.

    IndoGet():

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

    и в HTTPServletResponseListener после завершения:

    asyncContext.complete();
    

    Дополнительный вызов startAsync() сообщает контейнеру: , хотяЯ вернулся с doGet(), я еще не закончил этот запрос.Пожалуйста, подождите .Я написал статью о Servlet 3.0 некоторое время назад.

...