Reputation: 1902
My problem is related to the solution described in my previously described issue : Spring integration connecting inbound HTTP gateway with outbound Websocket gateaway. To sum-up quickly what I'm trying to do, is that I want to transfer a HTTP REST request coming to my server, to another websocket client, and when receiving the answer from the websocket client, I transfer it to the HTTP REST response.
The solution described in the previous link works without any problem. I tried to modify it a little bit, by adding a service activator lightOnStoringActivator
that creates a session, whenever I receive a message on the inbound websocket gateway (see below my new configuration file). After this modification, I have an exception, saying that the received message from the weboscket client can't be transfered to the reply channel. I'm sure the problem is coming from the line that is creating the session, because if I remove only the line creating the session, the problem disappears.
Any idea why this happens, and how to fix it ?
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
My configuration file is the following:
<!-- 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"/>
Upvotes: 2
Views: 1191
Reputation: 174554
You need to hold up the inbound thread and transfer the reply data to it.
Currently, your inbound REST flow ends as soon as you send the message to the outbound websocket adapter.
When the reply comes in on the websocket inbound adapter, you're trying to send it back to the gateway but that context is already gone; further, you can't access session variables on a "foreign" thread.
One solution would be to make the lightOnClientRequest
a <publish-subscribe-channel/>
and subscribe a <service-activator/>
to it - make sure it's the second subscriber (use the order
attribute to be sure).
Within that service, suspend the REST thread. Then, instead of the bridge, invoke another method on the service that transfers the data and releases the thread. That method should return void, so the WS flow ends at that point.
You could use a Map
of LinkedBlockingQueue
using the replyChannel
header (string) as the key. Have the gateway REST thread take()
from the queue and have the ws inbound thread put()
to the queue. When the take()
returns, remove the map entry.
Beware that the reply may be received before the second consumer is invoked so you need to deal with missing map entries on that side. Or, use 3 subscribers on the channel
You probably want to use poll
with a timeout rather than take()
in case you don't get a reply.
EDIT: (in response to your comment below).
That is more by luck than design - after the send, the http (REST) thread is sitting in the gateway awaiting the reply. It will work as long as you don't attempt to do anything with the HTTP session on the replying thread.
Only the http thread can access session-scoped variables. I can't see exactly what you are doing because your configuration appears incomplete - for example, there is a router in the stack trace and your configuration shows no router.
It appears that something downstream of the router is trying to access the session attributes: currentRequestAttributes
. If you need to do that, you need to extract the variables from the session on the main thread and store then in a header. You simply cannot access the HTTP request context from the WebSocket reply thread.
Upvotes: 1