Camel Restlet sends response to client asynchronously - web-services

I have a scenario that I'm using camel-restlet component to receive post requests, I'm forwarding these requests to an external web service, after receiving the response code from the external service, I need to add this response code to my own response to the client asynchronously.
Im trying to save the response object to a hashMap where key is an unique serial number generated based on the request content, once upon receiving the response from external web service, I can retrieve the response object from the hashMap using this unique key. Seems like restlet saves the response to exchange.getOut() message and sends back to the client synchronously which is not something I want. Not setting an out message would give me a nullPointerException.
route Class:
public class ReceiveRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("restlet:http://localhost:8083/api/atmp?restletMethod=post")
.to("activemq:queue:requestReceiveQueue");
from("activemq:queue:requestReceiveQueue")
.process(new RequestProcessor())
.to("activemq:queue:requestSendQueue");
from("activemq:queue:requestSendQueue")
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.to("jetty:http://localhost:8080/rest_api_demo/api/restService")
.bean("responseProcessor");
}
}
requestProcessor class:
public class RequestProcessor implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
Message message = exchange.getIn();
byte[] bytes = (byte[])message.getBody();
String body = new String(bytes);
String atmpId = GUIDGenerator.generateAtmpSerialNumber();
String terIndentifier = GUIDGenerator.generateTerminalIdentifier(body);
MapLookupHelper.insertResponse(atmpId, terIndentifier, exchange);
Map<String, Object> messageMap = new HashMap<String, Object>();
messageMap = FormatUtil.parseJson(body);
messageMap.put("ATMPId", atmpId);
exchange.getIn().setBody(messageMap.toString());
}
}
responseProcessor class
#Component
public class ResponseProcessor implements Processor {
#Override
public void process(Exchange exchange) throws Exception {
Message in = exchange.getIn();
String responseCode = in.getHeader(Exchange.HTTP_RESPONSE_CODE).toString();
String body = in.getBody().toString();
Map<String, Object> resMap = new HashMap<String, Object>();
resMap = FormatUtil.parseJson(body);
String atmpId = resMap.get("ATMPId").toString();
Exchange ex = MapLookupHelper.getOutMessage(atmpId);
ex.getOut().setHeader("HostResponseCode", responseCode);
ex.getOut().setBody(resMap.toString());
}
}
I'm new to Apache Camel and would like to know if restlet is the right way to go, if not, any suggestion on how I can handle async responses to client in Camel? Is AsyncProcessor only solution to such scenario?

I think it's not issue of restlet. Your exchange pattern is InOut, that's why all jms-endpoint's waiting synchronously result of your .bean("responseProcessor").
Even if you change pattern to InOnly your client will not receive response asynchronously. I think you should make another route's architecture, like below:
from("restlet:http://localhost:8083/api/atmp_asyncRequest?restletMethod=post")
.process(exchange -> {
exchange.setProperty("uniqueRequestId", GUIDGenerator.generateAtmpSerialNumber());
})
.inOnly("seda:requestReceiveQueue")// here starts async processing of your request
.process(exchange -> {
exchange.getProperty("uniqueRequestId");
// make here response for client with generated request id
});
from("seda:requestReceiveQueue")
.process(exchange -> {
// prepare\process request if need
})
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.to("jetty:http://localhost:8080/rest_api_demo/api/restService")
.process(exchange -> {
exchange.getProperty("uniqueRequestId");
// save somewhere prepared response for client bound to generated request id
});
from("restlet:http://localhost:8083/api/atmp_getResponse?restletMethod=post")
.process(exchange -> {
String requestId = ;//extract request id from client's request
Object body = ;//find response that you saved asynchronously by extracted request id
// if response not found, then async processing request not ended, so you should send message to client to continue polling
exchange.getIn().setBody(body);
});
That will work if you haven't callback server for async responses on client's side.
Also you can use Seda component instead of jms, for queueing tasks between routes.

Related

Django channels with Spring Boot Websockets (StompSession) do not work

