Интеграция Spring - исключение всякий раз, когда я создаю сеанс

Моя проблема связана с решением, описанным в моей ранее описанной проблеме: Интеграция Spring, соединяющая входящий HTTP-шлюз с исходящим шлюзом Websocket. Подводя итог, я пытаюсь сделать следующее: я хочу передать запрос HTTP REST, поступающий на мой сервер, другому клиенту веб-сокета, и при получении ответа от клиента веб-сокета я передаю его HTTP REST отклик.

Решение, описанное в предыдущей ссылке, работает без проблем. Я попытался немного изменить его, добавив активатор службы lightOnStoringActivator, который создает сеанс всякий раз, когда я получаю сообщение на входящем шлюзе веб-сокета (см. Ниже мой новый файл конфигурации). После этой модификации у меня есть исключение, говорящее о том, что полученное сообщение от клиента вебоскета не может быть передано в ответный канал. Я уверен, что проблема связана со строкой, создающей сеанс, потому что, если я удалю только строку, создающую сеанс, проблема исчезнет.

Любая идея, почему это происходит, и как это исправить?

org.springframework.messaging.MessageHandlingException: ; nested exception is org.springframework.messaging.MessageHandlingException: ; nested exceptionan actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still equestContextListener or RequestContextFilter to expose the current request.
    at org.springframework.integration.websocket.inbound.WebSocketInboundChannelAdapter$1.handleMessage(WebSocketInboundChannelAdapter.java:122)
    at org.springframework.integration.channel.FixedSubscriberChannel.send(FixedSubscriberChannel.java:70)
    at org.springframework.integration.channel.FixedSubscriberChannel.send(FixedSubscriberChannel.java:64)
    at org.springframework.integration.websocket.support.PassThruSubProtocolHandler.handleMessageFromClient(PassThruSubProtocolHandler.java:73)
    at org.springframework.integration.websocket.inbound.WebSocketInboundChannelAdapter.onMessage(WebSocketInboundChannelAdapter.java:232)
    at org.springframework.integration.websocket.IntegrationWebSocketContainer$IntegrationWebSocketHandler.handleMessage(IntegrationWebSocketContain
    at org.springframework.web.socket.handler.WebSocketHandlerDecorator.handleMessage(WebSocketHandlerDecorator.java:75)
    at org.springframework.web.socket.handler.LoggingWebSocketHandlerDecorator.handleMessage(LoggingWebSocketHandlerDecorator.java:56)
    at org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator.handleMessage(ExceptionWebSocketHandlerDecorator.java:72)
    at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.handleBinaryMessage(StandardWebSocketHandlerAdapter.java:122)
    at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.access$100(StandardWebSocketHandlerAdapter.java:42)
    at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$4.onMessage(StandardWebSocketHandlerAdapter.java:88)
    at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$4.onMessage(StandardWebSocketHandlerAdapter.java:85)
    at org.apache.tomcat.websocket.WsFrameBase.sendMessageBinary(WsFrameBase.java:549)
    at org.apache.tomcat.websocket.WsFrameBase.processDataBinary(WsFrameBase.java:514)
    at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:274)
    at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:116)
    at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:54)
    at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler$WsReadListener.onDataAvailable(WsHttpUpgradeHandler.java:192)
    at org.apache.coyote.http11.upgrade.AbstractServletInputStream.onDataAvailable(AbstractServletInputStream.java:178)
    at org.apache.coyote.http11.upgrade.AbstractProcessor.upgradeDispatch(AbstractProcessor.java:92)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:601)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.messaging.MessageHandlingException: ; nested exception is java.lang.IllegalStateException: No thread-bound request found:nally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of Disp request.
    at org.springframework.integration.handler.MethodInvokingMessageProcessor.processMessage(MethodInvokingMessageProcessor.java:78)
    at org.springframework.integration.handler.ServiceActivatingHandler.handleRequestMessage(ServiceActivatingHandler.java:71)
    at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:
    at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:78)
    at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:116)
    at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:101)
    at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:97)
    at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:77)
    at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:277)
    at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:239)
    at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:115)
    at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:45)
    at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:95)
    at org.springframework.integration.router.AbstractMessageRouter.handleMessageInternal(AbstractMessageRouter.java:164)
    at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:78)
    at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:116)
    at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:101)
    at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:97)
    at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:77)
    at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:277)
    at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:239)
    at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:115)
    at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:45)
    at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:95)
    at org.springframework.integration.handler.AbstractMessageProducingHandler.sendOutput(AbstractMessageProducingHandler.java:248)
    at org.springframework.integration.handler.AbstractMessageProducingHandler.produceOutput(AbstractMessageProducingHandler.java:171)
    at org.springframework.integration.handler.AbstractMessageProducingHandler.sendOutputs(AbstractMessageProducingHandler.java:119)
    at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:
    at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:78)
    at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:116)
    at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:101)
    at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:97)
    at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:77)
    at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:277)
    at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:239)
    at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:115)
    at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:45)
    at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:95)
    at org.springframework.integration.endpoint.MessageProducerSupport.sendMessage(MessageProducerSupport.java:101)
    at org.springframework.integration.websocket.inbound.WebSocketInboundChannelAdapter.handleMessageAndSend(WebSocketInboundChannelAdapter.java:279
    at org.springframework.integration.websocket.inbound.WebSocketInboundChannelAdapter.access$000(WebSocketInboundChannelAdapter.java:63)
    at org.springframework.integration.websocket.inbound.WebSocketInboundChannelAdapter$1.handleMessage(WebSocketInboundChannelAdapter.java:119)
    ... 25 more
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or  still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or R
    at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
    at com.transacteleurope.service.activator.FingerVeinWebsocketActivator.createHttpSession(FingerVeinWebsocketActivator.java:64)
    at com.transacteleurope.service.activator.FingerVeinWebsocketActivator.onEnrollmentCaptureForEnrollResponse(FingerVeinWebsocketActivator.java:15
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at org.springframework.expression.spel.support.ReflectiveMethodExecutor.execute(ReflectiveMethodExecutor.java:72)
    at org.springframework.expression.spel.ast.MethodReference.getValueInternal(MethodReference.java:129)
    at org.springframework.expression.spel.ast.MethodReference.access$000(MethodReference.java:49)
    at org.springframework.expression.spel.ast.MethodReference$MethodValueRef.getValue(MethodReference.java:347)
    at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:87)
    at org.springframework.expression.spel.ast.SpelNodeImpl.getTypedValue(SpelNodeImpl.java:126)
    at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:327)
    at org.springframework.integration.util.AbstractExpressionEvaluator.evaluateExpression(AbstractExpressionEvaluator.java:164)
    at org.springframework.integration.util.MessagingMethodInvokerHelper.processInternal(MessagingMethodInvokerHelper.java:276)
    at org.springframework.integration.util.MessagingMethodInvokerHelper.process(MessagingMethodInvokerHelper.java:142)
    at org.springframework.integration.handler.MethodInvokingMessageProcessor.processMessage(MethodInvokingMessageProcessor.java:75)
    ... 66 more

