Redirection inside reactive Spring Webflux REST controller - java

I'm creating simple controller server for spring reactive project. While setting redirection to another location, I have found an error when calling http://localhost:8080/:
There was an unexpected error (type=Internal Server Error, status=500).
ModelAttributeMethodArgumentResolver does not support multi-value reactive type wrapper: interface reactor.netty.http.server.HttpServerResponse
java.lang.IllegalStateException: ModelAttributeMethodArgumentResolver does not support multi-value reactive type wrapper: interface reactor.netty.http.server.HttpServerResponse
at org.springframework.util.Assert.state(Assert.java:94)
at org.springframework.web.reactive.result.method.annotation.ModelAttributeMethodArgumentResolver.resolveArgument(ModelAttributeMethodArgumentResolver.java:112)
at org.springframework.web.reactive.result.method.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:123)
at org.springframework.web.reactive.result.method.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:190)
at org.springframework.web.reactive.result.method.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:133)
at org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter.lambda$handle$1(RequestMappingHandlerAdapter.java:200)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44)
...
This is the controller code:
import reactor.core.publisher.Mono;
import reactor.netty.http.server.HttpServerResponse;
#RestController
public class BaseController {
#GetMapping("/")
public Mono<Void> indexController(HttpServerResponse response) {
return response.sendRedirect("/api/v1");
}
// ...
}
I expected it to be redirected from localhost:8080/ to localhost:8080/api/v1. But I've got the above exception.

Redirecting with Controller MVC good-old approach:
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import java.net.URI;
#RestController
public class Controller {
#GetMapping("/")
public Mono<Void> indexController(ServerHttpResponse response) {
response.setStatusCode(HttpStatus.PERMANENT_REDIRECT);
response.getHeaders().setLocation(URI.create("/api/v1"));
return response.setComplete();
}
}
If someone would prefer functional Router/Handler approach might investigate: How to redirect a request in spring webflux?.
#Bean
RouterFunction<ServerResponse> routerFunction() {
return route(GET("/"), req ->
ServerResponse.temporaryRedirect(URI.create("/api/v1"))
.build());
}

When using #RestController a ResponseEntity can work as well (here implemented using kotlin coroutines) :
#RestController
class SomeController() {
suspend fun someMethod() : ResponseEntity<Unit> {
return ResponseEntity
.status(HttpStatus.TEMPORARY_REDIRECT)
.location(URI.create("/api/v1"))
.build()
}
}

Related

How can I return validation errors in a RFC-7807 format in Spring Boot Data Rest controllers?

I have a Spring repository
#RepositoryRestResource
public interface MongoIntegrationTokenRepository extends MongoRepository<IntegrationToken, String>, CrudRepository<IntegrationToken, String> {}
I've added the validation configuration to add validation support and my entity has the validation annotations:
#Configuration
class MyRestMvcConfiguration implements RepositoryRestConfigurer {
private final LocalValidatorFactoryBean localValidatorFactoryBean;
#Autowired
public MyRestMvcConfiguration(LocalValidatorFactoryBean localValidatorFactoryBean) {
this.localValidatorFactoryBean = localValidatorFactoryBean;
}
#Override
public void configureValidatingRepositoryEventListener(
ValidatingRepositoryEventListener validatingRepositoryEventListener) {
validatingRepositoryEventListener.addValidator("beforeCreate", localValidatorFactoryBean);
validatingRepositoryEventListener.addValidator("beforeSave", localValidatorFactoryBean);
validatingRepositoryEventListener.addValidator("beforeSave", localValidatorFactoryBean);
}
}
When running the create operation, if there are any validation errors, the entity creation fails but the JSON response doesn't contain any errors details.
A POST to my endpoint with invalid data simply returns:
{
"message": "Server Error",
"details": [
"Validation failed"
]
}
I'd like to return the validation errors in the RFC7807 format. This should be possible via Spring HATEOAS or by this popular library by Zalando https://github.com/zalando/problem-spring-web but I'm unsure how they need to be wired in or which approach should be used.
I found this lone example on Github. https://github.com/marciovmartins/problem-spring-web-starter-expanded/blob/aed5825c958fad93f4aaad022689958926cf3b4a/src/main/kotlin/com/github/marciovmartins/problem/spring/web/expanded/ProblemExceptionHandler.kt and rewrote it in Java. This seems to do it.
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.data.rest.core.RepositoryConstraintViolationException;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.NativeWebRequest;
import org.zalando.problem.Problem;
import org.zalando.problem.ThrowableProblem;
import org.zalando.problem.spring.web.advice.ProblemHandling;
import org.zalando.problem.violations.Violation;
#ControllerAdvice
public class ProblemControllerAdvice implements ProblemHandling {
#ExceptionHandler
public ResponseEntity<Problem> handleRepositoryConstraintViolationException(RepositoryConstraintViolationException exception, NativeWebRequest request) {
List<Violation> violationList = exception.getErrors().getAllErrors()
.stream()
.map(objectError -> {
if (objectError instanceof FieldError) {
return new Violation(((FieldError) objectError).getField(), objectError.getDefaultMessage());
}
return new Violation(null, objectError.getDefaultMessage());
})
.collect(Collectors.toList());
ThrowableProblem problem = Problem.builder()
.withTitle("Constraint Violation")
.withStatus(defaultConstraintViolationStatus())
.with("violations", violationList)
.build();
return create(problem, request);
}
}

