How to create custom error response in Spring Integration using java DSL - spring-boot

I have following simple proxy integration flow. The main task of which is to take request from the proxy send it to the actual endpoint, get the respond and send it back to the client.
#SpringBootApplication
#EnableIntegration
public class IntegrationApp {
#Value("${narko.pin}")
private String pinUrl;
public static void main(String[] args) {
SpringApplication.run(MinzdravApplication.class, args);
}
#Bean
public DirectChannel requestPinChannel() {
return new DirectChannel();
}
#Bean
public DirectChannel replyPinChannel() {
return new DirectChannel();
}
#Bean
public IntegrationFlow httpProxyFlowPin() throws Exception {
return IntegrationFlows
.from(Http.inboundGateway("/narko/api/patient/by-pinpp")
.requestChannel(requestPinChannel()).replyChannel(replyPinChannel())
.mappedRequestHeaders("activityid")
.errorChannel("httpProxyErrorFlow.input")
)
.wireTap(sf->sf.handle(new InwardMessageHandler()))
.enrichHeaders(h -> h.header("Content-Type", "application/json"))
.handle(Http.outboundGateway(pinUrl).charset("utf-8")
.expectedResponseType(String.class))
.channel(replyPinChannel())
.get();
}
#Bean
public IntegrationFlow httpProxyErrorFlow() {
return f -> f
.transform(Throwable::getCause)
.<HttpClientErrorException>handle((p, h) ->
new RuntimeException("custom exception"));
}
}
When the api at the outbound gateway is down. I have following error:
{
"timestamp": "2022-08-10T12:51:58.561+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/narko/api/patient/by-pinpp"
}
And I have following exceptions on logs:
java.lang.ClassCastException: class org.springframework.web.client.ResourceAccessException cannot be cast to class org.springframework.web.client.HttpClientErrorException (org.springframework.web.client.ResourceAccessException and org.springframework.web.client.HttpClientErrorException are in unnamed module of loader 'app')
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
at org.springframework.integration.handler.LambdaMessageProcessor.processMessage(LambdaMessageProcessor.java:104) ~[spring-integration-core-5.5.10.jar:5.5.10]
at org.springframework.integration.handler.ServiceActivatingHandler.handleRequestMessage(ServiceActivatingHandler.java:105) ~[spring-integration-core-5.5.10.jar:5.5.10]
at org.springframework.integration.handler.AbstractReplyProducingMessageHandler.handleMessageInternal(AbstractReplyProducingMessageHandler.java:136) ~[spring-integration-core-5.5.10.jar:5.5.10]
at org.springframework.integration.handler.AbstractMessageHandler.handleMessage(AbstractMessageHandler.java:56) ~[spring-integration-core-5.5.10.jar:5.5.10]
at org.springframework.integration.dispatcher.AbstractDispatcher.tryOptimizedDispatch(AbstractDispatcher.java:115) ~[spring-integration-core-5.5.10.jar:5.5.10]
at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:133) ~[spring-integration-core-5.5.10.jar:5.5.10]
at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:106) ~[spring-integration-core-5.5.10.jar:5.5.10]
How can I create custom exception response?
Any navigation or hint is appreciated.

First of all you don't need that .replyChannel(replyPinChannel() and .channel(replyPinChannel()). An inbound gateway sends a message with a replyChannel header, the last replying endpoint in the flow, not founding its outputChannel, will consult with that replyChannel.
Secondly, your solution about an error handler is OK, but you see yourself in the stacktrace that you just don't cast to the proper type: the ResourceAccessException is not an instance of HttpClientErrorException. Consider to expect a RestClientException instead which is a super for both ResourceAccessException and HttpClientErrorException.

Related

Trying to call camel/javaee from springboot

