Spring MV 3.2 Exception Response Mapping - spring-mvc

Spring 3.2.15, MVC-based REST API here (not Spring Boot, sadly!). I am trying to implement an exception mapper/handler that meets the following criteria:
No matter what happens (success or error), the Spring app always returns a response entity of MyAppResponse (see below); and
In the event of processing a request successfully, return an HTTP status of 200 (typical); and
In the event of processing a request and an exception occurs, I need to control the mapping of the specific exception to a particular HTTP status code
Spring MVC framework errors (such as BlahException) must map to HTTP 422
Custom app exceptions, such as my FizzBuzzException have their own status mapping schemes:
FizzBuzzException -> HTTP 401
FooBarException -> HTTP 403
OmgException -> HTTP 404
All other exceptions, that is, non-Spring exceptions, and non-custom app exceptions (the 3 listed above), should produce an HTTP 500
Where the MyAppResponse object is:
// Groovy pseudo-code
#Canonical
class MyAppResponse {
String detail
String randomNumber
}
It appears like ResponseEntityExceptionHandler might be able to do this for me, but I'm not seeing the forest through the trees w.r.t. how it gets passed arguments. I'm hoping I can do something like:
// Groovy-pseudo code
#ControllerAdvice
class MyAppExceptionMapper extends ResponseEntityExceptionHandler {
ResponseEntity<Object> handleFizzBuzzException(FizzBuzzException fbEx, HttpHeaders headers, HttpStatus status) {
// TODO: How to reset status to 401?
status = ???
new ResponseEntity(fbEx.message, headers, status)
}
ResponseEntity<Object> handleFooBarException(FooBarException fbEx, HttpHeaders headers, HttpStatus status) {
// TODO: How to reset status to 403?
status = ???
new ResponseEntity(fbEx.message, headers, status)
}
ResponseEntity<Object> handleOmgException(OmgException omgEx, HttpHeaders headers, HttpStatus status) {
// TODO: How to reset status to 404?
status = ???
new ResponseEntity(omgEx.message, headers, status)
}
// Now map all Spring-generated exceptions to 422
ResponseEntity<Object> handleAllSpringExceptions(SpringException springEx, HttpHeaders headers, HttpStatus status) {
// TODO: How to reset status to 422?
status = ???
new ResponseEntity(springEx.message, headers, status)
}
// Everything else is a 500...
ResponseEntity<Object> handleAllOtherExceptions(Exception ex, HttpHeaders headers, HttpStatus status) {
// TODO: How to reset status to 500?
status = ???
new ResponseEntity("Whoops, something happened. Lol.", headers, status)
}
}
Any idea how I can fully implement this mapping logic and the requirement for the entity to be a MyAppResponse instance and not just a string?
Then, is annotating the class with #ControllerAdvice the only thing that I need to do to configure Spring to use it?

To reduce #bond-java-bond answer you do not need to build ResponseEntity by yourself:
Use #ResponseStatus for each handleSomeException method (e.g. #ResponseStatus(HttpStatus.UNAUTHORIZED))
Return custom MyAppResponse from those methods
But if each kind of exceptions will be processed by the same way (diffs by HTTP status only) I suggest to reduce MyAppExceptionMapper like this:
#ControllerAdvice
public class MyAppExceptionMapper {
private final Map<Class<?>, HttpStatus> map;
{
map = new HashMap<>();
map.put(FizzBuzzException.class, HttpStatus.UNAUTHORIZED);
map.put(FooBarException.class, HttpStatus.FORBIDDEN);
map.put(NoSuchRequestHandlingMethodException.class, HttpStatus.UNPROCESSABLE_ENTITY);
/* List Spring specific exceptions here as #bond-java-bond suggested */
map.put(Exception.class, HttpStatus.INTERNAL_SERVER_ERROR);
}
// Handle all exceptions
#ExceptionHandler(Exception.class)
#ResponseBody
public ResponseEntity<MyAppResponse> handleException(Exception exception) {
MyAppResponse response = new MyAppResponse();
// Fill response with details
HttpStatus status = map.get(exception.getClass());
if (status == null) {
status = map.get(Exception.class);// By default
}
return new ResponseEntity<>(response, status);
}
}
Pros:
Pretty short.
No code duplication.
Slightly more effective.
Easy to extend.
Also, you can move mapping configuration outside and inject it.
How to configure MVC Dispatcher Servlet
First of all, check if mvc-dispatcher-servlet.xml (or another contextConfigLocation from web.xml) contains:
<context:component-scan base-package="base.package"/>
<mvc:annotation-driven/>
Secondly, check if #ControllerAdvice annotated class and #Controller annotated class both belong to subpackage of base.package.
See complete examples at Exception Handling in Spring MVC or Spring MVC #ExceptionHandler Example for more details.