Hey there we want to use Django just to execute python code and use channels for the results. Implemented everything the websockets are not working as they should. If I try to send something from our Angular frontend to Django it works fine. And otherwise our Spring Boot StompSession Websockets work fine with other Spring Boot Applications and Angular.
But Django 3.1.3 and Spring Boot 2.3.3 do not.
Additionally channels 3.0.2, channels-redis 3.2.0 and a docker container of redis 6 are used.
What I can see from the Django server logs:
WebSocket HANDSHAKING /ws/card-detection/ [127.0.0.1:53209]
i'm in connect method
WebSocket CONNECT /ws/card-detection/ [127.0.0.1:53209]
i'm in receive method
text_data: CONNECT
This gets executed as soon as the websocket request comes in.
The django consumer (without the prints that are logged above):
class MyConsumer(WebsocketConsumer):
groups = ["broadcast"]
def connect(self):
self.accept()
def receive(self, text_data=None, bytes_data=None):
self.send(text_data="Hello world!")
def disconnect(self, close_code):
pass
From Spring Boot we send an base64 encoded image. But also everything else does not work. As you can see the text_data is just CONNECT.
Spring Boot:
StompSessionHandler sessionHandler =
new CardDetectionStompSessionHandler(request, user, webSocketMessagingService);
createStompClient().connect(djangoServiceWSPath, sessionHandler);
}
private WebSocketStompClient createStompClient() {
WebSocketClient client = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(client);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
return stompClient;
}
The stomp session handler to give you an idea how it looks. It should go in the afterConnected() method but it will never go in any of this methods:
public class CardDetectionStompSessionHandler extends StompSessionHandlerAdapter {
private final ImageClassificationRequest request;
private final String username;
private final String destination = "/card-detection/";
private final WebSocketMessagingService webSocketMessagingService;
private StompSession session;
public CardDetectionStompSessionHandler(
ImageClassificationRequest request,
String username,
WebSocketMessagingService webSocketMessagingService) {
this.request = request;
this.username = username;
this.webSocketMessagingService = webSocketMessagingService;
}
#Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
log.info("Client: New session established: {}", session.getSessionId());
this.session = session;
session.subscribe("/user/queue/response", this);
log.info("Client: Subscribed to \"/user/queue/response\"");
session.send("/app" + destination, request);
log.info("Client: Message sent to {}", session.getSessionId());
}
#Override
public Type getPayloadType(StompHeaders headers) {
return ImageClassificationResponse.class;
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
log.info("Client: Message received");
ImageClassificationResponse response = (ImageClassificationResponse) payload;
webSocketMessagingService.sendToUser(username, response);
if (response.isDone()) {
session.disconnect();
log.info("Client: Disconnected.");
}
}
#Override
public void handleTransportError(StompSession session, Throwable exception) {
log.info("Client: Django is not available");
WebSocketResponse response = new WebSocketResponse();
response.setDisconnect(true);
webSocketMessagingService.sendToUser(username, response);
}
}
We tried to change it to an async websocket in Django but that didn't changed a thing. The self.accept in Djangos connect() method does not seem to send a response or the response Spring Boot needs to go on.

Springboot SOAP service unmarshalling Issue with the response from WebServiceTemplate