Мой файл конфигурации выглядит следующим образом:

<!-- REST service to turn on the light -->
<int-http:inbound-gateway
        supported-methods="POST"
        request-channel="lightOnRequest"
        reply-channel="lightOnResponse"
        path="rest/lighton/{sessionId}">
    <int-http:header name="{sessionId}" expression="{sessionId}"/>
</int-http:inbound-gateway>

<!-- We add a header SESSION_ID_HEADER to choose the websocket destination client -->
<int:header-enricher 
    input-channel="lightOnRequest" 
    output-channel="lightOnClientRequest">
    <int:header 
        name="#{T(...SimpMessageHeaderAccessor).SESSION_ID_HEADER}"
        expression="headers.sessionId"/>
    <int:header-channels-to-string/>
</int:header-enricher>

<!-- Websocket out to client -->
<int-websocket:outbound-channel-adapter 
    channel="lightOnClientRequest" 
    container="serverWebSocketContainer" />

<!-- Response reception from the Websocket client -->
<int-websocket:inbound-channel-adapter 
    channel="lightOnClientResponse" 
    container="serverWebSocketContainer" />

<!-- We store some data in the session -->
<int:service-activator 
    request-channel="lightOnClientResponse"
    reply-channel="lightOnClientStoredResponse"
    ref="lightOnStoringActivator"
    method="onNewRestfullRequest"
    requires-reply="true" />