I'm trying to call camel/java-ee/class (it's very long programs, so no time to convert) from a spring-boot. So for now the solution is to just start with spring-boot and call the main class.
Here's the main application of springboot that will call the javaee/class, I tried the simple bean calling before, so this my latest test for I thought its about the proper call of bean, but unfortunately, even the proper calling of bean have the same error as the simple calling of bean,
#SpringBootApplication
public class App {
public static void main(String[] args) throws Exception {
ApplicationContext applicationContext = SpringApplication.run(App.class, args);
BeanAppService service = applicationContext.getBean(BeanAppService.class);
service.callInitCamelJavaEE();
}
}
Service interface,
public interface BeanAppService {
void callInitCamelJavaEE() throws Exception;
}
Configuration,
#Configuration
public class BeanAppConfig {
#Bean
public BeanAppService beanAppConfig() {
return new BeanApp();
}
}
BeanApp (this is the original main class with args in java/ee),
public class BeanApp implements BeanAppService {
public BeanApp() {}
protected <I extends SpongeRequest, O extends SpongeResponse> void createOperation(RestDefinition restDefinition, String operation,
String operationDescription, Class<I> requestClass, String requestDescription, Class<O> responseClass,
String responseDescription, BiFunction<I, Exchange, O> operationHandler) {
restDefinition.post(operation).description(operationDescription).type(requestClass).outType(responseClass).param().name("body")
.type(body).description(requestDescription).endParam().responseMessage().code(200).message(responseDescription)
.endResponseMessage().route().id("sponge-" + operation).process(exchange -> {
String requestBody = exchange.getIn().getBody(String.class);
if (logger.isDebugEnabled()) {
logger.debug("REST API {} request: {}", operation, RestApiUtils.obfuscatePassword(requestBody));
}
try {
setupResponse(operation, exchange,
operationHandler.apply(getObjectMapper().readValue(requestBody, requestClass), exchange));
} catch (Throwable processingException) {
logger.info("REST API error", processingException);
try {
setupResponse(operation, exchange, apiService.createGenericErrorResponse(processingException, exchange));
} catch (Throwable e) {
logger.error("REST API send error response failure", e);
throw e;
}
}
}).endRest();
}
#Override
public void callInitCamelJavaEE() throws Exception {
try (CamelContext camelContext = new DefaultCamelContext()) {
camelContext.addRoutes(new RouteBuilder() {
#Override
public void configure() throws Exception {
restConfiguration().enableCORS(true).setPort("8080");
}
});
camelContext.start();
Thread.sleep(6000000);
camelContext.stop();
}
}
The error,
StreamCaching is not in use. If using streams then it's recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
Error starting CamelContext (camel-2) due to exception thrown: Failed to start route route1 because of null
org.apache.camel.FailedToStartRouteException: Failed to start route route1 because of null
at org.apache.camel.impl.engine.RouteService.warmUp(RouteService.java:122) ~[camel-base-engine-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.InternalRouteStartupManager.doWarmUpRoutes(InternalRouteStartupManager.java:270) ~[camel-base-engine-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.InternalRouteStartupManager.safelyStartRouteServices(InternalRouteStartupManager.java:157) ~[camel-base-engine-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.InternalRouteStartupManager.doStartOrResumeRoutes(InternalRouteStartupManager.java:115) ~[camel-base-engine-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.AbstractCamelContext.doStartCamel(AbstractCamelContext.java:2889) ~[camel-base-engine-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.AbstractCamelContext.doStartContext(AbstractCamelContext.java:2702) ~[camel-base-engine-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.AbstractCamelContext.doStart(AbstractCamelContext.java:2665) ~[camel-base-engine-3.7.0.jar:3.7.0]
at org.apache.camel.support.service.BaseService.start(BaseService.java:115) ~[camel-api-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.AbstractCamelContext.start(AbstractCamelContext.java:2431) ~[camel-base-engine-3.7.0.jar:3.7.0]
at ca.toronto.eis.dwork2.BeanApp.BeanAppApp.callMain(BeanAppApp.java:531) ~[main/:?]
at ca.toronto.eis.dwork2.Dwork2Application.main(Dwork2Application.java:29) ~[main/:?]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_202]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_202]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_202]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_202]
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) ~[spring-boot-devtools-2.7.0.jar:2.7.0]
Caused by: java.lang.IllegalStateException: Cannot find RestConsumerFactory in Registry or as a Component to use
at org.apache.camel.component.rest.RestEndpoint.createConsumer(RestEndpoint.java:602) ~[camel-rest-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.DefaultRoute.addServices(DefaultRoute.java:575) ~[camel-base-engine-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.DefaultRoute.onStartingServices(DefaultRoute.java:160) ~[camel-base-engine-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.RouteService.doWarmUp(RouteService.java:150) ~[camel-base-engine-3.7.0.jar:3.7.0]
at org.apache.camel.impl.engine.RouteService.warmUp(RouteService.java:120) ~[camel-base-engine-3.7.0.jar:3.7.0]
... 15 more
Apache Camel 3.7.0 (camel-2) is shutting down
Apache Camel 3.7.0 (camel-2) uptime 28ms
Apache Camel 3.7.0 (camel-2) is shutdown in 10ms
Anyone who have encountered this before?
Seems like in the route builder you have not configured any route.
#Override
public void callInitCamelJavaEE() throws Exception {
try (CamelContext camelContext = new DefaultCamelContext()) {
camelContext.addRoutes(new RouteBuilder() {
#Override
public void configure() throws Exception {
restConfiguration().enableCORS(true).setPort("8080");
}
});
camelContext.start();
Thread.sleep(6000000);
camelContext.stop();
}
}
Assuming you are using Rest here is How to configure a route in RouteBuilder using REST DSL

Hystrix Feign Retry for Timeout not working

I have a Feign Configuration and Hystrix Commands in my project.
below is Feign Config
#Configuration
public class FeignRetryConfig {
#Primary
#Bean
public Feign.Builder feignBuilder(Retryer nephosFeignRetryer) {
return HystrixFeign.builder()
.errorDecoder(new FeignErrorDecoder())
.retryer(nephosFeignRetryer);
}
// retry set to 3 times
#Bean
public Retryer nephosFeignRetryer() {
return new Retryer.Default(10, SECONDS.toMillis(5), 5);
}
#Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
and below is my ErrorDecoder:
public class FeignErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
#Override
public Exception decode(String methodKey, Response response) {
Exception exception = defaultErrorDecoder.decode(methodKey, response);
if (response.status() == 500) {
log.error(String.format("##### Got %s response from %s #######", response.status(),
methodKey));
return new RetryableException(
exception.getMessage(),
exception,
null
);
}
return exception;
}
}
and below is my client:
#FeignClient(name = "TEST-CONFIG", configuration = FeignRetryConfig.class, fallbackFactory =
XYZClientFallbackFactory.class)
public interface TestClient {
#RequestMapping(value = "/test", method = RequestMethod.GET, consumes =
MediaType.APPLICATION_JSON_VALUE)
Observable<String> test();
}
SO from TEST-CONFIG I am throwing IOException ( 500 Error ) to Test, but it does not make any retry. below is my error:
com.netflix.hystrix.exception.HystrixRuntimeException: TestClient#test() failed and fallback failed.
at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:815)
at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:790)
at rx.internal.operators.OperatorOnErrorResumeNextViaFunction$4.onError(OperatorOnErrorResumeNextViaFunction.java:140)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at com.netflix.hystrix.AbstractCommand$DeprecatedOnFallbackHookApplication$1.onError(AbstractCommand.java:1451)
at com.netflix.hystrix.AbstractCommand$FallbackHookApplication$1.onError(AbstractCommand.java:1376)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
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)
Caused by: feign.RetryableException: status 500 reading TestClient#test(); content:
{"status":500,"erroritems":[{"code":"RuntimeException","message":"org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection"}]}
at feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java:108)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:301)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:297)
at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46)
... 30 common frames omitted
Caused by: feign.FeignException: status 500 reading TestClient#test(); content:
{"status":500,"erroritems":[{"code":"RuntimeException","message":"org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection"}]}
at feign.FeignException.errorStatus(FeignException.java:62)
at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:91)
Can Somebody Help Please, What am I Missing ?
I guess you have hystrix enabled. Try setting
feign.hystrix.enabled: false
and see if it works then; if so it would prove your configuration to be ok. There is a post on hystrix and retrying that suggests that this does not go well together. If you want to keep hystrix enabled (and why should you not), perhaps it is worth looking at spring-retry to circumvent the problem.

