Invoke Spring Controller from Spring Integration flow - java

Hi I have a little problem. I want to invoke spring controller manually but I have an exception. Firstly, let me show you some integration flow and controller:
#Bean
public IntegrationFlow flow() {
return IntegrationFlows.from(
Amqp.inboundAdapter(rabbitMqConfig.connectionFactory(), queue)
.acknowledgeMode(AcknowledgeMode.MANUAL)
.errorChannel("errorChannel")
.concurrentConsumers(2)
.maxConcurrentConsumers(3))
.transform(Transformers.fromJson(Event.class))
.transform(new EventToRequestTransformer())
.handle(Request.class, (request, headers) -> controller.trigger(request))
.<ResponseEntity, HttpStatus>transform(ResponseEntity::getStatusCode)
.routeToRecipients(some routing)
.get();
}
#Controller
public class SomeController {
#RequestMapping(value = "/trigger", method = RequestMethod.POST)
public ResponseEntity<Response> trigger(#RequestBody Request request)
{
//some logic
}
}
When I'm running my app and sending an event I am getting exception on line:
.handle(Request.class, (request, headers) -> controller.trigger(request))
Exception:
nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet
Could someone please tell me what is wrong and how to fix that? I thought I can just invoke controller method like it was coming from simple POJO.

You are mixing concerns and try to call Web tier from the service layer.
If the logic is like that, then design of the app is wrong.
You should extract some service from the controller logic and call it from the Web, as well as from there on the Integration level.
According your stack trace it looks like you try to get access to the request scope object. Well, and that is exactly what happens to #Controller beans, I guess.

Related

How do I not lose my RequestAttributes on new thread?

Using Spring 4, in a simple POST API, I am attempting to immediately return a response and continue some more processing in the background.
#RestController
#RequestMapping("/somewhere")
public class SomeController {
SomeService someService;
#PostMapping(value = "/something")
public ResponseModel getResponseAndDoOtherProcessing() {
ResponseModel response = someService.getInitialResponse();
// Async function
someService.doOtherProcessing(response);
return response;
}
}
The first function getInitialResponse() would return an object to be sent back to the client but I want to do more processing using the response I sent.
So I define an Async function for my controller to call on another thread as to not wait on it for sending the response to the client (#EnableAsync is on a configurable file):
#Async
#Override
public void doOtherProcessing(ResponseModel) {
// do some stuff
...
// get the request attributes for some other pieces
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
...
}
But since this on another thread, it looks to lose the request attributes:
java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
I have searched a few things such as using MODE_INHERITABLETHREADLOCAL and read some debugging pieces such as this but haven't had any luck.
How do I keep the scope of my request to do other processing? Can anyone point me in the right direction?

HTTP Response Exception Handling in Spring 5 Reactive

I'm developing some reactive microservices using Spring Boot 2 and Spring 5 with WebFlux reactive starter.
I'm facing the following problem: I want to handle all HTTP Statuses that I receive from calling another REST Services and throws an exception when I receive some bad HTTP Status. For example, when I call an endpoint and I receive an 404 HTTP Status, I want to throw an exception and that exception to be handled in some ExceptionHandler class, just like the way it was in Spring 4 with #ControllerAdvice.
What is the right way to do this? Hope to receive some good suggestions.
This can be addressed in two independent parts.
How to convert HTTP 404 responses received by WebClient into custom exceptions
When using WebClient, you can receive HTTP 404 responses from remote services. By default, all 4xx and 5xx client responses will be turned into WebClientResponseException. So you can directly handle those exceptions in your WebFlux app.
If you'd like to turn only 404 responses into custom exceptions, you can do the following:
WebClient webClient = //...
webClient.get().uri("/persons/1")
.retrieve()
.onStatus(httpStatus -> HttpStatus.NOT_FOUND.equals(httpStatus),
clientResponse -> Mono.error(new MyCustomException()))
.bodyToMono(...);
This is obviously done on a per client call basis.
You can achieve the same in a more reusable way with an ExchangeFilterFunction that you can set once and for all on a WebClient instance like this:
WebClient.builder().filter(myExchangeFilterFunction)...
How to handle custom exceptions in WebFlux apps
With Spring WebFlux with annotations, you can handle exceptions with methods annotated with #ExceptionHandler (see Spring Framework reference documentation).
Note: using a WebExceptionHandler is possible, but it's quite low level as you'll have no high-level support there: you'll need to manually write the response with buffers without any support for serialization.
I think what you are looking for is WebFluxResponseStatusExceptionHandler the check this for reference.
In the WebHandler API, a WebExceptionHandler can be used to to handle
exceptions from the chain of WebFilter's and the target WebHandler.
When using the WebFlux Config, registering a WebExceptionHandler is as
simple as declaring it as a Spring bean, and optionally expressing
precedence via #Order on the bean declaration or by implementing
Ordered.
This example may help, have not tried it myself.
#Component
#Order(-2)
class RestWebExceptionHandler implements WebExceptionHandler{
#Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
if (ex instanceof PostNotFoundException) {
exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
// marks the response as complete and forbids writing to it
return exchange.getResponse().setComplete();
}
return Mono.error(ex);
}
}
class PostNotFoundException extends RuntimeException {
PostNotFoundException(String id) {
super("Post:" + id + " is not found.");
}
}

Spring Non-blocking Rest "Send and forget"

I'm writing a non-blocking Spring Rest controller. My client should send a request and doesn't care for the response and doesn't need to wait.
This is my server code:
#RestController
#EnableAsync
public class testController {
#RequestMapping(value = "test", method = RequestMethod.GET)
public ResponseEntity<String> test() throws InterruptedException {
timeConsumingMethod();
System.out.println("I'm should be first");
return new ResponseEntity<String>("the server is processing your request", HttpStatus.OK);
}
#Async
private void timeConsumingMethod() throws InterruptedException {
Thread.sleep(1000*5);
System.out.println("I'm should be second!");
}
However, When I call http://localhost:8181/test using(POSTMAN, Chrome, etc...)
I get the following on the server log:
I'm should be second!
I'm should be first
AND only after waiting 5 seconds my browser shows:
the server is processing your request
Is that the correct way for a "send and forget" Behavior?
According to the doc page the #EnableAsync should be added on configuration class.
Enables Spring's asynchronous method execution capability, similar to
functionality found in Spring's XML namespace.
To be used on #Configuration classes as follows, where MyAsyncBean is
a user-defined type with one or more methods annotated with either
Spring's #Async annotation, the EJB 3.1 #javax.ejb.Asynchronous
annotation, or any custom annotation specified via the annotation()
attribute.
why don't you use this:
https://www.baeldung.com/spring-webclient-resttemplate
Webflux client seems to do the same. I was searching for a similar solution where 1 microservice calls multiple microservices async and this fits the model

spring RESTful service work fine, but throws an exception

I am creating a spring RESTful service, and it is working. My client send an object into a put request and my service receive this object perfectly, but after this, my client receive this exception: "org.springframework.web.client.HttpClientErrorException: 404 Not Found"
This is my client code:
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
Greeting greeting = new Greeting(21l, FileUtils.toByteArray("src/main/resources/test.png"));
String url = "http://localhost:8080/DolphinRestServer/bulletin/put";
restTemplate.put(url,greeting);
And this is my server code:
#Service
#RequestMapping("/bulletin")
public class BulletinService {
#RequestMapping(method = RequestMethod.PUT, value = "/put")
public void put(#RequestBody Greeting greeting){
System.out.println("it's work fine and my greeting is here --------->"+greeting);
}
}
When tested, i get those messages:
-server side:
-Client side:
Your put method has a void return type. Additionally, it does not take the HttpServletResponse as a parameter. According to the Spring MVC documentation, when such a situation arises, Spring MVC uses the URL as the view name and attempts to render it. In your case, Spring MVC is attempting to find and load a view called /bulletin/put, which it can't find and hence the 404 message.
As you can see it is making another request at 20:40:34:085 DolphinRestServer/bulletin/bulletin/put in the server side giving an error page not found.
And it is because you are calling BulletinService twice.
You defined as a bean and it has an annotation. Make sure you are only loading/scanning this package once..
Remove #service annotation on BulletinService

NotSerializableException escaping Spring Controller and causing problems with Google App Engine Queue

I have a Spring Controller that is being invoked via an HTTP POST from the GAE Queue Scheduler.
#Controller
#RequestMapping(value = RSSPoller.RSS_POLLER_URL)
public class RSSPoller implements Serializable {
private static final long serialVersionUID = -4925178778477404709L;
public static final String RSS_POLLER_URL = "/rsspoller";
#RequestMapping(method = RequestMethod.POST)
#ResponseStatus(HttpStatus.OK)
public void pollAndProcessRssFeed() throws ServiceException {
try {
// do some stuff
}
catch(Exception e) {
throw new ServiceException("Can't process RSS feed because", e);
}
}
}
However when it gets invoked, the response code is 500 with a Critical log message of
Uncaught exception from servlet
java.lang.RuntimeException: java.io.NotSerializableException: <some spring class that changes all the time, but does not implement java.io.Serializable>
The same log message shows up in the logs with a Warning level as well.
I get similar warning messages (but not critical) in my logs when I invoke other Spring Controllers that either render a web page (GET), or returns some XML data (essentially RPC invokes which use HTTP POST). When I do an HTTP GET/POST to those URLs, the response code is 200 and the output is correct (and I ignore the warning message in the logs).
That leads me to two questions:
Why do I get the Critical error message/HTTP 500 for the POST from the queue, but not the GET/POST to other Spring Controllers in my app?
How can I trap the exception and essentially discard it; as to my purposes the task is complete.
I can post the full exception log if it's of use; for brevity I've omitted it.
you should make your <some spring class that changes all the time, but does not implement java.io.Serializable> just Serializable (not only Controller). Me at least helped it.

Categories