Веб-сокет Tyrus: IllegalStateException не может установить WriteListener для неасинхронного запроса

У меня есть стандартная конечная точка веб-сокета, основанная на реализации Tyrus, которая время от времени запускает java.lang.IllegalStateException: Cannot set WriteListener for non-async or non-upgrade request. Мы работаем на Payara 4.1.

Моя стандартная реализация

@ServerEndpoint(value = "...", decoders=MessageDecoder.class, encoders=MessageEncoder.class)
public class EndpointImpl extends AbstractEndpoint{
    // onOpen, onClose, onMessage, onError methods
}

Где находится абстрактный класс

public abstract class AbstractEndpoint{

    // irrelevant onOpen, onOpen handling method

117        protected void sendMessage(Session session, Message message){
118            if(message == null){
119                LOGGER.error("null message");
120            } else if(!session.isOpen()){
121                LOGGER.error("session is not opened");
122            } else{
>>>123                session.getAsyncRemote().sendObject(message, (result) -> {
124                    if (result.isOK()) {
125                        LOGGER.info("success! yeah!");
126                    } else {
127                        LOGGER.error("error when sending message", result.getException());
128                    }
129                });
130            }
    } 
}

Илегалстатеексцептион

Пока ничего особенного. Я могу отлично общаться и отвечать на полученный запрос, а с помощью веб-сокета FTW я могу передавать информацию и получать обратную связь. Однако я время от времени получаю исключение:

java.lang.IllegalStateException: Cannot set WriteListener for non-async or non-upgrade request
        at org.apache.catalina.connector.OutputBuffer.setWriteListener(OutputBuffer.java:536)
        at org.apache.catalina.connector.CoyoteOutputStream.setWriteListener(CoyoteOutputStream.java:223)
        at org.glassfish.tyrus.servlet.TyrusServletWriter.write(TyrusServletWriter.java:140)
        at org.glassfish.tyrus.core.ProtocolHandler.write(ProtocolHandler.java:486)
        at org.glassfish.tyrus.core.ProtocolHandler.send(ProtocolHandler.java:274)
        at org.glassfish.tyrus.core.ProtocolHandler.send(ProtocolHandler.java:332)
        at org.glassfish.tyrus.core.TyrusWebSocket.sendText(TyrusWebSocket.java:317)
        at org.glassfish.tyrus.core.TyrusRemoteEndpoint.sendSyncObject(TyrusRemoteEndpoint.java:429)
        at org.glassfish.tyrus.core.TyrusRemoteEndpoint$Async.sendAsync(TyrusRemoteEndpoint.java:352)
        at org.glassfish.tyrus.core.TyrusRemoteEndpoint$Async.sendObject(TyrusRemoteEndpoint.java:249)
        at com.mycompany.websocket.AbstEndpoint.sendMessage(AbstEndpoint.java:123)

Вторая попытка метода sendMessage

Сначала я подумал, что моя асинхронная конечная точка была неправильно настроена, поэтому я попробовал способ Future‹> вместо обратного вызова:

RemoteEndpoint.Async async = session.getAsyncRemote();
async.setSendTimeout(5000); // 5 seconds
Future<Void> future = async.sendObject(message);
try{
    future.get();
}
catch(InterruptedException | ExecutionException ex){
    LOGGER.error("error when sending message", ex);
}

Я также получил исключение.

Пока и симптомы

Удивительно, но я нашел только одну ссылку, посвященную этой проблеме.

  1. Ссылка на github подчеркивает проблему с размером буфера. Я не использую частичные сообщения, только целые сообщения. Более того, независимо от того, использую ли я размер буфера по умолчанию или устанавливаю новый, возникает исключение
  2. Я не смог найти глобальное правило о том, как воспроизвести ошибку
  3. После возникновения исключения клиент мог продолжать отправлять сообщения, а сервер обрабатывал их, но сервер никогда не отвечал клиенту. Похоже, исходящий канал связи заблокирован
  4. Поскольку сервер продолжает обрабатывать входящие сообщения, канал веб-сокета не закрывается после исключения

Копаем в реализации Tyrus

Я просмотрел реализацию tyrus-core и обнаружил, что метод отправки зависит от какого-то компонента Grizzly. Я ничего не знаю о Grizzly, но похоже, что отправка должна быть синхронной в любом случае из-за некоторых ограничений Grizzly.

