Spring ResponseBodyAdvice that can work with ResponseEntity - spring

I really like Spring's ResponseBodyAdvice, it's a feature I only recently discovered. Here is a simple one that I wrote:
#ControllerAdvice
class ClientResponseAdvice : ResponseBodyAdvice<Client> {
override fun supports(method: MethodParameter, clazz: Class<out HttpMessageConverter<*>>): Boolean {
return method.parameterType == Client::class.java
}
override fun beforeBodyWrite(client: Client?, param: MethodParameter, mediaType: MediaType, clazz: Class<out HttpMessageConverter<*>>, req: ServerHttpRequest, res: ServerHttpResponse): Client? {
return client?.copy(clientSecret = "")
}
}
In this case, I'm using the advice to remove the clientSecret from the response payload, so no response can be sent out with this sensitive value. The problem is that in my controllers, sometimes I want to return a ResponseEntity<Client> instead of a Client directly (different status codes/headers/etc). When I do this, the ResponseBodyAdvice fails to properly detect the Client class, and this doesn't execute.
I've been exploring every idea I can come up with on my own to fix this and get it working again. I've so far hit a dead end.

Related

Spring webflux Multiple Exception handler in functional Endpoint

im working ins Spring web flux project and I used functional endpoints instead of controller annotation but I didn't find a solution to handle multiple exceptions for the same endpoint , this is my code :
#Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.GET("/router/users/{id}"),this::renderException);
}
private Mono<ServerResponse> renderException(ServerRequest request) {
Map<String, Object> error = this.getErrorAttributes(request, ErrorAttributeOptions.defaults());
error.remove("status");
error.remove("requestId");
return ServerResponse.status(HttpStatus.BAD_REQUEST).contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(error));
}
for the endpoint /router/users/{id} i trigger UserNotFoundException and UserException and I want to return 404 for UserNotFoundException and 500 for UserException but I don't know how to do that in the functional endpoint. anyone can guide me on how to do this in the correct way like we did in using #ExceptionHandler in rest controller?
If returning proper code is all you care about then adding #ResponseStatus for your custom exceptions might be the best solution.
#ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
// more methods...
public UserNotFoundException(final String message) {
super(message);
}
}
But if you want to build ServerResponse by yourself, make use of project reactor operators, like switchIfEmpty() or onErrorMap(), etc. which could be used as following
Mono<ServerResponse> response() {
return exampleService.getUser()
.flatMap(user -> ServerResponse.ok().body(user, User.class))
.switchIfEmpty(ServerResponse.notFound().build());
}
You might want to take a look at docs Which operator do I need? - handling errors

Is there a way to pass the request parameter to the /health endpoint in webflux?

I am writing a custom REACTIVE Health Indicator (i.e. by implementing ReactiveHealthIndicator) to check the connectivity to another app. However to check this I require some request parameter in the request URL of /health endpoint.(e.g. URL that i want is "/health?countryCode=IN") is there a way to achieve this? Please note that as it is reactive, autowiring HttpServletRequest won't work.
#Component
public class CustomServicesHealthIndicator implements ReactiveHealthIndicator {
#Override
public Mono<Health> health() {
//#TODO : This will be called like localhost:8080/acuator/health?param=value.
// Need to read value of 'param' here and take some actions accordingly
return doHealthCheck()
.onErrorResume(ex -> Mono.just(new Health.Builder().down(ex).build()));
}
private Mono<Health> doHealthCheck(){
return Mono.just(new Health.Builder().up().build());
}
}
Documentation : https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#reactive-health-indicators

Throw exception spring boot webflux

