Spring Integration Outbound Gate way for Synchronous REST Calls - spring-integration

Previously i was able to develop a little framework using the Spring Integration Where developers can specify the URLs, HTTP methods and Request Body and invoke any external REST API.
This is the configuration for my Spring Integration
<int:channel id='reply.channel'>
<int:queue capacity='10' />
</int:channel>
<int:channel id='request.channel'/>
<int:channel id='outbound.Channel'/>
<int:gateway id="outboundGateway"
service-interface="com.bst.pm.PostGateway"
default-request-channel="outbound.Channel">
</int:gateway>
<int:object-to-json-transformer input-channel="outbound.Channel" output-channel="request.channel"/>
<int-http:outbound-gateway id="outbound.gateway"
request-channel="request.channel" url-expression="headers.bstUrl"
http-method-expression="headers.bstHttpMethod" expected-response-type-expression="headers.bstExpectedResponseType"
charset="UTF-8" reply-timeout="5000" reply-channel="reply.channel"
mapped-request-headers="bst*, HTTP_REQUEST_HEADERS">
</int-http:outbound-gateway>
Then Developers can invoke external rest API calls using the above infrastruture as below
#Autowired #Qualifier("reply.channel") PollableChannel receivedChannel;
#Autowired #Qualifier("request.channel") MessageChannel getRequestChannel;
#Autowired #Qualifier("outbound.Channel") MessageChannel httpOutboundGateway;
Post post = new Post();
post.setTitle("Spring INtegration Test");
post.setBody("This is a sample request body to test Spring Integration HTTP Outbound gateway");
post.setUserId(Long.valueOf(1));
Message<?> message = MessageBuilder.withPayload(post)
.setHeader("bstUrl", "https://jsonplaceholder.typicode.com/posts")
.setHeader("bstHttpMethod", "POST")
.setHeader("bstExpectedResponseType", "com.bst.pages.crm.web.Post")
.build();
httpOutboundGateway.send(message);
Message<?> receivedMsg = receivedChannel.receive();
Post post = (Post) receivedMsg.getPayload();
System.out.println("############## ServerMsg ##############");
System.out.println(o);
System.out.println("############## Done! ##############");
In here i want to make all the REST calls via this integration infrastructure to be Synchronous. However i have used a QUEUE channel as the reply-channel for the http:outbound-gateway. Therefore as per my understanding the reply can be received by a wrong sender as any one can pool the channel for the messages.
How can we make sure the the correct sender will always receive the correct response?
Thanks,
Keth

Your is correct. Really having a global reply channel and several concurrent processes you may end up with work stealing situation.
To fix your problem you need to get rid of the reply-channel on your HTTP Gateway and just rely on your the replyChannel header populated by the Messaging Gateway. However your Gateway method should really be as a request-reply signature: it has to return Object.
See more info about replyChannel header in the Reference Manual: https://docs.spring.io/spring-integration/docs/5.0.6.RELEASE/reference/html/messaging-endpoints-chapter.html#gateway

Related

Spring Integration outbound gate way with dynamic URLs, HTTP methods and different response types