MessageDispatchingException: Dispatcher has no subscribers

Having a simple Spring Cloud Stream setup.
The interface
public interface MyKafkaBinding {
#Output(PUBLISHER)
MessageChannel publisher();
#Input("subscriber")
SubscribableChannel subscriber();
}
Binding
#EnableBinding(MyKafkaBinding.class)
Listener
#StreamListener(MyKafkaBinding.PUBLISHER)
public void listen(MyEvent message) {
// handle
}
App properties
spring.cloud.stream.bindings.publisher.destination=my-kafka-topic
spring.cloud.stream.bindings.publisher.producer.header-mode=headers
spring.cloud.stream.bindings.publisher.content-type=application/json
spring.cloud.stream.bindings.subscriber.destination=my-kafka-topic
spring.cloud.stream.bindings.subscriber.consumer.header-mode=headers
spring.cloud.stream.bindings.subscriber.content-type=application/json
It all works fine. Messages sent using the publisher are received.
Now I'm trying to send a message to this topic from another app, using KafkaTemplate:
kafkaTemplate.send(topic, message)
This time an error is thrown on the receiving side:
Caused by: org.springframework.messaging.MessageDeliveryException: Dispatcher has no subscribers for channel 'MyApp.subscriber'.; nested exception is org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers, failedMessage=GenericMessage [payload=com.bax.so.MyEvent#6da11fec, headers={b3=[B#304c5b9f, kafka_offset=10, scst_nativeHeadersPresent=true, kafka_consumer=org.apache.kafka.clients.consumer.KafkaConsumer#742c6888, deliveryAttempt=3, kafka_timestampType=CREATE_TIME, kafka_receivedMessageKey=null, kafka_receivedPartitionId=0, kafka_receivedTopic=my-kafka-topic, kafka_receivedTimestamp=1578085559878, kafka_groupId=my-default-group-id}]
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:77)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:453)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:401)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:187)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:166)
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:47)
at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:109)
at org.springframework.integration.endpoint.MessageProducerSupport.sendMessage(MessageProducerSupport.java:205)
at org.springframework.integration.kafka.inbound.KafkaMessageDrivenChannelAdapter.sendMessageIfAny(KafkaMessageDrivenChannelAdapter.java:369)
at org.springframework.integration.kafka.inbound.KafkaMessageDrivenChannelAdapter.access$400(KafkaMessageDrivenChannelAdapter.java:74)
at org.springframework.integration.kafka.inbound.KafkaMessageDrivenChannelAdapter$IntegrationRecordMessageListener.onMessage(KafkaMessageDrivenChannelAdapter.java:431)
at org.springframework.integration.kafka.inbound.KafkaMessageDrivenChannelAdapter$IntegrationRecordMessageListener.onMessage(KafkaMessageDrivenChannelAdapter.java:402)
at org.springframework.kafka.listener.adapter.RetryingMessageListenerAdapter.lambda$onMessage$0(RetryingMessageListenerAdapter.java:120)
at org.springframework.retry.support.RetryTemplate.doExecute(RetryTemplate.java:287)
at org.springframework.retry.support.RetryTemplate.execute(RetryTemplate.java:211)
at org.springframework.kafka.listener.adapter.RetryingMessageListenerAdapter.onMessage(RetryingMessageListenerAdapter.java:114)
at org.springframework.kafka.listener.adapter.RetryingMessageListenerAdapter.onMessage(RetryingMessageListenerAdapter.java:40)
at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeOnMessage(KafkaMessageListenerContainer.java:1592)
at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.invokeOnMessage(KafkaMessageListenerContainer.java:1575)
at org.springframework.kafka.listener.KafkaMessageListenerContainer$ListenerConsumer.doInvokeRecordListener(KafkaMessageListenerContainer.java:1534)
... 8 common frames omitted
Caused by: org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers
at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:138)
at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:105)
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:73)
... 27 common frames omitted
Spring version 5+.
Is this a valid scenario at all, sending messages using KafkaTemplate and expect them to be received by a cloud stream subscriber ?
Your #StreamListener is bound to the publisher channel instead of the subscriber channel.
Here is a working example:
#SpringBootApplication
#EnableBinding(MyKafkaBinding.class)
public class So59585815Application {
public static void main(String[] args) {
SpringApplication.run(So59585815Application.class, args);
}
#Autowired
private MessageChannel publisher;
#StreamListener("subscriber")
public void listen(String in) {
publisher.send(new GenericMessage<>(in.toUpperCase()));
}
#Bean
public ApplicationRunner runner(KafkaTemplate<byte[], byte[]> template) {
return args -> {
template.send("subscriber-topic", "foo".getBytes());
};
}
#KafkaListener(id = "listener", topics = "publisher-topic")
public void listen(byte[] in) {
System.out.println(new String(in));
}
}
interface MyKafkaBinding {
#Output("publisher")
MessageChannel publisher();
#Input("subscriber")
SubscribableChannel subscriber();
}
and
spring.cloud.stream.bindings.publisher.destination=publisher-topic
spring.cloud.stream.bindings.subscriber.destination=subscriber-topic
spring.cloud.stream.bindings.subscriber.group=myGroup
spring.kafka.consumer.auto-offset-reset=earliest
The reason for this problem in my project:
my project cannot connect to Kafka