Вопросы

  1. Кто-то уже встречал такую ​​ситуацию? Если да, действительно ли исключение означает, что где-то есть узкое место, или оно означает что-то еще?
  2. Является ли асинхронная конечная точка Tyrus действительно асинхронной, то есть как «обработать и забыть»?
  3. Я не нашел способа поставить в очередь исходящие сообщения: если сообщение A длинное, дождитесь завершения отправки сообщения A перед отправкой сообщения B. Есть ли способ обрабатывать большие сообщения в веб-сокете или асинхронная конечная точка является единственной способ?
  4. Я хочу убедиться, что при отправке не возникло никаких проблем, поэтому я выбрал асинхронное решение. Должен ли я вернуться к синхронному способу?

Я не стал подробно рассказывать о своем расследовании Тайруса. Если вы считаете это актуальным, не стесняйтесь спрашивать, и я с удовольствием разработаю.


person Al-un    schedule 03.08.2017    source источник
comment
Запрос не может быть асинхронным, если в конвейере есть фильтр сервлета, для которого не установлено значение asyncSupported=true. Сначала вам нужно исключить это, проверив web.xml, @WebFilter и ServletContext#addFilter().   -  person BalusC    schedule 03.08.2017
comment
Все ли фильтры обрабатываются моим собственным кодом? Я имею в виду, что если я использую сторонний компонент (например, Apache Shiro или так называемый Grizzly, которого я не знаю), возможно ли, что есть какие-то фильтры, которыми я не могу управлять?   -  person Al-un    schedule 03.08.2017
comment
Любой сторонний фильтр можно переопределить при переопределении в web.xml точно такого же имени фильтра.   -  person BalusC    schedule 03.08.2017
comment
Некоторые отзывы, через неделю я больше не встречал IllegalStateException. Так что это должно быть решение! Поскольку я не уверен во всех тонкостях, я подожду месяц, а затем опубликую ответ на свой вопрос, если он может помочь другим. В любом случае, большое спасибо @BalusC!   -  person Al-un    schedule 11.08.2017
comment
Я перепостил комментарий как ответ.   -  person BalusC    schedule 13.08.2017


Ответы (1)


java.lang.IllegalStateException: невозможно установить WriteListener для неасинхронного запроса или запроса без обновления

Чтобы запрос был полностью асинхронным, любой Filter в цепочке запрос-ответ должен быть явно установлен для поддержки асинхронных запросов. В частности, те «универсальные» фильтры, которые отображаются на /*.

Если фильтр зарегистрирован через запись <filter> в web.xml, это можно сделать, установив дочерний элемент <async-supported> на true.

<filter>
    ...
    <async-supported>true</async-supported>
</filter>

Если фильтр зарегистрирован через @WebFilter аннотации, это можно сделать, установив для атрибута asyncSupported значение true.

@WebFilter(..., asyncSupported="true")

Если фильтр зарегистрирован через ServletContext#addFilter(), это можно сделать, установив Registration.Dynamic#setAsyncSupported(). на true.

Dynamic filter = servletContext.addFilter(name, type);
filter.setAsyncSupported(true);

Причина в том, что реализация WebSocket внутри использует ServletRequest#startAsync()< /a> во время запроса квитирования, чтобы конвейер запроса-ответа оставался открытым «навсегда» до тех пор, пока ответ не будет явно закрыт. Его javadoc говорит следующее:

Выдает
IllegalStateException — если этот запрос находится в пределах области действия фильтра или сервлета, не поддерживающего асинхронные операции (то есть isAsyncSupported() возвращает false), или если этот метод вызывается снова без каких-либо асинхронных операций. диспетчеризация (результат одного из методов AsyncContext.dispatch()), вызывается вне области действия любой такой диспетчеризации, или вызывается повторно в рамках той же диспетчеризации, или если ответ уже был закрыт

isAsyncSupported() по умолчанию равно false, чтобы не нарушать работу существующих веб-приложений, использующих плохо реализованные фильтры сервлетов. Технически достаточно пометить только цель Servlet как поддерживаемую асинхронную и оставить фильтры в покое. Разумный «всеобъемлющий» Filter не стал бы явно ничего писать в HTTP-ответ, но API сервлетов никогда этого не запрещал, и поэтому такие фильтры, к сожалению, могут существовать.

Если у вас есть один такой фильтр, вы должны либо исправить его, чтобы больше ничего не писать в ответ, чтобы вы могли безопасно пометить его для поддержки асинхронных запросов, либо настроить его шаблон URL, чтобы он не охватывал запросы WebSocket. т.е. больше не сопоставляйте его с /*.

person BalusC    schedule 13.08.2017
comment
BalusC, не могли бы вы помочь мне с этим вопросом? stackoverflow.com/questions/45626939 / - person Randyka Yudhistira; 15.08.2017