I am calling rest endpoints from a middleware using apache camel. When we run the Performance test the requests are going to different endpoints and their bean validations failed.
It is happening at random and when the load is more. below is the code snippet:
#Component
public class AccountGrpcService extends RouteBuilder {
#Autowired
private FluentProducerTemplate producer;
#Override
public void configure() throws Exception {
getContext().getExecutorServiceManager().setDefaultThreadPoolProfile(
threadPool.getAccountThreads());
from(properties.getAccountGrpc())
.doTry()
.process(processor)
//.to(properties.getAccountUrl())
.process(e -> {
log.info("Before invoking CreateAccount {} request {}", properties.getAccountUrl(), e.getIn().getBody(String.class));
log.info("getAccountUrl {}",properties.getAccountUrl());
final String response = producer.withHeader(Exchange.CONTENT_TYPE, CONTENT_TYPE)
.withBody(e.getIn().getBody(String.class))
.withHeader(Exchange.HTTP_METHOD,"POST")
.to(properties.getAccountUrl()).request(String.class);
e.getIn().setBody(response);
})
.doCatch(Exception.class)
.process(errorHandler);
}
properties.getAccountGrpc() is GRPC url, it is from url.
properties.getAccountUrl() is the rest endpoint I am calling.
#Component
public class InquiryGrpcService extends RouteBuilder {
#Autowired
private FluentProducerTemplate producer;
/* #see org.apache.camel.builder.RouteBuilder#configure() */
#Override
public void configure() throws Exception {
getContext().getExecutorServiceManager().setDefaultThreadPoolProfile(
threadPool.getInquiryThreads());
from(properties.getActivationGrpc())
.doTry()
.process(processor)
.process(e -> {
log.info("Before invoking ServiceActivation {} request {}",
properties.getActivationUrl(), e.getIn().getBody(String.class));
log.info("getActivationUrl {}",properties.getActivationUrl());
final String response = producer.withHeader(Exchange.CONTENT_TYPE,
"application/json")
.withBody(e.getIn().getBody(String.class))
.withHeader(Exchange.HTTP_METHOD,"POST")
.to(properties.getActivationUrl()).request(String.class);
e.getIn().setBody(response);
})
.doCatch(Exception.class)
.process(errorHandler)
.doFinally()
.process(exch -> {
final String responseStr = exch.getIn().getBody(String.class);
log.info(responseStr);
exch.getIn().setBody(JsonProtoConvertUtil
.fromJson(responseStr,ServiceActivationResponse.class));
}).process(postHandler)
.convertBodyTo(ServiceActivationResponse.class);
}
Similarly, we have multiple endpoint configurations. for each grpc request, we are calling a rest endpoint.
How it is going to a different endpoint?
What is the reason for requests are going to another endpoint?
Related
Issue
I use AWS X-Ray SDK for Java to enable X-Ray tracing for my Spring Boot micro services.
With following snippet I am able to attach a custom SegmentListener:
final AWSXRayRecorder recorder = AWSXRayRecorderBuilder
.standard()
.withPlugin(new EcsPlugin())
.withSegmentListener(new SLF4JSegmentListener())
.withSegmentListener(new MyHttpHeaderSegementListener())
.build();
AWSXRay.setGlobalRecorder(recorder);
In MyHttpHeaderSegementListener I try to inject a X-Ray annotation based on an incoming HTTP request header (from the frontend):
public class MyHttpHeaderSegementListener implements SegmentListener {
// snippet source: https://stackoverflow.com/a/54349178/6489012
public static Optional<HttpServletRequest> getCurrentHttpRequest() {
return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.filter(ServletRequestAttributes.class::isInstance)
.map(ServletRequestAttributes.class::cast)
.map(ServletRequestAttributes::getRequest);
}
public MyHttpHeaderSegementListener() {}
#Override
public void onBeginSegment(final Segment segment) {
final var httpContext = MyHttpHeaderSegementListener.getCurrentHttpRequest();
httpContext.ifPresent(context -> segment.putAnnotation("Origin", context.getHeader("Origin")));
}
}
The segment listener is triggered as expected onBeginSegment segment but MyHttpHeaderSegementListener.getCurrentHttpRequest() always returns an Optional.empty.
Questions
Is there a possibility to inspect incoming HTTP requests (as they
were received by a Controller) within a SegmentListener?
Does aws-xray-sdk-java maybe even support a native way to do so?
Why is the request retrieved from RequestContextHolder always empty?
(A bit off-topic but: 4. Is it even a good practice to set an annotation based on a HTTP header)
I have no answer for the 2. and 3. question but I found an answer for 1. question.
For incoming requests you need to add a Spring Filter to configure AWS X-Ray. As filters have access to the HTTP request I just wrapped my own filter around the com.amazonaws.xray.javax.servlet.AWSXRayServletFilter of AWS:
public class XRayServletFilter extends AWSXRayServletFilter {
public XRayServletFilter(String fixedSegmentName) {
super(fixedSegmentName);
}
#Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
this.addHttpRequestToContext(request);
super.doFilter(request, response, chain);
}
private void addHttpRequestToContext(final ServletRequest request){
final Optional<HttpServletRequest> httpServletRequest = HttpRequestUtils.castToHttpRequest(request);
if (httpServletRequest.isPresent()) {
final ServletRequestAttributes requestAttributes = new ServletRequestAttributes(httpServletRequest.get());
RequestContextHolder.setRequestAttributes(requestAttributes);
}
}
}
Which uses a static class that I wrote:
public final class HttpRequestUtils {
public static Optional<HttpServletRequest> getCurrentHttpRequest() {
return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.filter(ServletRequestAttributes.class::isInstance)
.map(ServletRequestAttributes.class::cast)
.map(ServletRequestAttributes::getRequest);
}
public static Optional<HttpServletRequest> castToHttpRequest(ServletRequest request) {
try {
return Optional.of((HttpServletRequest) request);
} catch (ClassCastException classCastException) {
return Optional.empty();
}
}
}
This custom filter basically sets the HTTP requests in the RequestContextHolder. After that you can use it in your segment listeners:
public class MyHttpHeaderSegementListener implements SegmentListener {
public MyHttpHeaderSegementListener() {}
#Override
public void onBeginSegment(final Segment segment) {
final Optional<HttpServletRequest> request = HttpRequestUtils.getCurrentHttpRequest();
request.map(req -> req.getHeader("Origin")).ifPresent(origin -> segment.putAnnotation("client_origin", origin));;
}
}
I'm migrating a SOAP service to REST. What I do is I have an wsdl definition, and I use that to automatically generate the request and response classes with JAXB.
This is my endpoint:
#Endpoint
public class RefundServiceEndpoint {
#Autowired
private RefundService refundService;
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(value = RefundException.class)
public void handleException(RefundException exception) {
// This should be called
}
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(value = UnableToMapException.class)
public void handleException(UnableToMapException exception) {
// This should be called
}
#Autowired
HttpServletRequest serverRequest;
#PayloadRoot(namespace = NAMESPACE_URI, localPart = "ReturnRequest")
#ResponsePayload
public ReturnResponse returnRequest(#RequestPayload ReturnRequest request) {
return this.refundService.partialRefund(request, serverRequest).getBody();
}
}
I'm throwing those two exceptions but the handlers don't get called.
The only way I managed to get them is with a try/catch but I'm trying to avoid that.
Thanks
Camel has to call REST service for some integration, However, the REST service has one authentication api (POST api) which needs to be called first to get a token and then other subsequent api calls has to be invoked with the token embedded in header of HTTP requests.
Does Spring Restemplate or apache camel has some api to support the same?
Followed #gusto2 approach, Its pretty much working fine.
SO, I created two routes --> First one is a timer based like below, this generates the token, periodically refreshes it(since the route is timer based) and stores the token in a local variable for being reused by some other route.
#Component
public class RestTokenProducerRoute extends RouteBuilder {
private String refreshedToken;
#Override
public void configure() throws Exception {
restConfiguration().producerComponent("http4");
from("timer://test?period=1200000") //called every 20 mins
.process(
exchange -> exchange.getIn().setBody(
new UserKeyRequest("apiuser", "password")))
.marshal(userKeyRequestJacksonFormat) //convert it to JSON
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.to("http4://localhost:8085/Service/Token")
.unmarshal(userKeyResponseJacksonFormat)
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
UserKeyResponse response= exchange.getIn().getBody(
UserKeyResponse.class); //get the response object
System.out.println(response + "========>>>>>>" +
response.getResult());
setRefreshedToken(response.getResult()); //store the token in some object
}
}).log("${body}");
}
public String getRefreshedToken() {
return refreshedToken;
}
public void setRefreshedToken(String refreshedToken) {
this.refreshedToken = refreshedToken;
}
}
And the second route can call subsequent apis which will use the token generated by the first route, it would be something like this. Have to add error handling scenarios, where token might not be valid or expired. But I guess that would be separate concern to solve.
#Component
public class RestTokenUserOnboardRoute extends RouteBuilder {
private JacksonDataFormat OtherDomainUserRequestJacksonFormat = new JacksonDataFormat(
OtherDomainUserRequest.class);
private JacksonDataFormat OtherDomainUserResponseJacksonFormat = new JacksonDataFormat(
OtherDomainUserResponse.class);
#Override
public void configure() throws Exception {
restConfiguration().producerComponent("http4");
//This route is subscribed to a Salesforce topic, which gets invoked when there is any new messages in the topic.
from("salesforce:CamelTestTopic?sObjectName=MyUser__c&sObjectClass="+MyUser__c.class.getName()))
.convertBodyTo(OtherDomainUserRequest.class)
.marshal(OtherDomainUserRequestJacksonFormat).log("${body}")
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.log("The token being passed is ==> ${bean:tokenObj?method=getRefreshedToken}")
.setHeader("Authorization", simple("${bean:tokenObj?method=getRefreshedToken}"))
.to("http4://localhost:8085/Service/DomainUser")
.unmarshal(OtherDomainUserResponseJacksonFormat)
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
OtherDomainUserResponse response = exchange.getIn().getBody(
OtherDomainUserResponse.class);
System.out.println(response + "==================>>>>>> " + response.getStatusCode());
}
}).log("${body}");
}
}
So, here the token is getting consumed from the tokenObj bean (instance of RestTokenProducerRoute which has method getRefreshedToken() defined. It returns the stored token.
Needless to say, you have set the bean in camelcontext registry as follows along with other settings (like component, route etc). In my case it was as follows.
#Autowired
public RestTokenUserOnboardRoute userOnboardRoute;
#Autowired
public RestTokenProducerRoute serviceTokenProducerRoute;
#Autowired
private RestTokenProducerRoute tokenObj;
#Override
protected CamelContext createCamelContext() throws Exception {
SimpleRegistry registry = new SimpleRegistry();
registry.put("tokenObj", tokenObj); //the tokenObj bean,which can be used anywhere in the camelcontext
SpringCamelContext camelContext = new SpringCamelContext();
camelContext.setRegistry(registry); //add the registry
camelContext.setApplicationContext(getApplicationContext());
camelContext.addComponent("salesforce", salesforceComponent());
camelContext.getTypeConverterRegistry().addTypeConverter(DomainUserRequest.class, MyUser__c.class, new MyTypeConverter());
camelContext.addRoutes(route()); //Some other route
camelContext.addRoutes(serviceTokenProducerRoute); //Token producer Route
camelContext.addRoutes(userOnboardRoute); //Subsequent API call route
camelContext.start();
return camelContext;
}
This solves my problem of setting token dynamically where token is getting produced as a result of execution of some other route.
I've been trying to send an object from one application to another using rest.
Sender:
#Controller
public class Sender {
#RequestMapping(value = "/comMessageApp-api/getMessages")
public String restGetMessages() {
String url = "http://localhost:8079/comMessageApp-api/responseMessages";
HttpEntity<Dto2> entity = new HttpEntity<>(new Dto2());
ResponseEntity<Dto2> response = restTemplate.exchange(url, HttpMethod.POST, entity, Dto2.class);
}
}
Receiver:
#RestController
public class Receiver {
#RequestMapping(value = "/comMessageApp-api/responseMessages")
public void restResponseMessages(HttpEntity<Dto2> request) {
System.out.println(request.getBody());
}
}
DTO:
public class Dto2 {
private String string = "Test string";
public Dto2() {
}
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
}
Jackson is used serialization/deserialization.
Any ideas, why request.getBody() printed in the Receiver is null???
I tried to send the object inside HttpEntity and inside RequestEntity. No success in both cases. On the receiving side I always get null.
Your sender (client) side is very close but your server side doesn't return a value so change the type to Void:
ResponseEntity<Void> response = restOps.exchange(url, HttpMethod.POST, entity, Void.class);
Your receiver (server) side is not quite set up correctly either, you need to set the HTTP method to [edited] POST. You'll also need to tell Spring to map the body of the request (your rest payload) onto the parameter;
#RequestMapping(value = "/comMessageApp-api/responseMessages", method=RequestMethod.POST)
public void recieveDto (#RequestBody final Dto dto) {
System.out.println(dto.toString());
}
[EDIT] Brainfart, the http method should be set to POST on receive annotation.
[Further suggestion]
403 errors may be due to Spring Security, if you have it switched on (check your POM if you're not sure) try this;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests().
antMatchers("/**").permitAll();
}
}
You'll want to be tightening up security once you know it works.
try to use #RequestMapping(method = RequestMethod.POST, produces = "application/json", consumes = "application/json")
I have a fairly simple setup to test Stomp support in Spring.
I was planning on using JS to send messages to the queue and receive and handle them in Spring app. However, it doesn't seem to work for some reason.
JS client:
var ws = new SockJS('/hello');
client = Stomp.over(ws);
...
client.subscribe('jms.queue.test', function(message) {}
...
client.send("jms.queue.test", {}, "message");
Spring config (mostly useless at the moment, since i don't use /app destination):
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableStompBrokerRelay("jms.topic", "jms.queue");
config.setApplicationDestinationPrefixes("/app");
config.setPathMatcher(new AntPathMatcher("."));
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/hello").withSockJS();
}
}
Spring Boot App:
#Component
public class BusinessLogic implements CommandLineRunner {
#Autowired
hulloController controller;
#Override
public void run(String... args) throws Exception {
stuff(args);
}
public void stuff(String[] args) throws InterruptedException {
Thread.sleep(20000);//sleep while spring boot fully initializes
controller.waitForGreet();
}
}
Controller (not a real #Controller, i don't plan to use MVC):
#Component
public class hulloController {
private SimpMessagingTemplate template;
#Autowired
StompSessionHandler handler;
#Autowired
public hulloController(SimpMessagingTemplate template) {
this.template = template;
}
public void waitForGreet() {
System.out.println("Entered Listener");
WebSocketClient transport = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(transport);
stompClient.setMessageConverter(new StringMessageConverter());
stompClient.connect("ws://127.0.0.1:61613/stomp", handler);
}
}
And finally, the handler:
#Component
public class SessionHandler implements StompSessionHandler {
#Override
public void afterConnected(StompSession stompSession, StompHeaders stompHeaders) {
System.out.println("Connected!");
StompFrameHandler handler = new StompFrameHandler() {
#Override
public Type getPayloadType(StompHeaders stompHeaders) {
return null;
}
#Override
public void handleFrame(StompHeaders stompHeaders, Object o) {
System.out.println("received " + o.toString());
}
};
//StompSession.Subscription s = stompSession.subscribe("jms.queue.test", handler);
stompSession.send("jms.queue.test", "hello!");
}
...
}
If i comment the client.subscribe part, client.send works properly, message is received and rendered in JS, so the queue names and connection URL are fine. I also tried using SockJSClient as a Transport but it doesn't help.
When i send messages from JS, for about 1 or 2 minutes half of them isn't showing up (as it would be if the subscription would be working), then JS starts receiving 100% of the messages.
I've seen plenty of almost identical code on github today, and i just don't see what the issue might be here.
//StompSession.Subscription s = stompSession.subscribe("jms.queue.test", handler);
stompSession.send("jms.queue.test", "hello!");
The STOMP over WebSocket is a an async protocol.
You call there subscribe and got to the send. There is no guaranty that the subscription will happen already, when you start to send something to that destination.
Consider to use StompSession.Subscription and its addReceiptTask() to send messages to the destination after the confirmation for the subscription.
And you should enable StompSession.setAutoReceipt(true) to make that working with your STOMP Broker.
So, apparently switching from implementing StompSessionHandler to extending StompSessionHandlerAdapter for my handler did the trick.
I have absolutely no idea why, but i guess this passage from Spring docs for StompSessionHandler is there for a reason:
"Implementations of this interface should consider extending
StompSessionHandlerAdapter."
#Override
public Type getPayloadType(StompHeaders stompHeaders) {
return null;
}
because of this line: you supply unhandled converter - it throws exception. But it's not logged or anything. check out the code and paste proper Type instead of null