I have a little bit understanding about Spring Integration and so far i have used JMS and File outbound adapters and now i want to introduce the HTTP out bound adapter for Spring REST support. So far so good an i was able to call external REST api with outany issue as below.
Spring Integration Config
<int-http:outbound-gateway id="outbound.gateway"
request-channel="get.request.channel" url="https://jsonplaceholder.typicode.com/posts/1"
http-method="GET" expected-response-type="com.cst.entity.Post"
charset="UTF-8" reply-timeout="5000" reply-channel="reply.channel">
</int-http:outbound-gateway>
Outbound gateway invokation
public void restTest() throws Exception{
Message<?> message = MessageBuilder.withPayload().build();
getRequestChannel.send(message);
Message<?> receivedMsg = receivedChannel.receive();
Post post = (Post) receivedMsg.getPayload();
System.out.println("############## ServerMsg ##############");
System.out.println(post.toString());
System.out.println("############## Done! ##############");
}
However i want to develop a framework where developers can invoke any REST url with any method and expect different types of response types. I found a way of dynamically setting the URL as below by introducing
<int-http:outbound-gateway id="outbound.gateway"
request-channel="get.request.channel" url="{fhirurl}"
http-method="GET" expected-response-type="com.bst.pages.crm.web.Post"
charset="UTF-8" reply-timeout="5000" reply-channel="reply.channel">
</int-http:outbound-gateway>
with
public void restTest() throws Exception{
FHIRInput input = new FHIRInput();
input.setUrl(url);
Message<?> message = MessageBuilder.withPayload(input).build();
getRequestChannel.send(message);
Message<?> receivedMsg = receivedChannel.receive();
Post post = (Post) receivedMsg.getPayload();
System.out.println("############## ServerMsg ##############");
System.out.println(post.toString());
System.out.println("############## Done! ##############");
}
Then i tried to implement dynamic HTTP methods with dynamic response types using the above method and it didn't work and it looks like we can handle only the URL using <int-http:uri-variable/>.
What would be the ideal solution for this. appreciate your help
Thanks,
Keth
EDIT
After following below comments i was able to implement a framework where developers can call use dynamic URL s based on the payload content. below is my configuration for HTTP outbound adapter.
<int-http:outbound-gateway id="outbound.gateway"
request-channel="get.request.channel" url="{fhirurl}"
http-method-expression="payload.getHttpMethod()" expected-response-type-expression="payload.getResponseType()"
charset="UTF-8" reply-timeout="5000" reply-channel="reply.channel">
<int-http:uri-variable name="fhirurl" expression="payload.getUrl()"/>
</int-http:outbound-gateway>
However im still looking for a way to pass a dynamic request body as a POST parameter. Since we use payload to carry the URL, http method and expected response type i can not pass the request body.
It's not clear what you mean by
Then i tried to implement dynamic HTTP methods with dynamic response types using the above method and it didn't work and it looks like we can handle only the URL using <int-http:uri-variable/>.
To handle multiple response types, get them as String (JSON) and then use transformer(s) to convert to the types.
EDIT1
The response type and method can be an expressions:
<xsd:attribute name="expected-response-type-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
SpEL expression to determine the type for the expected response to which the response body should be converted
The returned value of the expression could be an instance of java.lang.Class or
java.lang.String representing a fully qualified class name.
This attribute cannot be provided if expected-response-type has a value
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="http-method-expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
The SpEL expression to determine HTTP method, use when executing requests with this adapter,
dynamically. This attribute cannot be provided if http-method has a value.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
Solution
I was able to come with a solution for a simple framework where we can allow developers to call different rest URLs with different HTTP methods and Response types.
This is the configuration for Spring Integration
<int:channel id='reply.channel'>
<int:queue capacity='10' />
</int:channel>
<int:channel id='request.channel'/>
<int:channel id='outbound.Channel'/>
<int:gateway id="outboundGateway"
service-interface="com.bst.pm.PostGateway"
default-request-channel="outbound.Channel">
</int:gateway>
<int:object-to-json-transformer input-channel="outbound.Channel" output-channel="request.channel"/>
<int-http:outbound-gateway id="outbound.gateway"
request-channel="request.channel" url-expression="headers.bstUrl"
http-method-expression="headers.bstHttpMethod" expected-response-type-expression="headers.bstExpectedResponseType"
charset="UTF-8" reply-timeout="5000" reply-channel="reply.channel"
mapped-request-headers="bst*, HTTP_REQUEST_HEADERS">
</int-http:outbound-gateway>
and here is my Java code for invoking the above integration system.
#Autowired #Qualifier("reply.channel") PollableChannel receivedChannel;
#Autowired #Qualifier("request.channel") MessageChannel getRequestChannel;
#Autowired #Qualifier("outbound.Channel") MessageChannel httpOutboundGateway;
Post post = new Post();
post.setTitle("Spring INtegration Test");
post.setBody("This is a sample request body to test Spring Integration HTTP Outbound gateway");
post.setUserId(Long.valueOf(1));
Message<?> message = MessageBuilder.withPayload(post)
.setHeader("bstUrl", "https://jsonplaceholder.typicode.com/posts")
.setHeader("bstHttpMethod", "POST")
.setHeader("bstExpectedResponseType", "com.bst.pages.crm.web.Post")
.build();
httpOutboundGateway.send(message);
Message<?> receivedMsg = receivedChannel.receive();
Post post = (Post) receivedMsg.getPayload();
System.out.println("############## ServerMsg ##############");
System.out.println(o);
System.out.println("############## Done! ##############");

