问题
Summary
I'm using Spring Integration's TCP & UDP Support to proxy TCP stream traffic through my application to an upstream server, and then proxy that server's response back through my application to the client. Although this is two-way communication, I need high-volume, asynchronous throughput, so I can't use Gateways. Instead, I am trying to use Collaborating Outbound and Inbound Channel Adapters as described in section 34.8.2.
Integration Component Setup
Request
A TcpReceivingChannelAdapter
receives requests via a TcpNetServerConnectionFactory
on port 6060. It places these requests on a requests QueueChannel
. Requests are picked up by a TcpSendingMessageHandler
, which send the request over a client connection generated by a TcpNetClientConnectionFactory
. This connection sends the request out of my application and to the upstream server.
Response
A TcpReceivingChannelAdapter
receives responses from the upstream server via the TcpNetClientConnectionFactory
connection.It places these responses on a responses QueueChannel
. Responses are picked up by a TcpSendingMessageHandler
, which attempts to send the response back to the client via the connection from the original TcpNetServerConnectionFactory
. This final connection is what fails.
@Bean
public PollableChannel requestChannel() {
return new QueueChannel(1000);
}
@Bean
public PollableChannel replyChannel() {
return new QueueChannel(1000);
}
@Bean
public TcpNetServerConnectionFactory serverFactory() {
TcpNetServerConnectionFactory serverFactory = new TcpNetServerConnectionFactory(6060);
serverFactory.setSerializer(new ByteArrayLengthHeaderSerializer(2));
serverFactory.setDeserializer(new ByteArrayLengthHeaderSerializer(2));
serverFactory.setSingleUse(false);
return serverFactory;
}
@Bean
public TcpNetClientConnectionFactory clientFactory() {
TcpNetClientConnectionFactory clientFactory = new TcpNetClientConnectionFactory("127.0.0.1", 6080);
clientFactory.setSerializer(new ByteArrayLengthHeaderSerializer(2));
clientFactory.setDeserializer(new ByteArrayLengthHeaderSerializer(2));
clientFactory.setSingleUse(false);
return clientFactory;
}
@Bean
public TcpReceivingChannelAdapter inboundRequestAdapter() {
TcpReceivingChannelAdapter inboundRequestAdapter = new TcpReceivingChannelAdapter();
inboundRequestAdapter.setConnectionFactory(serverFactory());
inboundRequestAdapter.setOutputChannel(requestChannel());
return inboundRequestAdapter;
}
@Bean
@ServiceActivator(inputChannel = "requestChannel", poller = @Poller(fixedDelay = "50", receiveTimeout = "5000"))
public TcpSendingMessageHandler outboundRequestAdapter() {
TcpSendingMessageHandler outboundRequestAdapter = new TcpSendingMessageHandler();
outboundRequestAdapter.setConnectionFactory(clientFactory());
return outboundRequestAdapter;
}
@Bean
public TcpReceivingChannelAdapter inboundReplyAdapter() {
TcpReceivingChannelAdapter inboundReplyAdapter = new TcpReceivingChannelAdapter();
inboundReplyAdapter.setConnectionFactory(clientFactory());
inboundReplyAdapter.setOutputChannel(replyChannel());
return inboundReplyAdapter;
}
@Bean
@ServiceActivator(inputChannel = "replyChannel", poller = @Poller(fixedDelay = "50", receiveTimeout = "5000"))
public TcpSendingMessageHandler outboundReplyAdapter() {
TcpSendingMessageHandler outboundReplyAdapter = new TcpSendingMessageHandler();
outboundReplyAdapter.setConnectionFactory(serverFactory());
return outboundReplyAdapter;
}
Actual Result
Error:
Unable to find outbound socket for GenericMessage
Full stack trace:
2019-02-01 14:10:55.315 ERROR 32553 --- [ask-scheduler-2] o.s.i.ip.tcp.TcpSendingMessageHandler : Unable to find outbound socket for GenericMessage [payload=byte[297], headers={ip_tcp_remotePort=6080, ip_connectionId=localhost:6080:51339:a3f66802-b194-4564-99c7-f194e55ddb11, ip_localInetAddress=/127.0.0.1, ip_address=127.0.0.1, id=bc36ec21-e2ae-405e-afa9-c0ec2f2eff8d, ip_hostname=localhost, timestamp=1549051855315}]
2019-02-01 14:10:55.319 ERROR 32553 --- [ask-scheduler-2] o.s.integration.handler.LoggingHandler : org.springframework.messaging.MessageHandlingException: Unable to find outbound socket, failedMessage=GenericMessage [payload=byte[297], headers={ip_tcp_remotePort=6080, ip_connectionId=localhost:6080:51339:a3f66802-b194-4564-99c7-f194e55ddb11, ip_localInetAddress=/127.0.0.1, ip_address=127.0.0.1, id=bc36ec21-e2ae-405e-afa9-c0ec2f2eff8d, ip_hostname=localhost, timestamp=1549051855315}]
at org.springframework.integration.ip.tcp.TcpSendingMessageHandler.handleMessageInternal(TcpSendingMessageHandler.java:123)
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:162)
at org.springframework.integration.handler.ReplyProducingMessageHandlerWrapper.handleRequestMessage(ReplyProducingMessageHandlerWrapper.java:49)
at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:123)
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:162)
at org.springframework.integration.endpoint.PollingConsumer.handleMessage(PollingConsumer.java:143)
at org.springframework.integration.endpoint.AbstractPollingEndpoint.doPoll(AbstractPollingEndpoint.java:390)
at org.springframework.integration.endpoint.AbstractPollingEndpoint.pollForMessage(AbstractPollingEndpoint.java:329)
at org.springframework.integration.endpoint.AbstractPollingEndpoint.lambda$null$1(AbstractPollingEndpoint.java:277)
at org.springframework.integration.util.ErrorHandlingTaskExecutor.lambda$execute$0(ErrorHandlingTaskExecutor.java:57)
at org.springframework.core.task.SyncTaskExecutor.execute(SyncTaskExecutor.java:50)
at org.springframework.integration.util.ErrorHandlingTaskExecutor.execute(ErrorHandlingTaskExecutor.java:55)
at org.springframework.integration.endpoint.AbstractPollingEndpoint.lambda$createPoller$2(AbstractPollingEndpoint.java:274)
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:93)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
This makes sense. I'm aware that a TcpReceivingChannelAdapter
sets the ip_connectionId
Message header field when it forwards a message. Since I don't have any correlation logic right now, the ID header from the first inbound Adapter is lost when the payload is proxied upstream, and the second inbound Adapter generates a new ID header.
As a result, when the reply gets back to the final outbound Adapter, the ID header doesn't match anything that the corresponding inbound Adapter knows about. So, it doesn't know which connection to use to send the response.
My question is this: is there any way to set a "default" connection, or augment the payload with correlating data without sending that upstream?
The issue is that my application must be a transparent proxy in regards to the upstream server. If I augment the payload at all with correlating data, the upstream server will reject it.
回答1:
It's difficult to correlate request/replies without the data containing correlation information.
The TcpOutboundGateway
can do it because the socket itself is used for the correlation; only one request can be outstanding on each socket at a time. The CachingClientConnectionFactory
allows concurrency in the gateway by maintaining a pool of sockets.
One technique might be a custom client connection factory that maintains a one-to-one map between your server factory connections and outgoing connections. Then, when a reply is received, look up the corresponding server factory connection to which to send the reply. It would just need a couple of maps - server connection id to client connection, and client connection id to server connection id.
If you come up with a solution, consider contributing it back to the framework.
来源:https://stackoverflow.com/questions/54486660/how-to-send-asynchronous-requests-responses-through-collaborating-channel-adapte