ControllerAdvice not being invoked but I can still see the exception in the response. Need to handle exceptions centrally

Since I have just started off with Spring boot, I'm creating a very basic project with very basic rest controllers. The intent is to understand the basics before trying out any complicated code so the examples below are very basic. There is no complex business logic, just minimal lines of code.
I have created a basic project with a simple rest controller, it has the following annotations
#RestController
#RequestMapping("users")
public class UserController
{
In the get method I have intentionally created a null pointer exception by assigning null to a variable and then checking the length.
#GetMapping(path="/{UserId}",produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<UserViewDTO> getUser(#PathVariable String UserId)
{
String firstName = null;
int len_fname = firstName.length();
UserViewDTO responseBody= new UserViewDTO();
responseBody.setFirstname("1stname");
responseBody.setLastname("lastname");
responseBody.setEmail("email#gmail.com");
responseBody.setUserid(UserId);
return new ResponseEntity<UserViewDTO>(responseBody,HttpStatus.OK);
}
As a result of this I get the bellow message in response in Postman.
{
"timestamp": "2021-03-07T19:56:48.790+00:00",
"status": 500,
"error": "Internal Server Error",
"exception": "java.lang.NullPointerException",
"message": "No message available",
"path": "/users/1212121"
}
I wanted to do handle this with #Controller advice from a central class.
I created the following
import org.apache.catalina.WebResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
#ControllerAdvice
public class AppExceptionHandler extends ResponseEntityExceptionHandler
{
#ExceptionHandler(value= {Exception.class})
public ResponseEntity<Object> handleAnyException(Exception ex, WebRequest request)
{
return new ResponseEntity<>(
ex, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
When I put a debugger the code under the #ControllerAdvice does not even get activated.
Also here is my application.properties code
server.error.include-exception=true
server.error.include-message=always
server.error.include-binding-errors=always
What am I doing wrong over here ?
I use it without extending. You should be remove extends ResponseEntityExceptionHandler
or you must be override parent class handler method
This code is run, You can review the usage here https://github.com/cebrailinanc/spring-cloud/tree/master/product-service/src/main/java/com/cbrl/cloud/product/api
#ControllerAdvice
public class AppExceptionHandler {
#ExceptionHandler(value = {Exception.class})
public ResponseEntity<Object> handleAnyException(Exception ex) {
return new ResponseEntity(ex, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Truth be told i dont see any problem in your #ControllerAdvice .
If possible please remove the value inside the annotation as If there is just one element named value, then the name may be omitted, as in the case of #ExceptionHandler it takes an array of Throwable Class.
And also if possible and if not needed please remove WebRequest and use #ResponseBody and #ResponseStatus
#ResponseBody
#ResposneStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(Exception.class)
public String handleException(Exception e) {
return e.getMessage();
}

Which exception handler is handling MissingRequestHeaderException?

I am working on a large Spring Boot codebase in which I am trying to introduce request header validation.
This is my sample controller:
import javax.validation.constraints.Size;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#Validated
#RequestMapping("/abc/test/v1")
public class TestController {
#GetMapping("/testMethod")
public String testMethod(#RequestHeader
#Size(min = 10, message = "Not valid header") String foo) {
return foo;
}
}
And this is the #ControllerAdvice error handler:
import java.util.LinkedList;
import java.util.List;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import com.abc.test.exception.RequestParamValidationException;
#SuppressWarnings({ "unchecked", "rawtypes" })
#ControllerAdvice
#RequestMapping( produces = MediaType.APPLICATION_JSON_VALUE)
public class ValidationExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<RequestParamValidationException> handleConstraintVoilationexception(ConstraintViolationException ex, WebRequest req) {
List<String> details = new LinkedList<>();
for(ConstraintViolation<?> violation : ex.getConstraintViolations()) {
details.add(violation.getMessage());
}
RequestParamValidationException exception = new RequestParamValidationException
("Request Param Validation Failed", details);
return new ResponseEntity(exception, HttpStatus.BAD_REQUEST);
}
}
This is a minimal reproducible example of my project, but I have no other error handlers present.
Even though I should be getting a ConstraintVoilationexception, but the MissingRequestHeaderException keeps being handled by a mysterious error handler which I have not declared, as I can see from my logs:
2020-06-08 10:49:10.655 WARN 3180 --- [0.0-8080-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingRequestHeaderException: Missing request header 'foo' for method parameter of type String]
It never reaches my exception handler.
How do I find out what is this mysterious exception handler is ?
Thanks.
First you need to define order of ValidationExceptionHandler
It's done with annotation #Order(Ordered.HIGHEST_PRECEDENCE)
Second, you need to override method because MissingRequestHeaderException is subclass of ServletRequestBindingException
#Override
protected ResponseEntity<Object> handleServletRequestBindingException(
final ServletRequestBindingException ex,
final HttpHeaders headers,
final HttpStatus status,
final WebRequest request
) {
return super.handleServletRequestBindingException(ex, headers, status, request);
}
MissingRequestHeaderException is a subclass of ServletRequestBindingException.Extending ResponseEntityExceptionHandler will automatically give you various methods to handle different exceptions by default.One of the method is handleServletRequestBindingException.
So your MissingRequestHeaderException is handled by the method of ResponseEntityExceptionHandler.
If you have to verify, go to the code of ResponseEntityExceptionHandler in your IDE.
Hope this helps.

How to handle both reactive and mvc ServerHttpRequest in a same #ControllerAdvice?

I have code like this below :
MyExceptionHandler {
#ExceptionHandler(Exception.class)
public Object handleMvc(Exception ex, org.springframework.http.server.ServerHttpRequest request) {
return request.getbody;
}
#ExceptionHandler(Exception.class)
public Object handleReactive(Exception ex, org.springframework.http.server.reactive.ServerHttpRequest request) {
return request.getBody();
}
}
The code above produces IllegalStateException: Ambiguous #ExceptionHandler method mapped for Exception error.
Is there a way to handle both MVC and reactive requests in the ControllerAdvice? I'm working on a common project that works across multiple projects using different httprequest type.
**In spring boot using this**
package com.example.demo.exception;
import java.util.HashMap;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
#ControllerAdvice
public class UserCVExceptionController {
#ExceptionHandler(value = UserCVNotfoundException.class)
public ResponseEntity<Object> exceptionMsg(UserCVNotfoundException exception) {
Map<String, String> headers=new HashMap<String, String>();
headers.put("Status", "404");
headers.put("Message", "User Not found");
headers.put("User", exception.getEmail());
return new ResponseEntity<>(headers, HttpStatus.NOT_FOUND);
}
}

Jersey ExceptionMapper not working with spring boot?

I am using jersey with spring boot, and I have a exception mapper:
package org.smarter.providers;
import com.google.common.collect.ImmutableMap;
import org.smarter.exception.ApiException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import java.util.Optional;
import static org.smarter.exception.UserException.UNAUTHORIZED;
#Provider
public class ExceptionMapper implements javax.ws.rs.ext.ExceptionMapper<ApiException> {
#Override
public Response toResponse(ApiException exception) {
return Response.status(status(exception)).build();
}
private Integer status(ApiException exception) {
return Optional.ofNullable(ImmutableMap.<String, Integer>builder()
.put(UNAUTHORIZED, 401)
.build()
.get(exception.code()))
.orElse(404);
}
}
Also registered, and using debug, i can see this mapper get invoked correctly, but how ever the final response still returning 404, no matter using junit test or manual test.
Here is the test:
when()
.get("/users/current/memos/daily")
.then()
.statusCode(401);
And then registered in Jersey Configuration:
register(ExceptionMapper.class);
I am using jersey with spring boot, and don't know how to trouble shooting on this. any advice will be appreciated.
I can see this mapper gets invoked correctly, but how ever the final response still returning 404.
It may be an issue with the implementation of your status() method. Are you sure exception.code() returns 401?
By the way, do you need such level of complexity using an Optional and an ImmutableMap? You could use:
private Integer status(ApiException exception) {
if (exception.code() == 401) {
return exception.code();
} else {
return 404;
}
}

Categories

Resources