Spring Integration http outbound gateway POST restful call with JSON

I tried to make a POST restful api call from Spring Integration with http:outbound-gateway. But I couldn't figure out how to pass the request JSON object to the restful API. Can someone show me how to do it? Thanks !
Here is my spring-integration-config.xml
<si:channel id="request.channel" />
<si:channel id="response.channel">
<si:queue capacity="10" />
</si:channel>
<int-http:outbound-gateway id="employeeinfoGateway"
request-channel="request.channel"
url="http://localhost:8080/EmployeeInfo"
http-method="POST"
expected-response-type="java.lang.String"
charset="UTF-8"
reply-timeout="5000"
reply-channel="response.channel">
</int-http:outbound-gateway>
Here is my Java code:
MessageChannel request = (MessageChannel) context.getBean("request.channel");
JSONObject obj = new JSONObject();
obj.put("empId", "100");
request.send(MessageBuilder.withPayload(obj).setHeader("content-type","application/json").build());
Here is the error message I received:
Could not write request: no suitable HttpMessageConverter found for request type [org.json.JSONObject] and content type [application/json], failedMessage=GenericMessage [payload={"empId":"100"}, headers={content-type=application/json, id=a0aba65f-3f49-4dfc-2431-51983666772b, timestamp=1521419315405}]
Not sure what is that JSONObject. Seems for me that's not how Jackson JSON processor works, but I would suggest to try to send just a plain java.util.Map instead.
There is a MappingJackson2HttpMessageConverter in the RestTemplate underneath to convert incoming payloads to a JSON representation.
Maybe you just don't have a com.fasterxml.jackson.core:jackson-databind jar in your classpath?

How to config post parameters in http:outbound-gateway

We have an case that retrieve orders from our customers system by using http post call, post request include username and password, and we can get order contents response then.
We want to call customer's service every 10 minutes, and then process the response in our service activator, however, I don't know how to config username and password as post parameters in spring integration configuration, anyone can help?
my current configuration
<int:inbound-channel-adapter channel="inChannel" expression="''">
<int:poller fixed-delay="60000"></int:poller>
</int:inbound-channel-adapter>
<int:channel id="inChannel"/>
<int:channel id="outChannel"/>
<http:outbound-gateway
url="http://****/vendorServer/order"
request-channel="inChannel" reply-channel="outChannel" http-method="GET" expected-response-type="java.lang.String">
</http:outbound-gateway>
<int:service-activator input-channel="outChannel" ref="orderService"/>
how to config username and password as post parameters in above configuration?
The HTTP POST method implies the body. The organic way to provide the body in Spring Integration is message payload. Therefore you should consider to build a Map payload with required properties before sending to the http:outbound-Gateway

Overloading MessageHistory object for auditing purpouses