WebClient ExchangeFilterFunction unable to add header java.lang.UnsupportedOperationException ReadOnlyHttpHeaders

I am trying to add a simple ExchangeFilterFunction to a WebClient request. However, I am seeing the following exception:
java.lang.UnsupportedOperationException
at org.springframework.http.ReadOnlyHttpHeaders.add(ReadOnlyHttpHeaders.java:84)
at com.ecs.springframework.reactive.web.filter.AddHeaderExchangeFilter.filter(AddHeaderExchangeFilter.java:24)
at org.springframework.web.reactive.function.client.ExchangeFilterFunction.lambda$apply$2(ExchangeFilterFunction.java:66)
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultRequestBodyUriSpec.exchange(DefaultWebClient.java:319)
at com.ecs.springframework.reactive.web.filter.AddHeaderExchangeFilterTest.whenExchangeFilterFunctionInjectedIntoWebClient_thenWebClientShouldPropagate(AddHeaderExchangeFilterTest.java:57)
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:498)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:532)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
at
ExchangeFilterFunction
public class AddHeaderExchangeFilter implements ExchangeFilterFunction {
#Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
ClientRequest newRequest = ClientRequest.create(request.method(), request.url()).build();
newRequest.headers().add("EFF-TEST-HEADER", "EFF-TEST-VALUE");
return next.exchange(newRequest);
}
}
WebClient webTestClient = WebClient.builder().filter(new AddHeaderExchangeFilter()).build();
webTestClient.get().uri("http://httpbin.org/get").exchange();
I am relatively new to WebFlux so any pointers would be appreciated.
You need to add the headers when you creating newRequest:
public class AddHeaderExchangeFilter implements ExchangeFilterFunction {
#Override
public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
ClientRequest newRequest = ClientRequest.from(request)
.header("EFF-TEST-HEADER", "EFF-TEST-VALUE")
.build();
return next.exchange(newRequest);
}
}
The reason for your current behavior is that ClientRequest.create initiating new DefaultClientRequestBuilder.
You can see in the Spring's source code that once you build() your request the builder building the request with read only headers, and therefore you can't add the headers after you built the ClientRequest.