I am really bugged with an unmarshalling issue with the response from the SOAP service. I am using springboot application and WebServiceTemplate for calling an existing SOAP service. I am using below code to set up beans for marshalling and webservicetemplate. Any help is highly appreciated.
On calling webServiceTemplate.marshalSendAndReceive(request); I am expecting TravelResponse object but it is giving me JAXBElement<TravelResponse> object as response. I need help to understand
1) why is it giving above response instead of TravelResponse
2) How to convert to TravelResponse
Code snippet below:
#Bean
Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
jaxb2Marshaller.setContextPath("com.cater.trip.simple_api.trip.v1");
return jaxb2Marshaller;
}
#Bean
public WebServiceTemplate webServiceTemplate() throws Exception {
WebServiceTemplate webServiceTemplate = new WebServiceTemplate();
webServiceTemplate.setMessageFactory(getMessageFactory());
webServiceTemplate.setMarshaller(jaxb2Marshaller());
webServiceTemplate.setUnmarshaller(jaxb2Marshaller());
webServiceTemplate.setDefaultUri(defaultUri);
webServiceTemplate.setMessageSender(getMessageSender());
return webServiceTemplate;
}
#Bean
public SaajSoapMessageFactory getMessageFactory() {
return new SaajSoapMessageFactory();
}
#Bean
public HttpComponentsMessageSender getMessageSender() {
return new HttpComponentsMessageSender();
}
#Override
public Object getData( ) {
ObjectFactory clientFac = new ObjectFactory();
TravelRequest request = populateRequest(clientFac);
TravelResponse res = (TravelResponse) webServiceTemplate.marshalSendAndReceive(request);
return res;
}
As per Spring's doc, WebServiceTemplate.marshalSendAndReceive(Object requestPayload)
Sends a web service message that contains the given payload, marshalled by the configured Marshaller. Returns the unmarshalled payload of the response message, if any.
This will only work with a default uri specified!
So, you can do this to return the expected response.
JAXBElement<TravelResponse> res = (JAXBElement<TravelResponse>) webServiceTemplate.marshalSendAndReceive(request);
return res.getValue();
Try JaxbIntrospector.getValue to get the actual response from JAXB element.
TravelResponse response = JaxbIntrospector.getValue(jaxbElement);

Camel exchange expired via jetty continuation

Is there a possibility in Apache Camel to register a handler for managing exchanges that cannot be written to jetty endpoint http response because continuation timeout has been reached?
I'll just add my notes on that because I made it available in my project by modifying CamelContinuationServlet in the if (continuation.isExpired()) block like this
if (continuation.isExpired()) {
String id = (String) continuation.getAttribute(EXCHANGE_ATTRIBUTE_ID);
// remember this id as expired
expiredExchanges.put(id, id);
log.warn("Continuation expired of exchangeId: {}", id);
consumer.getBinding().doWriteExceptionResponse(new TimeoutException(), response);
return;
}
in combination with a custom HttpBinding called ErrorHandlingHttpBinding in my code like this
public class ErrorHandlingHttpBinding extends DefaultHttpBinding {
#Override
public void doWriteExceptionResponse(Throwable exception, HttpServletResponse response) throws IOException {
if (exception instanceof TimeoutException) {
response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT);
response.getWriter().write("Continuation timed out...");
} else {
super.doWriteExceptionResponse(exception, response);
}
}
}
registered as spring bean with id="errorHandlingHttpBinding" and referred in the component string as jetty:http://localhost:21010/?useContinuation=true&continuationTimeout=1&httpBindingRef=errorHandlingHttpBinding.
No this is not possible. Maybe you need to set a higher timeout if you have some slow processing exchanges.
You are welcome to dive in the Jetty APIs to see if you can find a hook for such a onTimeout event and see what it takes to support that in camel-jetty.

Implement CXF client to read LIST from a REST server