First the error / exception handler should not worry about the success response.
Thus the responsibility of success response should lie with controller (plain or REST controller) method(s) annotated with #RequestMapping as below
#RequestMapping(value = "/demo", method = RequestMethod.GET)
#ResponseStatus(value = HttpStatus.OK)
public MyAppResponse doSomething() { .... }
For mapping a particular HTTP response code with exception(s) simply write a #ControllerAdvice as below (no additional configuration required)
#ControllerAdvice
public class CustomExceptionHandler {
// Handle FizzBuzzException with status code as 401
#ExceptionHandler(value = FizzBuzzException.class)
#ResponseBody
public ResponseEntity<MyAppResponse> handleException(FizzBuzzException ex) {
return new ResponseEntity<MyAppResponse>(buildResponse(ex), HttpStatus.UNAUTHORIZED);
}
// Handle FooBarException with status code as 403
#ExceptionHandler(value = FooBarException.class)
#ResponseBody
public ResponseEntity<MyAppResponse> handleException(FooBarException ex) {
return new ResponseEntity<MyAppResponse>(buildResponse(ex), HttpStatus.FORBIDDEN);
}
// Handle OmgException with status code as 404
#ExceptionHandler(value = OmgException.class)
#ResponseBody
public ResponseEntity<MyAppResponse> handleException(OmgException ex) {
return new ResponseEntity<MyAppResponse>(buildResponse(ex), HttpStatus.NOT_FOUND);
}
// handle Spring MVC specific exceptions with status code 422
#ExceptionHandler(value = {NoSuchRequestHandlingMethodException.class, HttpRequestMethodNotSupportedException.class, HttpMediaTypeNotSupportedException.class,
HttpMediaTypeNotAcceptableException.class, MissingPathVariableException.class, MissingServletRequestParameterException.class, ServletRequestBindingException.class,
ConversionNotSupportedException.class, TypeMismatchException.class, HttpMessageNotReadableException.class, HttpMessageNotWritableException.class, MethodArgumentNotValidException.class,
MissingServletRequestPartException.class, BindException.class, NoHandlerFoundException.class, AsyncRequestTimeoutException.class})
#ResponseBody
public ResponseEntity<MyAppResponse> handleException(Exception ex) {
return new ResponseEntity<MyAppResponse>(buildResponse(ex), HttpStatus.UNPROCESSABLE_ENTITY);
}
// Handle rest of the exception(s) with status code as 500
#ExceptionHandler(value = Exception.class)
#ResponseBody
public ResponseEntity<MyAppResponse> handleException(Exception ex) {
return new ResponseEntity<MyAppResponse>(buildResponse(ex), HttpStatus.INTERNAL_SERVER_ERROR);
}
private MyAppResponse buildResponse(Throwable t) {
MyAppResponse response = new MyAppResponse();
// supply value to response object
return response;
}
}
Let know in comments if any further information is required.
P.S.: List of Spring MVC exception reference

Related

False positive in consumer tests for valid and invalid reques(matching multiple contracts)

We have the case where we have two different outcomes on the producer side depending on a request one is for success and on which trows error message.
A simplified sample with two contracts:
1)Contract.make {
request {
method PUT()
urlPath("/sample")
headers {
contentType('application/json')
}
body("{\"acc\": \"1234A\" ,\"case\":\"abc23\",\"re\":2018/12/12}")
}
response {
status BAD_REQUEST()
}
}
2)
Contract.make {
request {
method PUT()
urlPath("/sample")
headers {
contentType('application/json')
}
body("{\"acc\": \"1234\" ,\"case\":\"abc23\",\"re\":2018/12/12}")
}
response {
status 200
}
}
On the consumer side it is able to match both request where as , when i run the invalid request test case it is throwing org.springframework.web.client.HttpClientErrorException$BadRequest: 400 Bad Request
But the for both scenarios i can able to see request and coresponding response, i can able to see in the logs
Can any body help me in this?
Thanks
These are my consumer test cases
1) its sucess request scenauro its working fine it is getting 200
enter code here
#Test
public void should_update_case_sucess() throws Exception {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Content-Type", "application/json");
ResponseEntity<String> response = restTemplate.exchange(
"http://localhost:8083//sample",
HttpMethod.PUT,
new HttpEntity<>("{\"acc\":\"1234\",\"case\":\"abc23\",\"re\":\"20181212\"}", httpHeaders), String.class);
BDDAssertions.then(response.getStatusCodeValue()).isEqualTo(200);
}
2)
This is the failure scenario which not getting 400 respose instead it is trowing httpclient error,it is not able to invoke target
enter code here
#Test
public void should_update_case_error() throws Exception {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Content-Type", "application/json");
ResponseEntity<String> response = restTemplate.exchange(
"http://localhost:8083//sample",
HttpMethod.PUT,
new HttpEntity<>("{\"acc\":\"1234A\",\"caseNumber\":\"abc23\",\"representmentStartDate\":\"20181212\"}", httpHeaders), String.class);
BDDAssertions.then(response.getStatusCodeValue()).isEqualTo(400);
}
Could you help me in this
This will not work cause wiremock has two same requests and two different responses so the first one wins.
What you have to do is to alert the request a little bit to differentiate between the two and that way you'll find the proper response.