How to avoid the MessageDeliveryException?

I'm trying to send a simple message through tcp but I can't even manage that using spring integration... I'm really getting bored with that ...
So I tried using TcpOutboundGateway and TcpInboudGateway in client mode but I get a MessageDeliveryException.
Here is my code:
#EnableIntegration
#IntegrationComponentScan
#Configuration
public class TcpClientConfiguration {
#Bean
public TcpNetClientConnectionFactory clientConnectionFactory() {
TcpNetClientConnectionFactory factory = new TcpNetClientConnectionFactory("localhost", 7015);
return factory;
}
#Bean
public DirectChannel outputChannel() {
return new DirectChannel();
}
#Bean
public MessageChannel replyChannel() {
return new DirectChannel();
}
// #Bean
// public TcpOutboundGateway tcpOutGateway(AbstractClientConnectionFactory clientConnectionFactory) {
// TcpOutboundGateway outGateway = new TcpOutboundGateway();
// outGateway.setConnectionFactory(clientConnectionFactory);
// outGateway.setOutputChannel(outputChannel());
// return outGateway;
// }
#Bean
public TcpInboundGateway tcpInboundGateway(AbstractClientConnectionFactory clientConnectionFactory) {
TcpInboundGateway inGateway = new TcpInboundGateway();
inGateway.setConnectionFactory(clientConnectionFactory);
inGateway.setClientMode(true);
inGateway.setRequestChannel(outputChannel());
inGateway.setReplyChannel(replyChannel());
return inGateway;
}
}
And the scheduled method to send the message :
#Component
public class SimulatorTask {
#Autowired
DirectChannel outputChannel;
#Scheduled( fixedDelay = 3000 )
public void sendMsg() {
outputChannel.send(new GenericMessage<>("Hello world!"));
}
}
The error I get :
2018-05-03 13:42:44.578 ERROR 11144 --- [ask-scheduler-7] o.s.integration.handler.LoggingHandler : org.springframework.messaging.MessageDeliveryException: Dispatcher has no subscribers for channel 'application.outputChannel'.; nested exception is org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers, failedMessage=GenericMessage [payload=Hello world!, headers={id=ed173189-b102-6f85-5fe5-d901f4585140, timestamp=1525347764578}], failedMessage=GenericMessage [payload=Hello world!, headers={id=ed173189-b102-6f85-5fe5-d901f4585140, timestamp=1525347764578}]
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:77)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:445)
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:394)
at be.thingsplay.fmb920simulator.tcp.SimulatorTask.sendMsg(SimulatorTask.java:20)
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:498)
at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:65)
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
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.integration.MessageDispatchingException: Dispatcher has no subscribers, failedMessage=GenericMessage [payload=Hello world!, headers={id=ed173189-b102-6f85-5fe5-d901f4585140, timestamp=1525347764578}]
at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:138)
at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:105)
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:73)
... 16 more
I'm really getting bored with Spring...
So, what happens is that you are sending message successfully. The message does successfully get to the outputChannel which you chose to be a DirectChannel.
DirectChannel by definition requires a subscriber, which I don't see in your configuration (such as #Transformer or #ServiceActivator or any other type of MessageHandler), and exception is telling you exactly that.
So, if you just want to validate that the message is sent you may want to chose different implementation of channel. For example, you may choose QueueChannel which will buffer the messages until they are polled from it, or PublishSubscribeChannel which will drop messages if there are no subscribers.
Or, add a subscriber.
#ServiceActivator(inputChannel="outputChannel", outputChannel="replyChannel")
public Message echo(Message message) {
return message;
}

Resources