I am trying to implement a rest web service using Apache CXF and I want to return list of object as a response from server. So i have used generic entity to wrap my list on a server and everything is fine when I enter path from browser. It prints XML representation of object because I have used Jackson JAX-B but when i try to use JAX-RS client. I am getting an exception.
Exception in thread "main" javax.ws.rs.client.ResponseProcessingException: Problem with reading the data, class XYZ, ContentType: /.
at org.apache.cxf.jaxrs.impl.ResponseImpl.reportMessageHandlerProblem(ResponseImpl.java:433)
at org.apache.cxf.jaxrs.impl.ResponseImpl.doReadEntity(ResponseImpl.java:378)
at org.apache.cxf.jaxrs.impl.ResponseImpl.readEntity(ResponseImpl.java:325)
at org.apache.cxf.jaxrs.impl.ResponseImpl.readEntity(ResponseImpl.java:313)
at XYZ.ABC()
at XYZ.ABC()
Caused by: javax.ws.rs.core.NoContentException: Message body is empty
at org.apache.cxf.jaxrs.provider.AbstractJAXBProvider.reportEmptyContentLength(AbstractJAXBProvider.java:276)
at org.apache.cxf.jaxrs.provider.JAXBElementProvider.readFrom(JAXBElementProvider.java:166)
at org.apache.cxf.jaxrs.utils.JAXRSUtils.readFromMessageBodyReader(JAXRSUtils.java:1325)
at org.apache.cxf.jaxrs.impl.ResponseImpl.doReadEntity(ResponseImpl.java:369)
... 4 more
I have written a following client code to get data from server
final Client client = ClientBuilder.newClient();
WebTarget webTarget = client.target(URI.create(PATH));
Response response = webTarget.request(MediaType.APPLICATION_XML).get();
List<ABC> obj = response.readEntity(new GenericType<List<ABC>> (ABC.class){});
But Apart from it I have tried many code to implement CXF client and get data from server but I am getting a same exception almost all the time. I have tried JAXRSCLIENTFactory also to implement client but the same exception.
I ran into the same problem in unmarshalling using the CXF client. Here is how I did it:
Read the response into a String.
Used Gson to convert from string to list of objects.
Note: You will need a wrapper class for your list of objects.
Example:
If the server returns a list of products, Here is how to unmarshall the list:
Create a wrapper class
public class ProductList {
private List<Product> products;
public List<Product> getProducts() {
return products;
}
public void setProducts(List<Product> products) {
this.products = products;
}
}
Code to unmarshall
String responseBody = response.readEntity(String.class);
ProductList productList = new Gson().fromJson(responseBody, ProductList.class);
if(productList.getProducts() != null)
return productList.getProducts();

How to I unit test a #future method that uses a callout?

I have a trigger that fires when an opportunity is updated, as part of that I need to call our API with some detail from the opportunity.
As per many suggestions on the web I've created a class that contains a #future method to make the callout.
I'm trying to catch an exception that gets thrown in the #future method, but the test method isn't seeing it.
The class under test looks like this:
public with sharing class WDAPIInterface {
public WDAPIInterface() {
}
#future(callout=true) public static void send(String endpoint, String method, String body) {
HttpRequest req = new HttpRequest();
req.setEndpoint(endpoint);
req.setMethod(method);
req.setBody(body);
Http http = new Http();
HttpResponse response = http.send(req);
if(response.getStatusCode() != 201) {
System.debug('Unexpected response from web service, expecte response status status 201 but got ' + response.getStatusCode());
throw new WDAPIException('Unexpected response from web service, expecte response status status 201 but got ' + response.getStatusCode());
}
}
}
here's the unit test:
#isTest static void test_exception_is_thrown_on_unexpected_response() {
try {
WDHttpCalloutMock mockResponse = new WDHttpCalloutMock(500, 'Complete', '', null);
WDAPIInterface.send('https://example.com', 'POST', '{}');
} catch (WDAPIException ex) {
return;
}
System.assert(false, 'Expected WDAPIException to be thrown, but it wasnt');
}
Now, I've read that the way to test #future methods is to surround the call with Test.startTest() & Test.endTest(), however when I do that I get another error:
METHOD RESULT
test_exception_is_thrown_on_unexpected_response : Skip
MESSAGE
Methods defined as TestMethod do not support Web service callouts, test skipped
So the question is, how do I unit test a #future method that makes an callout?
The callout is getting skipped because the HttpCalloutMock isn't being used.
I assume that WDHttpCalloutMock implements HttpCalloutMock?
If so, a call to Test.setMock should have it return the mocked response to the callout.
WDHttpCalloutMock mockResponse = new WDHttpCalloutMock(500, 'Complete', '', null);
Test.setMock(HttpCalloutMock.class, mockResponse);
WDAPIInterface.send('https://example.com', 'POST', '{}');
Incidentally, the Salesforce StackExchange site is a great place to ask Salesforce specific questions.