Returning value from Apache Camel route to Spring Boot controller - spring-boot

I am calling a camel route from a Spring Boot controller. The camel route calls a REST service which returns a string value and I am trying to return that value from the camel route to the controller. Below is the Spring Boot controller:
#RestController
#RequestMapping("/demo/client")
public class DemoClientController {
#Autowired private ProducerTemplate template;
#GetMapping("/sayHello")
public String sayHello() throws Exception {
String response = template.requestBody("direct:sayHelloGet", null, String.class);
return response;
}
}
And below is my camel route:
#Component
public class MyRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("direct:sayHelloGet")
.log("Route reached")
.setHeader(Exchange.HTTP_METHOD, simple("GET"))
.to("http://localhost:8080/demo/sayHello")
.log("${body}");
}
}
In the route, the log is printing the return value from the REST service but that String is not returned to the controller. Can anyone please suggest how to return the value to the Spring Boot controller?
The Spring Boot version I am using is 2.2.5 and Apache Camel version is 3.0.1.

See this FAQ
https://camel.apache.org/manual/latest/faq/why-is-my-message-body-empty.html
The response from http is streaming based and therefore only readable once, and then its read via the log and "empty" as the response. So either
do not log
enable stream caching
convert the response from http to a string (not streaming and re-readable safe)

Related

Log raw request body before deserialization in Spring Boot REST controller

Given the following, kind of basic, REST controller in Spring Boot:
#RestController
public class NotificationController {
#PostMapping(path = "/api/notification")
public void handleNotification(#RequestBody #Valid NotificationRequest request) {
System.out.println(request.getMessage());
}
}
One requirement is to log the incoming request body before deserializing it to a NotificationRequest. We'd like to have a trace, e.g. if the request is not well-formed. My first idea was to use the HttpServletRequest directly but then I would lose the validation and automatic deserialization.
public void handleNotification(HttpServletRequest httpRequest) {
// ...
}
Is there some mechanism to log the incoming "raw" request body for this particular endpoint?

Intercepting Camel #Consume

I have an existing application which is using Apache Camel to send messages to SEDA endpoints for Async processing and would like to intercept calls to these methods for instrumentation.
Example code:
#Component
public class CamelMessageService {
private static final Logger log = LoggerFactory.getLogger(CamelMessageService.class);
public static final String QUEUE = "seda:message";
#Resource
private ProducerTemplate producerTemplate;
public void send() {
producerTemplate.sendBody(QUEUE, "Hello World");
}
#Consume(uri = QUEUE)
public void receive(#Body String payload) {
log.info("Received message {}", payload);
}
}
Is there a way to intercept all methods annotated with #Consume before invoking. I looked at an AOP based approach but this seemed to fall over due to existing Spring/Camel proxying of these classes.
I have also tried using various Camel Intercept routes and adding a custom InterceptStrategy but it seems that the example above does not create a Camel route so is not intercepted.
EDIT: On further investigation in seems that these endpoints can be Intercepted using camel but only if there is at least 1 other route defined in the Camel Context?
#Component
class MyRouteBuilder extends RouteBuilder {
private static final Logger log = LoggerFactory.getLogger(MyRouteBuilder.class);
public void configure() {
interceptSendToEndpoint(CamelMessageService.QUEUE)
.process(exchange -> log.info("intercepted exchange {}", exchange));
from("timer:hello?period={{timer.period}}").routeId("hello").routeGroup("hello-group")
.transform().simple("yo")
.filter(simple("${body} contains 'foo'"))
.to("log:foo")
.end()
.to("stream:out");
}
}
If I run this app with the Route Builder above then my interceptor is triggered if however I comment out the hello route it is not?
Any help would be greatly appreciated.

MediaTypeNotAcceptable with SpringBoot RestController

I have a rest controller with a GetMapping that produces media type "Plain_text". When an exception occurs in the underlying service, it will be handled by the controller advice and the controller advice returns an object that will be serialized to JSON.
In the happy path, where the service doesn't throw any exception, I'm getting a correct response. But in case of error scenarios, I'm getting an exception with error "Could not find acceptable representation". If I removed the produces tag, the controller is working fine.
Is there a way in spring boot to let an api return plain text media type and in case of errors, return a Json response?
Here is my code:
#RestController
#RequestMapping("/sample")
public class SampleController() {
#Autowired
SampleService service;
#GetMapping(produces = MediaType.TEXT_PLAIN)
public String getString(){
return service.getString();
}
}
ControllerAdvice:
#RestControllerAdvice
public class SampleControllerAdvice(){
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler({SampleNotFoundException.class})
public SampleErrorResponse handleException(Exception ex) {
return new SampleErrorResponse(e.getMessage());
}
}
This looks related to SPR-16318, which has been fixed in Spring Framework 5.1 - this is the version used in Spring Boot 2.1.
You should upgrade to Spring Boot 2.1+ to get that fix in your application.

