I am trying to better handle bad responses when using the sprint rest client. My code is as follows:
ResponseEntity<Foo> response = null;
try {
response = restTemplate.exchange(uri, HttpMethod.GET,
new HttpEntity<>(new HttpHeaders()), Foo.class);
catch (RestClientException rce) {
//what to do here?
}
This works great as long as there is no error. The problem is that when the corresponding web service returns an error (or times out), I just get an exception. There is no enum with a status code where I could handle it as I wish. Sometimes the error message is useful. Sometimes not. Usually, if the corresponding server returns an html error page, I just get a message that the object cannot be created, but the specific error is swallowed.
Any ideas?
What can you get with rce.getMostSpecificCause()? Maybe there is some more information.
You can catch HttpStatusCodeException that is direct subclass of RestClientException which is more generic. There are a lot of exceptions that you can catch that are much more specific than RestClientException e.g.
public class HttpServerErrorException
extends HttpStatusCodeException
Exception thrown when an HTTP 5xx is received.
Look at the docs for RestClientException.
If the website you are querying returns an HTTP errorstatus then that should be reflected in the response object and you can switch through the statuses that you want to cover.
switch(response.getStatus()) {
case HTTPStatus.BAD_REQUEST: {
}
case HTTPStatus.BAD_GATEWAY: {
}
...
}
The RestClientException should only be thrown if there is a client side error, i.e. that's independent from the server response. (Spring Doc)
Related
So we have implemented an interceptor middleware in java spring boot. The intention is to use this middleware in any client application (rest api clients) and validate all incoming http requests to prevent any CSRF attack. We are using this middleware inside one of our rest client, say java-demo. Everything works fine except the case when an invalid url i.e. the end point which does not exist, is hit.
For ex: localhost:8080/java-demo/doesnotexist
In this case, when the interceptor logic raises CSRFException, it does not go to the ControllerAdvice where we have defined Exception handler for this particular exception. And in result, we get status 200 in postman, whereas we should get the exception with http status code as 403 which we throw with the response entity.
But when we hit a valid url,
For ex: localhost:8080/java-demo/exists
and if the exception is raised from the interceptor logic, then ControllerAdvice catches the exception and throws to the client, which is expected behaviour.
What can be the possible reason ControllerAdvice is not able to catch the exception when the url is not valid.
For the sake of clarity: When invalid url is hit and interceptor does not throw any exception, we get 404 not found error in postman.
ExceptionHandler class code which is present inside csrf middleware
#ControllerAdvice
public class CSRFExceptionHandler {
#ExceptionHandler({CSRFException.class})
public ResponseEntity<ErrorResponse> handleCSRFException(CSRFException e) {
return new ResponseEntity<>(new ErrorResponse(e.getErrorCode(), e.getMessage()), HttpStatus.FORBIDDEN);
}
#ExceptionHandler({ExpectedArgNotAvailableException.class})
public ResponseEntity<ErrorResponse> handleExpectedArgsNotAvailable(ExpectedArgNotAvailableException e) {
return new ResponseEntity<>(new ErrorResponse(e.getErrorCode(), e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
I want to know what is best practice to preserve error messages when calling several micro services that is chained: I have an angular front end that calls a back end rest service which calls another rest service which calls another 3rd party service.
The 3rd party service is somewhat unreliable. And I want the response from that service to be propagated to my front end.
So to make it easier for the sake of demo’ing the problem.
I have a control class in downstream project (separate micro-service/application)
#RestController
#RequestMapping("/my-down-stream-service")
public class MyController {
#RequestMapping(value = "my-method")
public MyCustomResponse method1() {
//Some complex logic that catch exceptions and propogates a nice little message
throw new RuntimeException(“This is my exception that indicates what the response is to my 3rd party service”);
}
}
On the other micro-service calling the service above I have a restTemplate making the call to the above service
public MyResponse doIt() {
try {
restTemplate.postForEntity(“MyUrl…”, req, MyResponse.class);
} catch (final HttpStatusCodeException ex) {
//If I add a break point and inspect the exception here
}
}
I can see it is a 500 internal exception that gets send to the front end.
If I go and get the ex.getResponseBodyAsString() I get back a JSON map with the actual detail of the exception.
{
"timestamp": "2020-05-06T22:17:08.401+0200",
"status": 500,
"error": "Internal Server Error",
"exception": "java.lang.RuntimeException",
"message": "This is my exception that indicates what the response is to my 3rd party service",
"path": "…"
}
And I can convert this into a map and get the message portion and construct a new exception and throw that
new ObjectMapper().readValue(ex.getResponseBodyAsString(), HashMap.class).get("message")
But this seems like a lot of work that needs to be implemented where ever I need this.
Is there a better way of doing this?
I also tried creating my own HttpStatus - Like a 550 with my "Own custom message". But you cannot set the message for the HttpStatus code dynamically aka at Runtime. Not even sure if this is the correct venture or path to go down.
My solution in the end based on Amit's suggestion
I finally ended up creating a custom class that extends springs ResponseEntityExceptionHandler. If this is on the class path of your springboot app it will intercept the exception before returning it from the controller. I also created my own exception. Reason being this way if I want my functionality to trigger I fire my own exception and everyone else can still follow the normal way. It can be changed at any time.
Also on the client side I had to cast the exception's getBody() JSON to my exception. But I didn't knew if it was my exception to start of with. So I also added some HTTP header. And on the client side I check if that header is present then I know the body is my exception and I could comfortable convert the JSON to my exception.
#ControllerAdvice
public class MyRestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = {MyCustomException.class})
protected ResponseEntity<Object> handleConflict(final MyCustomException ex, final HttpServletResponse response) {
if (!response.containsHeader("MYTAG")) {
response.addHeader("EX_TYPE", "MYTAG");
}
//here you can go wild as to what type of or just the normal 500
//return ResponseEntity.status(ex.getHttpStatus()).body(ex); // 500 internal exception
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex);
}
}
If I were you, I would like to create a controller advice to handle all kind of exceptions. Then I would like to create a ErrorMessage class which will have custom errorCode, errorMessage fields as per requirements. From this controller advice, for any kind of exceptions occurred in application, it will create an instance of ErrorMessage with details like errorCode and errorMessage and wrap into ResponseEntity object (with HTTP status) and return to the other microservices.
At consumer end check the response status and act accordingly.
I think the answer you are looking for is creating an implementation of ExceptionMapper. The interface is designed to handle java exceptions that map to Response.
In your case, if the 3rd part throws an exception which is handled by the ExceptionMapper implementation you can access the error message and return that in the response.
public class ServiceExceptionMapper implements ExceptionMapper<ServiceException>
{
/**
* {#inheritDoc}
*/
#Override
public Response toResponse(ServiceException exception)
{
//grab the message from the exception and return it in the response
}
I am aware we can use recordExceptions() while building CircuitBreakerConfig to register exceptions on which Circuit Breaker should transition to OPEN state.
Code
I am using resilience4j-feign to decorate my CircuitBreaker. Would be really helpful if you can point me to a code example.
Question
How to make the Circuit Breaker kick-in in case of a specific HTTP status code (e.g. on 503 Service Unavailable) ?
You need to write an Exception/Response handler to your client's external calls and throw custom exceptions based on http status received. Then register these exceptions as record exceptions in your circuit breaker config. Following is a small example. The CB will be open only on AbcException. The CB config isresilience4j.circuitbreaker.instances.bookService.record-exceptions=com.sk.example.cb.circuitbreakerr4j.AbcException
#Service
#Slf4j
public class BookApiService {
RestTemplate restTemplate = new RestTemplate();
#CircuitBreaker(name = "bookService", fallbackMethod = "getBookFallback")
public String getBook(){
try {
ResponseEntity<String> stringResponseEntity = restTemplate.getForEntity(new URI("http://localhost:8080/book"), String.class);
if(null != stringResponseEntity){
if(stringResponseEntity.getStatusCode().is2xxSuccessful()){
return stringResponseEntity.getBody();
}
}
} catch (URISyntaxException e) {
e.printStackTrace();
}catch (HttpServerErrorException e){
log.error("Service unavailable",e);
if(e.getMessage().startsWith("503")) {
throw new AbcException();
}else{
throw e;
}
}
return "";
}
From the docs, Create and configure a CircuitBreaker:
// Create a custom configuration for a CircuitBreaker
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.recordExceptions(IOException.class, TimeoutException.class) // add you exceptions here!!!
.ignoreExceptions(BusinessException.class, OtherBusinessException.class)
.build();
A list of exceptions that are recorded as a failure and thus increase
the failure rate. Any exception matching or inheriting from one of the
list counts as a failure, unless explicitly ignored via
ignoreExceptions.
TL;DR: use a custom exception that communicates HTTP status (e.g. 503) from HTTP client (e.g. Feign) to Resilience4J
Feign: implement and configure an ErrorDecoder to throw a custom exception on HTTP status like 503
Resilience4J: record that custom exception using Circuit Breaker config.
Feign
Feign by default throws a FeignException in case of an erroneous HTTP status code. You can get the status code number via method int status().
To customize your feign-clients error-handling configure a (custom) implementation of ErrorDecoder
If you need more control over handling unexpected responses, Feign instances can register a custom ErrorDecoder via the builder.
[..]
All responses that result in an HTTP status not in the 2xx range will trigger the ErrorDecoder's decode method, allowing you to handle the response, wrap the failure into a custom exception or perform any additional processing. If you want to retry the request again, throw a RetryableException. This will invoke the registered Retryer.
Customize Feign error-handling
Implement and configure a custom ErrorDecoder to throw an exception in case of HTTP status 503.
#Component
#Slf4j
public class CustomErrorDecoder implements ErrorDecoder {
#Override
public Exception decode(String methodKey, Response response) {
switch (response.status()) {
case 400:
log.error("Status code {} on methodKey '{}'", response.status(), methodKey);
case 503:
return new ServiceUnavailableException("HTTP status 503 when calling " methodKey);
default:
return new Exception(response.reason());
}
}
}
This will then throw your custom exception ServiceUnavailableException.
Resilienc4J's CircuitBreaker
By default the circuit-breaker reacts on exceptions. It records them and will open the circuit if there are too much in too less time.
You can configure, which Exceptions to record and which to ignore as expected on the business-level.
Trigger CircuitBreaker on specific exceptions
You can configure CiruitBreaker to record that exception. Joke's answer explains how to do that.
See also
spring feign client exception handling
Feign Client Error Handling
Apps Developer Blog: Feign Error Handling with ErrorDecoder
Resilience4J docs: Feign, Decorating Feign Interfaces
I use exception handler in my controllers like this:
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.CONFLICT)
#ResponseBody
public ApiError handleException(Exception e) {
logger.error("Exception occurred {}", e.getMessage(), e);
return new ApiError(HttpStatus.CONFLICT, e.getMessage());
}
Now I thought about choosing the right response status depends on exception type. Is there any relation in best practices?
The modern way is using Runtime exception everywhere so am not sure that it is always correct to use 4XX response codes for all Runtime exceptions.
Could you clarify?
P.S.
I understans that 4XX is client error, but 5XX is server error.
IMHO it just does not depend on a runtime vs checked exception pattern, but on a semantically right differentiation.
The type of exceptions does not reflect this semantic by default.
5xx is telling the client that something went wrong at you side.
4xx is telling the client that the API is used "wrong" or not in an expected way.
You can implement a 4xx or 5xx status code with runtime or checked exceptions. It just depends on your own software architecture.
I would not determine 4xx/5xx from the differentiation of checked or runtime exceptions (seems to harm the principle of least astonishment - POLA)
Personally, I just throw a custom Exception back to the controller when something goes wrong at runtime, and a ResponseEntity is automatically created, returning my Exception with a HttpStatus code. I try to keep as often as possible a logic, and to return the clearer code along with the message and the exception string name.
Like :
HttpStatus.PRECONDITION_FAILED when a pre-treatment fail (not because of the client payload tho)
HttpStatus.BAD_REQUEST when something is wrong in the payload received from client
HttpStatus.NO_CONTENT when something is OK but we return empty response cause no result is found
etc ...
You are on the right track. In my opinion, it is not always correct to return 4xx response codes for all unchecked exceptions. I would rather map a defined subset of those exceptions to 4xx response codes and anything else should be an internal server error, thus 500 response code.
#ResponseStatus(value=HttpStatus.NOT_FOUND)
public class EntityNotFoundException extends RuntimeException {
// ...
}
This exception can be thrown from #Service annotated classes if the entity is not found. For the other cases, you could define custom exceptions as well. You can also stick to you #ExceptionHandler, but then you would have to do the mapping yourself.
I should be good practise to map exceptions to related HTTP response codes:
Bad Request: 400
Not Found: 404
Forbidden: 403
Internal Server Error: 500
Also, do not forget to return one of the following (or any other) response code, if your request was successful.
OK: 200
Created: 201
Above response codes are commonly used. Of course, there are a lot more, but most APIs only take a small subset. With correct mapping, it is easier for clients to know what possibly went wrong (or right) and then can carry out further actions or display the client neccessary information. For example:
200: show a success message
403: redirect to the login page
400: show errors if a form was submitted
It is also good practise to include meaningful information in the body if anything went wrong. This answers also the question: "What should i do if i cannot map an exception to an existing response code?". I have asked myself the same question and the answer is simple. Try to map it to the closest response code and include anything else in the body.
If the client is missing a parameter you could use the following:
{ "error" : "Bad Request - Your request is missing parameter 'id'. Please verify and resubmit." }
Or for above mentioned forms errors (response code 400):
{
"errors": [
"username": "AlreadyInUse",
]
}
Just make sure you stick to one format when returning information in the body. Otherwise, it is a pain to work with.
I understand that a Jersey-based web service is able to associate exceptions thrown by service methods to desired HTTP return codes (here). Now, is there any chance to make the client generate exactly the same exception that was generated by the service method? I mean, if the server side throws MySpecificException, is there a way to store such information (i.e., the FQN of the exception class) in the HTTP response (automatically, I don't want to turn to methods that build the response explicitly, I want them to return POJOs or void), so that the client can use it to re-throw the same exception?
REST does not specify exception as a response and thus there's no straightforward way to do this (this is not RPC).
However, you can introduce your own convention. For example:
On the provider side you could define ForbiddenException:
public class ForbiddenException extends WebApplicationException {
public ForbiddenException(String code, String readableMessage) {
super(Response.status(Status.FORBIDDEN).entity(new ForbiddenEntity(code, readableMessage)).build());
}
}
(You should probably compose response in ExceptionMapper instead of exception itself, but this is just for demonstration purposes).
And on the consumer side - ClientFilter:
public class ForbiddenExceptionClientFilter extends ClientFilter {
#Override
public ClientResponse handle(ClientRequest cr) throws ClientHandlerException {
ClientResponse response = getNext().handle(cr);
if (response.getStatus() == Status.FORBIDDEN.getStatusCode()) {
ForbiddenEntity reason = response.getEntity(ForbiddenEntity.class);
throw new RemoteResourceForbiddenException(reason.getCode(), reason.getReadableMessage());
}
return response;
}
}
This works as long as server complies with the convention and client uses the client filter.
Please note, this is not "exactly the same" exception - stacktrace is not transferred, however this is the right thing to do as it does not make any sense in client application. If you need stacktrace - it should be printed to logs using ExceptionMapper on server side.