Modify request/observable between retry

I have rxjava(observable) + retrofit2 together to make http requests to my application. I create OkHttpClient once for app and don't want to recreate it.
I have retry logic implemented on observable level - using filter, retryWhen together.
What I want - if request finished with error from server side, i want to retry it and send additional header with it.
So, I dont understand neither how can I modify observable inside retryWhen nor how to get the knowledge about observable from interceptor level.
Any ideas and/or knowledge about possible approaches?
You need to create your own Interceptor implementation where you can setup the header logic. Something like
public class FallbackInterceptor implements Interceptor {
static String header1Key = "key1";
static String extraHeaderKey = "key2";
String header1, extraHeader;
boolean useextraheader = false;
public FallbackInterceptor(string header1, string extraheader) {
this.header1 = header1;
this.extraheader = extraheader;
}
public void setUseExtraHeader(boolean useextraheader) {
this.userextraheader = useextraheader;
}
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request original = chain.request();
// Add request headers
Request.Builder requestBuilder = original.newBuilder().header(header1Key, header1);
if (useExtraHeader) {
requestBuilder = requestBuilder.header(extraHeaderKey, extraHeader)
}
Request newRequest = requestBuilder.method(original.method(), original.body())
.build();
// Return the response
return chain.proceed(request);
}
}
Add this to an okhttpclient and have your retrofit instance use this this. You can then manipulate the extraheader flag in your retry logic.

How to handle exceptions in Odata V4 client?

Asp.Net Web API Odata Controller Action:
public async Task<IHttpActionResult> Post(Product product)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Products.Add(product);
await db.SaveChangesAsync();
return Created(product);
}
Odata client code:
(Odata v4 client code generator v4)
static void AddProduct(Default.Container container, ProductService.Models.Product product)
{
container.AddToProducts(product);
var serviceResponse = container.SaveChanges();
foreach (var operationResponse in serviceResponse)
{
Console.WriteLine("Response: {0}", operationResponse.StatusCode);
}
}
I would like to handle exception in a proper way inside AddProducts() Method while saving the changes.
How can I catch process the ModelState error which is sent from server return BadRequest(ModelState);?
Finally I just want to show the error message to the end uses which was sent from server.
Example:
"Product category is required."
What is the use of ODataException class? Will this help me?
Please help me.
if I understood well, you want to intercept that the ModelState is not valid, and customize the OData error that is shown to the user.
If you just want that the errors of the invalid model show up in the returned payload, you can use:
if (!ModelState.IsValid)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
If you want to fully control the exceptions handling and messages shown, I'd suggest several action points for you to accomplish this:
Intercept ModelState is not valid: you can do this with a custom ActionFilterAttribute. In there, you can override the method OnActionExecuting(HttpActionContext actionContext). You can access the ModelState through actionContext.ModelState, check if it is valid, check the fields that have errors, check the nature of these errors and the generated messages for these errors, etc. The ModelState may be not valid for different reasons, like different types than the expected, not meet requirements specified by DataAnnotations, etc. You can check more on Model validation in here. For your case, I guess the Product entity will have a Required data annotation in the Category field.
After checking all errors, you can throw a custom Exception with the error/list of errors with the messages you want. This is necessary to later intercept your custom exception and be able to return your custom message in the error payload.
Intercept your custom exception: create a custom ExceptionFilterAttribute to intercept your thrown exceptions. Overriding the
OnException(HttpActionExecutedContext filterContext) you will have access to the exception, and inspecting it you will be able to build your proper OdataError:
In here you should return the HttpResponseMessage with the BadRequest http status code and the created ODataError as a payload. As an example of very simple code (you can see that it would depend on how you build your custom exception):
public override void OnException(HttpActionExecutedContext filterContext)
{
Exception ex = filterContext.Exception;
HttpRequestMessage currentRequest = filterContext.Request;
if (filterContext.Exception.GetType() == typeof(YourCustomValidationException))
{
var oDataError = new ODataError()
{
ErrorCode = "invalidModel",
Message = "Your model is not valid.",
InnerError = new ODataInnerError()
{
TypeName = ex.TheEntityThatHasErrors
},
};
foreach (var validationError in ex.ValidationErrors)
{
oDataError.InnerError.Message += validationError + ", ";
}
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.BadRequest);
response.RequestMessage = currentRequest;
response.Content = new StringContent(JsonConvert.SerializeObject(oDataError));
filterContext.Response = response;
}
}
Finally, you will have to setup the custom ActionFilterAttribute and the custom ErrorFilterAttribute to be used each time that a request reach your controller. You can decorate your actions, controllers, or you can set the filters for all your API controllers in the WebApiConfig, with config.Filters.Add(...);
You can find more information about all of this in here. In the end, the error and exception handling is the same for ASP.Net Web API, with or without OData; difference is that if you have an OData API, you should return errors in OData style.
Hope all this info is understandable and helps you somehow.