<!-- The websocket client provides again the reply channel in the headers.
     The bridge connects the response to the reply channel -->
<int:bridge input-channel="lightOnClientStoredResponse"/>

person Omar BELKHODJA    schedule 23.03.2015    source источник
comment
Пожалуйста, опубликуйте полную трассировку стека; возможно, в Gist или подобном, если он слишком большой, чтобы публиковать здесь.   -  person Gary Russell    schedule 23.03.2015
comment
Я полностью обновил свой первоначальный пост, потому что файл конфигурации XML на самом деле не то, что я сказал. На самом деле, я сделал некоторую чистку, чтобы сделать пост как можно меньше. На самом деле происходит то, что я пытаюсь создать сеанс после получения ответа от клиента веб-сокета. Но как я могу сказать, что хочу создать сеанс в запросе REST, используя данные, поступающие от клиента веб-сокета?   -  person Omar BELKHODJA    schedule 23.03.2015


Ответы (1)


Вам нужно задержать входящий поток и передать ему данные ответа.

В настоящее время ваш входящий поток REST завершается, как только вы отправляете сообщение исходящему адаптеру веб-сокета.

Когда ответ приходит на входящий адаптер веб-сокета, вы пытаетесь отправить его обратно на шлюз, но этот контекст уже ушел; кроме того, вы не можете получить доступ к переменным сеанса во «внешнем» потоке.

Одним из решений было бы сделать lightOnClientRequest <publish-subscribe-channel/> и подписаться на него <service-activator/> - убедитесь, что это второй подписчик (для уверенности используйте атрибут order).

В этой службе приостановите поток REST. Затем вместо моста вызовите другой метод службы, который передает данные и освобождает поток. Этот метод должен возвращать void, поэтому поток WS заканчивается в этой точке.

Вы можете использовать Map из LinkedBlockingQueue, используя заголовок (строку) replyChannel в качестве ключа. Получите поток REST шлюза take() из очереди и входящий поток ws put() в очередь. Когда take() вернется, удалите запись карты.

Имейте в виду, что ответ может быть получен до того, как будет вызван второй потребитель, поэтому вам нужно иметь дело с отсутствующими записями карты на этой стороне. Или используйте 3 подписчиков на канале

  1. создать запись карты
  2. написать в ВС
  3. взять из очереди и удалить запись карты

Вероятно, вы захотите использовать poll с тайм-аутом, а не take() на случай, если вы не получите ответа.

EDIT: (в ответ на ваш комментарий ниже).

Это скорее удача, чем дизайн - после отправки поток http (REST) ​​сидит в шлюзе в ожидании ответа. Это будет работать до тех пор, пока вы не попытаетесь что-либо сделать с сеансом HTTP в отвечающем потоке.

Только поток http может получить доступ к переменным области сеанса. Я не могу точно увидеть, что вы делаете, потому что ваша конфигурация кажется неполной — например, в трассировке стека есть маршрутизатор, а в вашей конфигурации нет маршрутизатора.

Похоже, что что-то ниже маршрутизатора пытается получить доступ к атрибутам сеанса: currentRequestAttributes. Если вам нужно это сделать, вам нужно извлечь переменные из сеанса в основном потоке и сохранить их в заголовке. Вы просто не можете получить доступ к контексту HTTP-запроса из потока ответа WebSocket.

person Gary Russell    schedule 23.03.2015
comment
Привет Гэри. Спасибо за Ваш ответ. Я согласен с вами, что ваше решение, вероятно, сработает, хотя мне оно кажется немного сложным. Чего я не понимаю, так это того, что если я удалю код создания сеанса, исключение не появится, что означает, что поток веб-сокета смог сопоставить поток REST, просто используя ReplyChannel. Поэтому мне все еще интересно, есть ли более простое решение. - person Omar BELKHODJA; 23.03.2015