I need to audit every message that pass throught an spring-integration flow, very basic one.
The application reads from an int-redis:queue-inbound-channel-adapter and writes in an int-http:outbound-gateway.
The output gateway has a reply-channel in order to receive the response of the http method, is in this exact moment when I want to register de audit information, including the initial message and http response.
If the http method fails, the message goes to errorHandler and I can obtain the history object with timestamps and payload, what's perfect for audit.
But if the http method works (Code 200,201 ...), I get a message including the ResponseEntity object with the status code, but I've no the initial payload from redis.
So, is there any way to corelate the two messages (before sending http outbound and after http response) in order to get the full info for auditing?
Thanks in advance!
Here the integration flow:
<int:message-history/>
<int:channel id="responseChannel"/>
<int:channel id="responseError"/>
<int:logging-channel-adapter channel="responseChannel" level="INFO"
expression="#messageHandlerSpel.handle( #this )" id="logger"/>
<int:logging-channel-adapter channel="responseError"
level="ERROR" expression="#errorHandlerSpel.handle( #this )"
id="LogingERROR"/>
<int-redis:queue-inbound-channel-adapter id="inputRedis" connection-factory="redisCF"
channel="redisInput" queue="redis-input" serializer="" error-channel="responseError"/>
<int:channel id="redisInput" />
<int-http:outbound-gateway id="HttpOutbound" request-channel="redisInput"
url="http://{server}:{port}/{index}/{type}/{id}"
http-method="PUT" extract-request-payload="true" charset="UTF-8"
reply-channel="responseChannel" request-factory="requestFactory"/>
And the handler class:
#Component
public class MessageHandlerSpel {
public String handle(Message<byte[]> message) {
MessageHeaders msgH = message.getHeaders();
MessageHistory msgHistory =
message.getHeaders().get(MessageHistory.HEADER_NAME, MessageHistory.class);
... // Do whatever
return "";
}
One trick is to store the request payload in the custom header before performing the HTTP request.
After receiving response, the request headers will be merged with that response payload. Therefore in the downstream you will have both: the request in the header, and reply in the payload.
The MessageHistory remains the same and there is no reason to hack there somehow.
Another approach is based on the <aggregator> to correlate request and reply. But this one is for more complex or distributed scenario.
The simple copy/paste to header via:
<header-enricher>
<header name="requestPayload" expressio="payload"/>
</header-enricher>
should be enough for you.

How do I get the request URL for message headers

I have an inbound gateway to process JSON payloads. Since we utilize the same backend service as our SOAP endpoints, I need to have an inbound gateway that maps to all paths. I can see the default HTTP headers come across the into the message, but I specifically need the request URL so I can parse it and route to the appropriate serve as needed.
My inbound-gateway:
<int-http:inbound-gateway id="JSONGateway"
path="*" request-channel="JSONRequestChannel" supported-methods="POST"
reply-timeout="5000"
header-mapper="headerMapper"
request-payload-type="java.lang.String" >
</int-http:inbound-gateway>
I tried adding a header-enricher like I do for SOAP, but the TransportContextListener is null.
<int:header-enricher input-channelJSONequestChannel"
output-channel="JSONRequestChannelWithHeaders">
<int:header name="service"
expression="T(org.springframework.ws.transport.context.TransportContextHolder).transportContext.connection.uri.toString().substring(T(org.springframework.ws.transport.context.TransportContextHolder).transportContext.connection.uri.toString().lastIndexOf('/')+1)" />
</int:header-enricher>
I need a way to get the URL of the request so I can parse out the service into the message headers for my downstream router.
/json/ContactService = "ContactService"
/json/ContactService/insert = "ContactService"
/json/ContactService/get/234 = "ContactService"
I also tried adding a header-mapper class, but still run into the same problem. How can I get a handle to the HTTPServletRequest in code? Once I get that I can get all the headers I need.
Well, you didn't check the message headers.
After converting ServletServerHttpRequest to the Message it gets these headers by default:
Message<?> message = messageBuilder
.setHeader(org.springframework.integration.http.HttpHeaders.REQUEST_URL,
request.getURI().toString())
.setHeader(org.springframework.integration.http.HttpHeaders.REQUEST_METHOD,
request.getMethod().toString())
.setHeader(org.springframework.integration.http.HttpHeaders.USER_PRINCIPAL,
servletRequest.getUserPrincipal())
.build();
So, http_requestUrl is for you to go ahead and parse it appropriately.

Resources