ASP.NET Web Api 2 Custom HttpResponseMessage

I created a custom Result I use in the Web API Controller actions where I return a custom async Task<IHttpActionResult>.
public class CustomResult<T> : NegotiatedContentResult<T>
{
public CustomResult(HttpStatusCode statusCode, T content, ApiController controller)
: base(statusCode, content, controller)
{
}
public CustomResult(HttpStatusCode statusCode, T content, IContentNegotiator contentNegotiator, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
: base(statusCode, content, contentNegotiator, request, formatters) { }
public override async Task<HttpResponseMessage> ExecuteAsync(System.Threading.CancellationToken cancellationToken)
{
HttpResponseMessage response = await base.ExecuteAsync(cancellationToken);
return response;
}
}
And basically in my action I return it like this:
public async Task<IHttpActionResult> Register(RegisterViewModel model)
{
if (!ModelState.IsValid)
{
return new CustomResult<string>(HttpStatusCode.BadRequest,"Model is invalid", this);
}
else
{
return new CustomResult<string>(HttpStatusCode.Ok,"Model is valid", this);
}
}
The problem is the custom message I want to return. It doesn't work! If the model is invalid I always get the 400 Bad Request and the custom message: "The remote server returned the following error while establishing a connection - 'Bad Request', instead of getting 400 Bad Request and my custom message Model is invalid.
This however works when I return 200 OK.
Is there something I am doing wrong?
Try to set
<system.webServer>
<httpErrors existingResponse="PassThrough"/>
</system.webServer>
in web.config. PassThrough option - leaves the response untouched if an existing response exists. (more about httperrors)

How to invoke a post when using HubController<T>?

I can't find much documentation on the new HubController<T> so maybe I'm going about this wrong. This is what I have:
public class StatusController : HubController<StatusHub>
{
private string _status = "";
public string Get()
{
return _status;
}
public void Post(string status)
{
_status = status;
// Call StatusChanged on SignalR clients listening to the StatusHub
Clients.All.StatusChanged(status);
}
}
public class StatusHub : Hub { }
This is how I'm attempting to create the hub proxy:
var hubConnection = new HubConnection("http://localhost:51076/");
var statusHubProxy = hubConnection.CreateHubProxy("StatusHub");
statusHubProxy.On<string>("StatusChanged", status => Console.WriteLine("New Status: {0}", status));
await hubConnection.Start();
How do I call the Post method of my controller? This is where I'm getting an exception:
await statusHubProxy.Invoke("Post", "Test Status");
HubController<T> just provides some basic plumbing that gets you access to the resources that are associated with the specific hub type (e.g. Clients) that you want to work with. Calling it has nothing to do with invoking the actual hub itself, so you don't use the hub client API, it's just straight HTTP calls. Without HubController<T> you would have to reach out to SignalR's GlobalHost.Configuration.GetHubContext<T>() yourself to find the IHubContext for your hub type.
So, you can call your StatusController::Post method with any of the standard .NET HTTP APIs: HttpClient, WebClient or HttpWebRequest.

Resources