Отмена длительной обработки сервлета при закрытии браузера

У меня есть сервлет, который долго обрабатывает запрос. Предполагается, что вы будете продолжать делать что-то в цикле внутри doPost и отправлять данные через устройство записи ответа. По сути, это постоянное добавление данных в клиентский браузер. Но проблемы возникают, когда клиент просто закрывает браузер. Несмотря на разорванное соединение, поток записи ответа в сервлете никогда не закрывается, поэтому сервлет не знает о нарушении соединения и продолжает сбрасывать данные в модуль записи без каких-либо ошибок. Как это возможно? И как обнаружить и отменить длительную обработку запросов в случае отключения браузера?

Я думаю, что checkError() в авторе ответа должен помочь, но это не сработает. Есть идеи, почему?

Это код сервлета, который никогда не останавливается:

protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
   HttpSession session = request.getSession();
   System.out.println("Session " + session.getId() + " started");

   response.setContentType("text/html;charset=UTF-8");

   PrintWriter out = response.getWriter();
   try
   {
       while (!out.checkError())
       {
           try
           {
               Thread.sleep(1000);
           } catch (InterruptedException ex)
           {
               ex.printStackTrace();
           }

           Date date = new Date();

           // TODO append output to the client browser here
           out.println(".....");

           System.out.println("Session " + session.getId() + "data sent at: " + date);

           out.flush();
       //break;  // _TEST
       }
   } finally
   {
       System.out.println("Session " + session.getId() + " finished");
       out.close();
   }
}

Спасибо,
Майк


person Ma99uS    schedule 06.02.2009    source источник


Ответы (6)


Единственный реальный способ справиться с этим - заставить браузер постоянно сообщать вам, что он все еще там, скажем, каждые 5-10 секунд. Если вы в течение минуты ничего не слышите, вы предполагаете, что клиент ушел, и забываете, что делаете.

Есть несколько способов реализовать это:

  • Мета-обновление: мне это не нравится, потому что это сбивает с толку и очевидно для пользователя;
  • iframe, который делает это (менее очевидно); или
  • Некоторая технология AJAX, которая, вероятно, является лучшим вариантом.

Нет реального способа определить, читает ли клиент то, что вы отправляете. Чтобы TCP-соединения прекратились, требуется время, и путь к клиенту в любом случае часто является непрямым (через прокси и т. д.), поэтому забудьте об этом.

person cletus    schedule 06.02.2009
comment
Похоже, вы правы. Мне нужно было бы иметь какой-то механизм для отправки ACK со стороны клиента, чтобы указать, что сервер должен отправлять данные. Это прискорбно и странно, потому что http использует TCP и, следовательно, предполагает, что он знает о соединении. - person Ma99uS; 06.02.2009
comment
Неверно описывать HTTP как TCP. Конечно, протокол HTTP есть, но Интернета нет (обязательно). Рассмотрим кэши Squid, которые обмениваются данными и доставляют контент по протоколу UDP. Кроме того, ваше веб-приложение может доставлять контент, закрывать соединение, и прокси-сервер теряет соединение с браузером. - person cletus; 06.02.2009
comment
Означает ли это, что если вы начали загрузку большого файла через веб-браузер, а затем закрыли его. Сервер по-прежнему будет отправлять вам весь файл, используя трафик и/или ресурсы сервера? - person Ma99uS; 06.02.2009

У сервлета есть выходной буфер, который должен заполниться до фактической отправки данных в браузер. Если этот буфер не заполнен, фактически данные по сети не отправляются. Пока вы не отправите данные по сети, вы не узнаете, что соединение закрыто.

Вы можете установить размер буфера, вызвав метод объекта ответа до получения выходного потока или средства записи.

person Martin OConnor    schedule 06.02.2009
comment
Я попытался добавить response.setBufferSize(0); но, похоже, это не имело никакого эффекта. - person Ma99uS; 06.02.2009
comment
Вы не можете установить его в ноль, вы можете установить его в какое-то маленькое значение и попытаться записать в буфер. - person Martin OConnor; 06.02.2009

Вы пытаетесь сделать что-то, что не поддерживается протоколом HTTP. Вся природа Сети основана на запросе/ответе.

Один из способов смоделировать то, что вам нужно, — использовать Ajax. Поток будет примерно таким:

  1. Браузер отправляет с помощью Ajax запрос пунктов с 1 по 10
  2. Сервер обрабатывает запрос, создает и отправляет ответ с элементами от 1 до 10.
  3. Браузер получает ответ, анализирует результат, отображает элементы с 1 по 10 и повторяет шаг 1, запрашивая элементы с 11 по 20.

Эта архитектура избавит вас от необходимости проверять «доступность» клиента, а также улучшит масштабируемость ваших серверных компонентов.

Я надеюсь, что это помогает.

person Handerson    schedule 06.02.2009

java.io.PrintWriter javadoc sez: Методы в этом классе никогда не генерируют исключения ввода-вывода, поэтому вместо этого используйте javax.servlet.SocketOutputStream (response.getOutputStream()), и в конечном итоге вы получите некоторое IOException.

person joe    schedule 17.11.2009

Ваш общий подход работает для меня (у меня есть длительная серверная задача, которая просто прерывается, если клиент зависает, используя checkError, после того, как какой-то разумный объем не может быть отправлен). Все, что я могу предложить, это обновиться до последней версии Java.

person andrew cooke    schedule 06.09.2013

У нас была аналогичная проблема. В нашем случае это был веб-сервер с включенным ESI (http://www-01.ibm.com/support/docview.wss?uid=swg27015501). Мы сделали дамп TCP, чтобы увидеть, что там произошло, и увидели, что пакет RES (после закрытия браузера) никогда не отправляется обратно в приложение. Веб-сервер получил его, но не отправил дальше. Поэтому функция print.checkError() никогда не возвращала значение true, и принтер продолжал сбрасывать данные. После отключения этой функции пакет RES был отправлен в приложение, и через некоторое время функция print.checkError() вернула значение true. Вот почему я наткнулся на ваш вопрос. Он продолжает отправлять 3 раза. После этого checkError() возвращает true.

person kinglite    schedule 27.04.2017