I am trying to make a condition, which if not satisfied, throw an exception. But I tried in many ways and without success.
My restcontroller:
#GetMapping(value = ["/{id}"])
fun find(#PathVariable id: String): Mono<ResponseEntity<Mono<Person>>> {
return ResponseEntity.ok().body(service.find(id)).toMono()
}
My service
override fun find(id: String): Mono<Person> {
return repository.findById(id).doOnError { throw DataNotFound("Person not found")}
}
If I enter an existing ID, it returns me a registered person.
But if I enter a nonexistent ID, instead of throwing the exception, it returns me a 200 with empty body.
How do I solve this? Could anyone help?
Try this:
#GetMapping(value = ["/{id}"])
fun find(#PathVariable id: String): Mono<ResponseEntity<?>> {
service.find(id).map(person -> ResponseEntity.ok().body(person))
.onErrorResume(DataNotFound.class, exception -> ResponseEntity.notFound())
}
fun find(id: String): Mono<Person> {
repository.findById(id).onErrorMap(error -> new DataNotFound("Person not found"))
}
It returns OK response if a person exists and NOT_FOUND otherwise.
Usually, in a more complex scenario, you want to do a translation from exceptions to some kind of error resource.
In this scenario, you will use the same response type for your method which handles the HTTP request. More precisely, in your case:
#GetMapping(value = ["/{id}"])
fun find(#PathVariable id: String): Mono<ResponseEntity<Mono<Person>>> {
return ResponseEntity.ok().body(service.find(id)).toMono()
}
(this will remain as it is!)
And next, you will provide a so-called ControllerAdvice which can look in your case like in the following snippet:
#ControllerAdvice(assignableTypes = [YourRestController::class]
class ControllerAdvice {
#ExceptionHandler
fun handle(ex: DataNotFoundException): ResponseEntity<DataNotFoundErrorResource> {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(DataNotFoundErrorResource.from(ex)
}
}
Note: Be aware that I've not used a Kotlin compiler, but I've compiled it in my mind :) I hope that it will be ok!

How do I get the HTTP status code of a given URL via Spring?

I am working in a Spring #Component class and I am trying to get the HTTP status code of a particular URL for further processing. I have a function as follows:
fun getStatus() : String
{
val webClient = WebClient.create("https://stackoverflow.com")
val result = webClient.get()
.exchange().map { res -> res.rawStatusCode() }
println(result)
return "statusGotten"
}
However, rather than getting the Int value of the status code (e.g. 200, or 401), I am simply getting: "MonoMap".
I am new to both Spring and Web Programming in general, so I'm a little confused how to proceed from here. I'm aware that "result" is being returned as a "Mono", but I'm less clear about what a "Mono" is, or how I might transform it into something with more scrutable properties, as even looking at "result" in the debugger doesn't shed any light as to whether the HTTP request was actually sent or was successful:
Am I calling the webclient incorrectly? Or merely failing to parse the resultant data in a meaningful way? Any suggestions on how or where I might learn more about the underlying topics would be much appreciated as well.
If you need a blocking way to do this is easy just
#Test
public void myTest(){
WebClient client = WebClient.builder().baseUrl("https://stackoverflow.com/").build();
ClientResponse resp = client
.get()
.uri("questions/")
.exchange()
.block();
System.out.println("Status code response is: "+resp.statusCode());
}
But for this you can use directly the RestTemplate instead the webclient... the recomended way to do this is non blocking what means you should return a Mono with the status and consume outside your method like for example:
public Mono<HttpStatus> myMethod(){
WebClient client = WebClient.builder().baseUrl("https://stackoverflow.com/").build();
return client
.get()
.uri("questions/")
.exchange()
.map( clientResp -> clientResp.statusCode());
}
The way of consume this Mono depends of your code...

Spring MVC Controller method mapping using form body

