I am trying to communicate with a websocket server using Apache Camel AHC-Websocket Component with Akka Camel in Java. In this case, websocket endpoint is goint to be a well known websocket public service.
Using:
JDK 8.x
Akka Java API with Akka Camel, Akka Actor and Akka SLF4J 2.3.9
Apache Camel with AHC WS Component 2.14.1
I followed Akka Camel tutorial for Java located over here.
Short description: Every response received by UntypedProducerActor when I make a request returns CamelMessage with body field as null. But when I make request via ProducerTemplate I receive correct response.
Long description: I am getting strange behaviour from Akka Camel when I make a request and expect a response from the websocket endpoint. When I make a request to the endpoint via defined ActorRef, for example like so:
ActorRef wsProducer = getContext().actorOf(SimpleProducer.props("ahc-ws:echo.websocket.org"));
final Timeout timeout = new Timeout(3, TimeUnit.MINUTES);
final Future<Object> future = Patterns.ask(wsProducer, "Please, respond!", timeout);
final Object result = Await.result(future, timeout.duration());
I clearly see in the logs that Apache Camel websocket endpoint received a response:
DEBUG o.a.c.component.ahc.ws.WsEndpoint - received message --> Please, respond!
But the result object will be a CamelMessage with body field always set to null. The same CamelMessage present in public Object onTransformResponse(Object message) method of my SimpleProducer.
However, when I make request via ProducerTemplate like this:
final Camel camel = CamelExtension.get(getContext().system());
final CamelContext context = camel.context();
final ProducerTemplate template = camel.template();
Object result = template.requestBody("ahc-ws:echo.websocket.org", "Alpha is there!");
It works and result will contain correct response body: "Alpha is there!".
My SimpleProducer is pretty much the same as in tutorial:
public class SimpleProducer extends UntypedProducerActor {
private final LoggingAdapter LOG = Logging.getLogger(getContext().system(), this);
private final String mEndpointUri;
public SimpleProducer(final String serverUrl) {
mEndpointUri = serverUrl;
}
#Override
public String getEndpointUri() {
return mEndpointUri;
}
#Override
public Object onTransformResponse(Object message) {
return super.onTransformOutgoingMessage(message);
}
#Override
public boolean isOneway() {
return false;
}
public static Props props(final String endpointUri) {
return Props.create(new Creator<SimpleProducer>() {
private static final long serialVersionUID = 1L;
#Override
public SimpleProducer create() throws Exception {
return new SimpleProducer(endpointUri);
}
});
}
}
Maybe somebody had the same issue and can help me out?
Related
I'm using java with CXF to create a client with WS-Addressing features:
String url = "http://example.com";
CxfService service = new CxfService(new WSAddressingFeature());
CxfServicePort port = service.getCxfServicePort();
AddressingProperties addressingProperties = new AddressingProperties();
RelatesToType relatesToType = new RelatesToType();
relatesToType.setRelationshipType("correlationid");
addressingProperties.setRelatesTo(relatesToType);
BindingProvider bp = (BindingProvider) port;
bp.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, url);
bp.getRequestContext().put(JAXWSAConstants.CLIENT_ADDRESSING_PROPERTIES, addressingProperties);
I have an interceptor that sets the value of RelatesTo property:
public class CorrelationIdInterceptor extends AbstractSoapInterceptor {
public CorrelationIdInterceptor() {
super(Phase.POST_LOGICAL);
}
public void handleMessage(SoapMessage message) throws Fault {
AddressingProperties addressingProperties = (AddressingProperties) message.get(JAXWSAConstants.CLIENT_ADDRESSING_PROPERTIES);
addressingProperties.getRelatesTo().setValue(UUID.randomUUID().toString());
}
The issue with above configuration is that all SOAP requests are sharing the same requestContext (as indicated on https://cxf.apache.org/faq.html#FAQ-AreJAX-WSclientproxiesthreadsafe?. That also means that every message has the same MessageID and I need to avoid that.
I could use the thread.local.request.context=true flag, but then I don't know where and how to put the creation of AddressingProperties object. Moreover, setting the flag to true also clears my endpoint address, which is a bit frustrating.
What is recommended way of making sure each SOAP request is using an unique AddressingProperties and MessageID ?
Kind regards
I have a Micronaut application running with the below configuration:
micronaut:
server:
cors:
enabled: true
port: 8080
Now I have an enhancement where I want to call a 3rd party URL and get the response in my application (one of the module in my application). I used the below code snippet:
EmbeddedServer server = ApplicationContext.run(EmbeddedServer.class);
HttpClient client = server .getApplicationContext() .createBean(HttpClient.class, server.getURL());
HttpRequest req = HttpRequest.GET(urlHost);
HttpResponse<String> response = client.toBlocking().exchange(req, String.class);
But this is not working. I get port already in use. I did not find much help in google because Micronaut's HttpClient is usually used in Micronaut Test which is not in my case. Is this possible to use it in my application? If so how? Thanks in Advance.
It is because you are starting another server by ApplicationContext.run(EmbeddedServer.class).
You don't need it. It is enough to inject HttpClient into your class by constructor:
#Singleton
public class MyClient {
private final RxHttpClient client;
public MyClient(#Client("https://some.third-party.com") RxHttpClient client) {
this.client = client;
}
HttpResponse<String> getSomething(Integer id) {
URI uri = UriBuilder.of("/some-objects").path(id).build();
return client.toBlocking().exchange(HttpRequest.GET(uri), String.class);
}
}
If you have third-party server URL in application configuration under some-service.url path for example, then you can use #Client("${some-service.url}")
Another option is to define declarative client for the third-party server and then inject it in your classes where needed.
First define client interface for your third-party service:
#Client("some-service")
public interface SomeServiceClient {
#Get("/api/some-objects/{id}")
String getSomeObject(#QueryValue("id") Integer id);
}
Add client configuration for that service in application configuration (application.yaml):
micronaut:
http:
services:
some-service:
url: "https://some.third-party.com"
read-timeout: 1m
And then you can inject the SomeServiceClient where you need it:
#Singleton
public class SomeServiceConsumer {
private final SomeServiceClient client;
public SomeServiceConsumer(SomeServiceClient client) {
this.client = client;
}
void doWithSomething(Integer id) {
String object = client.getSomeObject(id);
... // processing of object here
}
}
You can find more information in a Micronaut documentation
https://guides.micronaut.io/latest/micronaut-http-client-gradle-java.html
Problem
How to forward requests in Spring Cloud application? I need to forward requests to other services depending on the part of uri.
For example
HTTP GET http://user-application/api/users, returns users JSON.
HTTP GET http://user-application/api/proxy/jobs-application/api/jobs, returns jobs JSON, but this request should be forwarded to another application:
HTTP GET http://jobs-application/api/jobs.
Any HTTP method is allowed, not only GET.
Context
I have a SpringBoot Application, User application which has REST end-points which return data.
For example GET http://user-application/api/users would return users in the JSON format.
User application also has an HTTP end-point which should forward the request to other applications - let's call one of them Jobs application.
This end-point is HTTP {ANY_METHOD} /api/proxy/{dynamic-service}/{dynamic-path} as an example,
GET http://user-application/api/proxy/jobs-application/api/jobs
Please, note, initial request comes to the User application, while then it is forwarded to the Jobs application.
Approaches
I put some my approaches which I think about. Maybe you have done similar things in the past, so you could share your experience doing so. Or even improve one of my approaches.
ProxyController approach
I would create a ProxyController in User application with mapping /proxy
#Controller
#RequestMaping("/proxy/**")
ProxyController
public void proxy(final HttpServletRequest request, HttpResponse response) {
final String requestUri = request.getRequestUri();
if (!requestUri.startsWith("/api/proxy/")) {
return null; // Do not proxy
}
final int proxyIndex = "/api/proxy/".lenght(); // Can be made a constant
final String proxiedUrl = requestUri.subString(proxyIndex, requestUri.lenght());
final Optional<String> payload = retrievePayload(request);
final Headers headers = retrieveHeaders(request);
final HttpRequest proxyRequest = buildProxyRequest(request, headers);
payload.ifPresent(proxyRequest::setPayload);
final HttpResponse proxyResponse = httpClient.execute(proxyRequest)
pdateResponse(response, proxyResponse);
}
The problem with this approach, I have to write a lot of code t build a proxy request, to check if it has payload and if it has, copy it into proxy request, then copy headers, cookies etc to the proxy request, copy HTTP verb into proxy request. Then when I get proxy response, I have to populate its details into the response.
Zuul approach
I was inspired by ZuulFilters:
https://www.baeldung.com/spring-rest-with-zuul-proxy
https://stackoverflow.com/a/47856576/4587961
#Component
public class ProxyFilter extends ZuulFilter {
private static final String PROXY_PART = "/api/proxy";
private static final int PART_LENGTH = PROXY_PART.length();
#Autowired
public ProxyFilter() {
}
#Override
public boolean shouldFilter() {
final RequestContext context = RequestContext.getCurrentContext();
final String requestURI = retrieveRequestUri(context);
return requestURI.startsWith(PROXY_PART);
}
#Override
public Object run() {
final RequestContext context = RequestContext.getCurrentContext();
final String requestURI = retrieveRequestUri(context);
final String forwardUri = requestURI.substring(PART_LENGTH);
context.setRouteHost(buildUrl(forwardUri));
return null;
}
#Override
public String filterType() {
return "proxy";
}
#Override
public int filterOrder() {
return 0;
}
private String retrieveRequestUri(final RequestContext context) {
final HttpServletRequest request = context.getRequest();
return request.getRequestURI();
}
private URL buildUrl(final String uri) {
try {
return new URL(uri);
} catch (MalformedURLException e) {
throw new RuntimeException(String.format("Failed to forward request uri %s}.", uri), e);
}
}
}
This code allows me to forward requests with less effort. However, we also use client side load balancer Ribbon and circuit breaker Hystrix in Spring Cloud Zuul out of box. How to enable these features? Will they be enabled out of box in context.setRouteHost(forwardUrl);
I would like to add another approach, maybe it can also work.
Static application.yml file to configure Zuul proxy approach
This approach does not requre dynamic Zuul Filters.
application.yml
zuul:
routes:
user-application:
path: /api/users/**
serviceId: user-service
stripPrefix: false
sensitiveHeaders:
# I have to define all other services similarly.
jobs-application:
path: /api/proxy/jobs/**
serviceId: jobs-application
stripPrefix: true
sensitiveHeaders:
It will work only if I know all the services my clients need to call before I deploy the User application. What if a new application is added dynamically? Then I will have to update the configuration.
I have setup locastack in my local pc. I am able to create, send and receive in the queue using command line.
How am trying to connect SpringBoot application with the localstcak queue.
I am not finding any tutorial which will guide me how we can read data from localstack queue using spring cloud.
I have a class which looks like this
#Component
#Profile("aws")
public class EventListener {
private static final Logger LOGGER = LoggerFactory.getLogger(VisitsQueue.class);
#Value("${sqs.queuename}")
private String queueName;
private ObjectMapper mapper = new ObjectMapper();
#RuntimeUse
#SqsListener("${sqs.queuename}")
public void receiveMessage(String message, #Header(value = "SenderId", required = false) String senderId,
#Headers Map<String, Object> allHeaders) {
LOGGER.info("Received message with content {}", message);
}
}
I'm facing the following problem and I found no working solution yet.
I have 3 different applications that should communicate with each other:
the UI part (1)
the backend application (2)
the microservice "in the cloud" (3)
The backend application provides a Webservice (REST) for the UI to get and put information from/to the microservice.
Everything I want to grab from the microservice works fine, but:
If I want to put data to the microservice, the specs require a websocket connection. This works fine too, but the microservice returns a message after the (un-)successful command, like
{"statusCode":200,"messageId":"1234567890"}
The problem now is: How can I grab this message in my application and send it back to the UI, so the user knows if the command was successful?
For the moment I tried this:
WebSocketClient.java
#OnMessage
public void onMessage(Session session, String msg) {
if (this.messageHandler != null) {
this.messageHandler.handleMessage(msg);
}
}
public void addMessageHandler(MessageHandler msgHandler) {
this.messageHandler = msgHandler;
}
public static interface MessageHandler {
public String handleMessage(String message);
}
MyTotalAwesomeController.java
public class MyTotalAwesomeController {
WebSocketClient wsc = new WebSocketClient();
...
#RequestMapping(value="/add", method={RequestMethod.POST, RequestMethod.OPTIONS})
public ResponseEntity<Object> putDataToMicroservice(#RequestBody Map<String, Object> payload, #RequestHeader(value = "authorization") String authorizationHeader) throws Exception {
...
wsc.addMessageHandler(new WebSocketClient.MessageHandler() {
public String handleMessage(String message) {
System.out.println("RETURN MSG FROM WSS : " + message);
return message;
}
});
return ResponseEntity.ok("worked");
}
I can see the console output from the MessageHandler return, but I don't know how I can pass this to the parent method for return insted of just returning the ResponseEntity.ok().
I'm not very used to WebSocket connections in Java yet, so please don't judge me ;-)
Thank you for your help.
The code below will work under the assumption that the #OnMessage method is executed in a thread managed by the WebSocket client runtime. Please inspect the thread that runs the #OnMessage method.
If the above premise is true, the putDataToMicroservice() method, executed by a thread in the global scope, will wait until the WebSocket response arrives at the WS client thread, which will repass the message to the global scope thread. Then the execution in your controller class will continue.
public class MyTotalAwesomeController {
WebSocketClient wsc = new WebSocketClient();
// Queue for communication between threads.
private BlockingQueue<String> queue;
#PostConstruct
void init() {
queue = new SynchronousQueue<>(true);
// This callback will be invoked by the WebSocket thread.
wsc.addMessageHandler(new WebSocketClient.MessageHandler() {
#Override
public String handleMessage(String message) {
System.out.println("RETURN MSG FROM WSS : " + message);
// Pass message to the controller thread.
queue.put(message);
// Note that the return value is not necessary.
// You can take it out of the interface as well.
return null;
}
});
}
#RequestMapping(value="/add", method={RequestMethod.POST, RequestMethod.OPTIONS})
public ResponseEntity<Object> putDataToMicroservice(#RequestBody Map<String, Object> payload, #RequestHeader(value = "authorization") String authorizationHeader) throws Exception {
// At this point you make a WebSocket request, is that right?
doWebSocketRequest();
// This poll call will block the current thread
// until the WebSocket server responds,
// or gives up waiting after the specified timeout.
//
// When the WebSocket server delivers a response,
// the WS client implementation will execute the
// #OnMessage annotated method in a thread
// managed by the WS client itself.
//
// The #OnMessage method will pass the message
// to this thread in the queue below.
String message = queue.poll(30, TimeUnit.SECONDS);
if (message == null) {
// WebSocket timeout.
}
return ResponseEntity.ok("worked");
}
}