Throwing Custom Runtime exception in Spring Cloud Gateway Filters

We are using Spring Cloud Gateway with Spring Boot 2 and reactive WebFlux module.
There is an authentication filter which is added for one of the routes. Now if we throw a RuntimeException with a particular status code, it is really not picking up.
Earlier this authentication check was part of the HandlerInterceptor in Spring, but now we cannot use the web module along with WebFlux (conflict from Spring cloud gateway).
Example:
#Override
public GatewayFilter apply(Object config) {
ServerHttpRequest httpRequest = exchange.getRequest();
if(!someUtil.validRequest(httpRequest) {
throw new RuntimeException("Throw 401 Unauthorized with Custom error code and message");
}
}
Currently, the actual response always gives a 500 internal server error. From where is this coming from? Can we get hold of the errors from Filters here?
You can implement a custom error handler, and here is the Spring Boot document.
Or you can simply throw a ResponseStatusException. The default error handler will render the specific status.
Keep in mind, as of the time of writing, spring-cloud-gateway uses Spring Framework WebFlux. This means that the approach would be different. You can get hold of the exception in a filter as shown below.
Declare an exception like this:
public class UnauthorisedException extends ResponseStatusException {
public UnauthorisedException(HttpStatusCode status) {
super(status);
}
public UnauthorisedException(HttpStatusCode status, String reason) {
super(status, reason);
}
}
NB: The exception extends ResponseStatusException.
The ControllerAdvice class can be implemented as follows:
#ControllerAdvice
public class MyErrorWebExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(UnauthorisedException.class)
public Mono<ServerResponse> handleIllegalState(ServerWebExchange exchange, UnauthorisedException exc) {
exchange.getAttributes().putIfAbsent(ErrorAttributes.ERROR_ATTRIBUTE, exc);
return ServerResponse.from(ErrorResponse.builder(exc,HttpStatus.FORBIDDEN,exc.getMessage()).build());
}
}
In your filter you can now implement the apply method as follows:
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
if (request.getHeaders().get("token") == null){ //test is an example
throw new UnauthorisedException(HttpStatus.FORBIDDEN, "Not Authorised from Gateway");
}
ServerHttpRequest.Builder builder = request.mutate();
return chain.filter(exchange.mutate().request(builder.build()).build());
};
}

How to expose both a SOAP web-service and RESTful API at the same time in Spring Boot?

In Spring Boot 1.4.3 I exposed a SOAP web service endpoint which works successfully on port 8080.
For the purpose of running a health check I also need to expose a RESTful API. I tried both using Actuator and a rest controller:
#RestController
public class RESTapis {
#RequestMapping(method = {RequestMethod.GET, RequestMethod.POST}, value = "/health")
public String healthCheck() {
return "ACK";
}
}
but in both cases I get the same response: HTTP 405 (method not allowed).
The REST api returns HTTP 200 if I disable the web-service.
How can I have both the SOAP web-service and REST working at the same time?
Here is the web-service configuration:
#EnableWs
#Configuration
public class WebServiceConfig extends WsConfigurerAdapter {
#Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/*");
}
}
So going off the messageDispatcherServlet method it looks like you are binding your servlet to all the incoming request due to the wildcard registration:
return new ServletRegistrationBean(servlet, "/*");
Hence the MessageDispatcher is intercepting all of your incoming requests and trying to find the /health and throwing http 405.
Fix:
return new ServletRegistrationBean(servlet, "/soap-api/*");
Explanation:
By binding the Message Dispatcher to a specific URI namespace we can ensure that all the request fired on the /soap-api/* namespace ONLY will be intercepted by the MessageDispatcher. And all the other requests will be intercepted by the DispatcherServlet making it possible to run a Rest interface in parallel to your Soap WS.
Suggestion:
Not knowing the reasons / specifics of the app, but going off the name of the method healthcheck(), you can look at using spring boot actuator to generate health checks, metrics for you app. You can also override it for customisations.
Reference for actuator: https://spring.io/guides/gs/actuator-service/

Resources