Spring Integration http outbound gateway POST restful call with JSON - spring-integration

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?

Related

Spring Integration Outbound Gate way for Synchronous REST Calls

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

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! ##############");

Using QueueChannel in Spring Integration

I am working on the below case study.
Create a Rest Service to accept Reference id.
Use the Reference Id to get data (CLOB) from Database.
Put the data(CLOB) in a channel(queue) for further processing.
Reply to the rest client with response data in JSON format {"status": true,"message": "RECEIVED"}
I have the Rest Service created and using the Ref Id am getting the data from database but I am unable to send the response back to the rest client after putting the message in a channel(queue).
The output received by Rest Client is : No reply received within timeout
Basically I want the request thread to be returned immediately after the data (CLOB) is pushed to the channel(queue).
Below is the configuration.
<int:channel id="responseChannel"/>
<int:channel id="initCalculation">
<int:queue/>
</int:channel>
<!-- GET -->
<int-http:inbound-gateway
request-channel="httpGetChannel"
reply-channel="responseChannel"
supported-methods="GET"
path="/init/{refId}"
payload-expression="#pathVariables.refId">
<int-http:request-mapping produces="application/json"/>
</int-http:inbound-gateway>
<int:chain input-channel="httpGetChannel" output-channel="initCalculation">
<int-jdbc:stored-proc-outbound-gateway
id="outbound-gateway-storedproc-get-forma" data-source="dataSource"
is-function="false"
stored-procedure-name="XX_EMPROC.GET_FRMA"
ignore-column-meta-data="true"
expect-single-result="true">
<int-jdbc:sql-parameter-definition name="V_REF_ID" direction="IN" />
<int-jdbc:sql-parameter-definition name="V_FRMA" direction="OUT" type="#{T(oracle.jdbc.OracleTypes).CLOB}"/>
<int-jdbc:parameter name="V_REF_ID" expression="payload" />
</int-jdbc:stored-proc-outbound-gateway>
<!- Convert to JSON Format -->
<int:service-activator ref="brInitGateway" method="getResponse"/>
</int:chain>
<int:outbound-channel-adapter channel="initCalculation" ref="brInitGateway" method="process"/>
Kindly advise on the corrections needed in the above.
Thanks
Look, there is no body who sends message as a reply to the <int-http:inbound-gateway>. You have declared responseChannel, but who is going to use it as an output-channel?
I'd suggest you to do this:
<publish-subscribe-channel id="responseChannel"/>
<int:chain input-channel="httpGetChannel" output-channel="responseChannel">
<int:bridge input-channel="responseChannel" output-channel="initCalculation"/>
So, what happens here:
The publish-subscribe-channel for the reply-channel makes an internal bridge to the replyChannel header as one of the subscribers.
You send a result from the <chain> to that channel. Therefore the <int-http:inbound-gateway> gets its reply.
Having that <int:bridge> from response to the initCalculation you have a second subscriber and, therefore, send a message to the required queue.
If you are not interested in the brInitGateway.getResponse() as a reply for the HTTP request, you should consider do not have that reply-channel="responseChannel" at all, but still use some <publish-subscribe-channel> to send to the queue and some transformer to prepare a reply, e.g.:
<transformer input-channel="prepareProcess" expression="' {"status": true,"message": "RECEIVED"}'"/>
This transformer is without output-channel because it is going to send its result into the replyChannel header, therefore to the <int-http:inbound-gateway> initiator.

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.

spring integration http outbound adapter not resolving properties

We are using http outbound adapter to make http get request and we want to read URL from properties file as it changes from envt to envt. We also append some other path to this url using message payload but then it is giving us this error message "Caused by: java.lang.IllegalArgumentException: Map has no value for URL". All we need is read base url from properties file and generate final url with payload.
Here is our sample config looks like
<int-http:outbound-gateway request-channel="requestChannel"
url="${url}/{payload}"
http-method="GET"
expected-response-type="java.lang.String"
>
</int-http:outbound-gateway>
Actually {payload} in your URL is an URI variable and it can't be resolved automatically. See how it works:
UriComponentsBuilder.fromUriString(uri).buildAndExpand(uriVariables)
Where uriVariables is a Map for those URI variables.
So, in your case the expected configuration must be like this:
<int-http:outbound-gateway request-channel="requestChannel"
url="${url}/{payload}"
http-method="GET"
expected-response-type="java.lang.String">
<int-http:uri-variable name="payload" expression="payload"/>
</int-http:outbound-gateway>
More information you can find in the Reference Manual.

Resources