I'm building a small application to serve as a client for some third party library here at work. The API states that a Webhookis needed to respond some asynchronous events, but all their methods have the very same signature, apart from a changing _method field between the calls. For example, I have a _method = ping, media, etc.
I'd like to have separate methods on my controller to respond for each one of these methods. If the app allowed me to specify different URLs for each method it would be easy to use Spring MVC's #RequestMapping for each one of them. But I have to specify a single endpoint to receive all calls.
Is there a way (for example using Spring's HttpMessageConverter or something like that) to map different controller methods based on what the Request Body is? I've already tried with #RequestBody, #RequestParam but didn't seem to find anything.
I really, really didn't want to use a bunch of case, switch methods on a front controller to dispatch actions based on my _method field that comes with my POST data, so I happen to believe someone had this problem before and solved it intelligently.
Thanks a lot!
Edit 1: Providing source code
#Controller
#RequestMapping("/webhooks")
public class WebhookController {
#RequestMapping(method = RequestMethod.POST, params = {"_method=ping"})
#ResponseBody
public String ping(){
return "pong";
}
#RequestMapping(method = RequestMethod.POST, params = {"_method=media"})
#ResponseBody
public String media(){
return "media";
}
}
This is the answer:
{
"timestamp": 1440875190389,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.web.bind.UnsatisfiedServletRequestParameterException",
"message": "Parameter conditions \"_method=ping\" not met for actual request parameters: ",
"path": "/webhooks"
}
Right, I got it working. The answer is a bit tricky so I wanted to register it here should anyone have such problem.
#Neil McGuigan pointed me on the right direction on his comment but I didn't pay attention at first. The main culprit here is a very, very, very bad API design on our remote application's side.
_method is a field used to specify non-standard HTTP verbs such as PUT, PATCH, DELETE, TRACE and so on. This field is filtered by HiddenHttpMethodFilter and the HttpServletRequest is wrapped with this 'new' method. You can see at the file's source how it works.
As I wanted this _method field to get thru the filter without modifying the whole request (and causing the errors because there's no such verb as pingor message on `RequestMethod) I firstly had to deactivate the filter. This could be done by two ways:
I could stop Spring Boot from automagically configuring Spring MVC, skipping WebMvcAutoConfiguration from being loaded when the ApplicationContext was loaded. As you can imagine this is a BIG, BIG, BIIIIG NO because, well, things could happen.
I could use a FilterRegistrationBean to disable the bad filter. Pretty simple and straightforward, this was the method I chose to use:
#Bean
public FilterRegistrationBean registration(HiddenHttpMethodFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
Last but not least, I decided to give HiddenHttpMethodFilter a little extension to somehow improve how the requests were getting thru. The Java EE Spec is pretty clear on the Servlet Spec Commandments where it states:
Thou should not alter your request on your side. You must respect the sender (something like that)
Though I agree with this, for the sake of my mental stability I decided to alter it anyway. To achieve this, we can use a simple HttpServletRequestWrapper, override the chosen methods and filter the original request with the wrapped part. I ended up doing something like this:
public class WhatoolsHiddenHttpMethodFilter extends OrderedHiddenHttpMethodFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String paramValue = request.getParameter(OrderedHiddenHttpMethodFilter.DEFAULT_METHOD_PARAM);
if("POST".equals(request.getMethod()) && StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
List<String> whatoolsMethods = Arrays.asList("ping", "message", "carbon", "media", "media_carbon", "ack");
if(whatoolsMethods.contains(paramValue)){
WhatoolsHiddenHttpMethodFilter.HttpMethodRequestWrapper wrapper = new WhatoolsHiddenHttpMethodFilter
.HttpMethodRequestWrapper(request, "POST", paramValue);
filterChain.doFilter(wrapper, response);
} else {
WhatoolsHiddenHttpMethodFilter.HttpMethodRequestWrapper wrapper = new WhatoolsHiddenHttpMethodFilter
.HttpMethodRequestWrapper(request, method, null);
filterChain.doFilter(wrapper, response);
}
} else {
filterChain.doFilter(request, response);
}
}
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
private final String whatoolsMethod;
public HttpMethodRequestWrapper(HttpServletRequest request, String method, String whatoolsMethod) {
super(request);
this.method = method;
this.whatoolsMethod = whatoolsMethod;
}
#Override
public String getMethod() {
return this.method;
}
#Override
public String getHeader(String name) {
if("x-whatools-method".equals(name)){
return this.whatoolsMethod;
}
return super.getHeader(name);
}
#Override
public Enumeration<String> getHeaderNames() {
List<String> names = Collections.list(super.getHeaderNames());
if(this.whatoolsMethod != null){
names.add("x-whatools-method");
}
return Collections.enumeration(names);
}
}
}
So, what this does is to wrap the request with a new x-whatools-method header when the header is in my whatoolsMethods list. With this, I can easily use #RequestMapping's headers property and map the requests to the correct controller methdods.
Back to the initial question, I'm almost sure (well, 99,95% should be completely sure but let's not risk it) the params property on #RequestMapping works only for request parameters on GET URIs, e.g http://foo.bar/?baz=42. It won't work filtering parameters sent on the request's body.
Thanks Neil for your guidance, even if small! I hope this helps someone.
You can use params in a request mapping:
#RequestMapping(value="/foo", params={"_method=ping"})
Assuming these are post parameters that is
params DOES work for POST, I promise you
Here's my controller:
#Controller
#RequestMapping("/test1")
public class ParamTestController {
#RequestMapping(method = RequestMethod.POST)
#ResponseBody String getA(){
return "A";
}
#RequestMapping(method = RequestMethod.POST, params = {"b"})
#ResponseBody String getB(){
return "B";
}
}
Here's my test:

Resources