Can I include the restTemplate request body inside restTemplate.setErrorHandler? - java

Here In the below simple code of RestTemplate's errorHandler I want to include the request body inside the log
I want to keep the code clean and not using the error handler out side restTemplate configuration class. So the only way i found is to handel it from rest template caller/user, WDYT?
#Configuration
public class HTTPConfiguration {
...
restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
#Override
public void handleError(ClientHttpResponse response) throws IOException {
if (response.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR) {
LOGGER.error("Server error: {} {}", response.getStatusCode(), response.getStatusText());
} else if (response.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR) {
LOGGER.error("Client error: {} {}", response.getStatusCode(), response.getStatusText());
}
}
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return (response.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR
|| response.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR);
}
});
...
}

The straight answer is no, ResponseErrorHandler is an interface and only has three methods, the best you can get is request url and request method docs
handleError(ClientHttpResponse response)
Handle the error in the given response.
handleError(java.net.URI url, HttpMethod method, ClientHttpResponse response)
Alternative to handleError(ClientHttpResponse) with extra information providing access to the request URL and HTTP method.
handleError(java.net.URI url, HttpMethod method, ClientHttpResponse response)
Indicate whether the given response has any errors.
And ResponseErrorHandler has two implementation classes DefaultResponseErrorHandler, ExtractingResponseErrorHandler but none of them has method with HttpEntity as an argument DefaultResponseErrorHandler,and ExtractingResponseErrorHandler

Related

Spring Boot error handling in app combining MVC and rest api

I am currently trying to figure out the best way to handle errors in a spring boot app.
Assume that the app consists of thymeleaf templates served on paths starting with /admin, and a REST API served on other URIs.
My project currently has the following configuration:
Thymeleaf error templates located at /src/main/resources/templates/error/{id}.html, defined for errors 400, 401, 403, 404, 500.
Default thymeleaf error template /src/main/resources/templates/error.html
HttpSecurity configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
(...)
http
.anonymous();
http
.exceptionHandling()
.authenticationEntryPoint((request, response, authException) -> response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()))
.accessDeniedHandler(new AccessDeniedHandlerImpl());
(...)
}
Exception Handlers for both rest and MVC:
#ControllerAdvice
public class ExceptionHandlers {
private final BasicErrorController basicErrorController;
public ExceptionHandlers(BasicErrorController basicErrorController) {
this.basicErrorController = basicErrorController;
}
#ExceptionHandler(Exception.class)
public Object handleAllExceptions(Exception e, HttpServletRequest request, HttpServletResponse response) {
return handle(e, request, response, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(EntityNotFoundException.class)
public Object handleEntityNotFoundException(EntityNotFoundException e, HttpServletRequest request, HttpServletResponse response) {
return handle(e, request, response, HttpStatus.NOT_FOUND, I18nCodes.ENTITY_NOT_FOUND);
}
/**
* We exclude all exceptions deriving from {#link AccessDeniedException} from custom exception handling.
*/
#ExceptionHandler(AccessDeniedException.class)
public Object handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request, HttpServletResponse response) {
throw e;
}
/**
* We exclude all exceptions deriving from {#link AppBaseException} from custom exception handling.
*/
#ExceptionHandler(AppBaseException.class)
public Object handleAppBaseException(AppBaseException e, HttpServletRequest request, HttpServletResponse response) {
throw e;
}
private Object handle(Exception e, HttpServletRequest request, HttpServletResponse response, HttpStatus status) {
return handle(e, request, response, HttpStatus.INTERNAL_SERVER_ERROR, I18nCodes.getCodeByStatus(status));
}
private Object handle(Exception e, HttpServletRequest request, HttpServletResponse response, HttpStatus status, String message) {
String header = request.getHeader("Accept");
if (header != null && header.contains("text/html")) {
setErrorCode(request, response, status);
return basicErrorController.errorHtml(request, response);
}
return createJsonResponse(message, status, request.getRequestURI());
}
private ResponseEntity<ErrorResponseDTO> createJsonResponse(String message, HttpStatus status, String path) {
ErrorResponseDTO errorResponseDTO = new ErrorResponseDTO()
.setTimestamp(new Timestamp(System.currentTimeMillis()))
.setStatus(status.value())
.setMessage(message)
.setPath(path)
.setError(status.name().toLowerCase());
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return ResponseEntity.status(status).headers(httpHeaders).body(errorResponseDTO);
}
private void setErrorCode(HttpServletRequest request, HttpServletResponse response, HttpStatus httpStatus) {
request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, httpStatus.value());
response.setStatus(httpStatus.value());
}
}
Without the Exception handlers everything works about as expected, I get 401 error when I am not authenticated, and 403 when authenticated but lacking authorities. My custom exceptions are correctly mapped according to their message and Status defined in #ResponseStatus eg:
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class AccountInfoException extends AppBaseException {
protected AccountInfoException(String message) {
super(message);
}
protected AccountInfoException(String message, Throwable cause) {
super(message, cause);
}
public static AccountInfoException emailAlreadyExists() {
return new AccountInfoException(I18nCodes.EMAIL_EXIST);
}
public static AccountInfoException accountNotFound() {
return new AccountInfoException(I18nCodes.ACCOUNT_NOT_FOUND);
}
}
Will map to status 400 with one of two messages I18nCodes.EMAIL_EXIST or I18nCodes.ACCOUNT_NOT_FOUND.
I arrive on the correct thymeleaf error page depending on the status code - life is perfect.
The issue comes with the requirement of handling all other exceptions. It's not a possibility to let an unexpected error to the client. So for that I've defined an exception handler handing Exception, to return a generic 500 message. This breaks everything. I'm unable to access the Status codes of my custom exceptions
Authentication entry point and access denied handler are both ignored, and both cases of being not authenticated and having insufficient authority are handles in the same exception(AccessDeniedException). I also lose default mappings for some exceptions eg. org.springframework.security.authentication.LockedException returns status 401.
My temporary workaround was to handle all exceptions, and to create methods handling exceptions that I want to ignore and just rethrow the exception. I feel like the best thing I could do is remove all exception handlers and repack all container exceptions to my own custom exceptions, only issue is that there doesn't seem to be a way to do it.
I'm looking for a more permanent solution - what would be the best approach?

How can I change response status in void method (not returning ResponseEntity)?

I use Java 11 und Spring.
Let's assume I have a method like this:
#POST
#Path(WebservicePaths.EXAMPLE_PATH)
public void processData(#NotNull #RequestBody TypeOfRequest request) {
try {
doSomething(request);
}
catch(Exception e) {
?????????????
}
}
At this moment the HTTP response is set automatically, right?
How can I get und print to the console body of the final response?
How can I change response status or other details to specific one only in block catch() {}?
You can inject HttpServletResponse and can call sendError method and pass the required HttpStatus in it. Below is the example:
#POST
#Path(WebservicePaths.EXAMPLE_PATH)
public void processData(#NotNull #RequestBody TypeOfRequest request, HttpServletResponse response) throws IOException {
try {
doSomething(request);
}
catch(Exception e) {
response.sendError(HttpStatus.INTERNAL_SERVER_ERROR.value());
}
}

Not able to read request body in HandlerInterceptor postHandle() method

I have created the Interceptor where i am trying to read the request body .But i keep getting the error
getInputStream() has been called for this request
How to solve it in postHandle ?I can do the same without any error by ovver riding preHandle but i need it in postHandle .
public class LoggerInterceptor implements HandlerInterceptor {
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
try {
request.getReader();
String s = IOUtils.toString(request.getReader());
System.out.println(" post ---- " + s);//i want to log into DB
} catch (Exception e) {
System.out.println(" eror " + e);
}
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}

Implementing the ResponseErrorHandler Interface

I'm trying to override the ResponseErrorHandler interface to be able to return the entire request (status code, body etc.) in case of any response other than 2xx.
I noticed that the Spring (RestTemplate) default returns an exception in case of a response other than 2xx. I do not want to return an exception, I just want to be able to return a:
new ResponseEntity(HttpStatus.STATUS_CODE)
Following some tutorials, I've found the following code:
#Component
public class LoginErrorHandler
implements ResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse httpResponse)
throws IOException {
return (
httpResponse.getStatusCode().series() == CLIENT_ERROR
|| httpResponse.getStatusCode().series() == SERVER_ERROR);
}
#Override
public void handleError(ClientHttpResponse httpResponse)
throws IOException {
if (httpResponse.getStatusCode()
.series() == SERVER_ERROR) {
// handle SERVER_ERROR
} else if (httpResponse.getStatusCode()
.series() == CLIENT_ERROR) {
// handle CLIENT_ERROR
}
}
(Reference)
But I have not understood how I can return a ResponseEntity without changing the method return (which I can not by implementing the method).
Implementation:
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new LoginErrorHandler());
return restTemplate.postForEntity(url, request, String.class);
You can use Spring's ControllerAdvice and ExceptionHandler annotations to handle exceptions through your application. Below code returns 500 http status code if any exception encountered in your request. You can add other Exception classes or your own custom class to handle specific cases and return specific status codes to client.
Edit
Handling each code will not be a good idea. Rather you can wrap them in your custom exception and provide proper message to your client service. Still you can try something like below.
#Component
#ControllerAdvice
public class MyExceptionHandler {
#ExceptionHandler(HttpClientErrorException.BadRequest.class)
#ResponseStatus(code=HttpStatus.BAD_REQUEST, reason="Bad Request", value=HttpStatus.BAD_REQUEST)
public void handleBadRequest(HttpClientErrorException.BadRequest e) {
//handle bad request exception
}
#ExceptionHandler(HttpClientErrorException.NotFound.class)
#ResponseStatus(code=HttpStatus.NOT_FOUND, reason="Not Found", value=HttpStatus.NOT_FOUND)
public void handleNotFound(HttpClientErrorException.NotFound e) {
//handle Not Found
}
#ExceptionHandler(HttpServerErrorException.InternalServerError.class)
#ResponseStatus(code=HttpStatus.INTERNAL_SERVER_ERROR, reason="Internal Server Error", value=HttpStatus.INTERNAL_SERVER_ERROR)
public void handleInternalServerError(HttpServerErrorException.InternalServerError e) {
//handle internal server error
}
//more methods for each code.
}
Then handle the codes from in your rest template as below. Here you won't be able to return body of the response to the client.
#Component
public class LoginErrorHandler
implements ResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse httpResponse)
throws IOException {
return (httpResponse.getStatusCode() != HttpStatus.OK);
}
#Override
public void handleError(ClientHttpResponse httpResponse)
throws IOException {
if (httpResponse.getRawStatusCode() >=400 && httpResponse.getRawStatusCode()<500 ) {
throw HttpClientErrorException.create(httpResponse.getStatusCode(), httpResponse.getStatusText(), httpResponse.getHeaders(), null, null);
}else if(httpResponse.getRawStatusCode() >=500){
throw HttpServerErrorException.create(httpResponse.getStatusCode(), httpResponse.getStatusText(), httpResponse.getHeaders(), null, null);
}else {
//throw some other exceptions for other codes and catch them in controller advice.
}
}
}
You can do this:
#Component
#ControllerAdvice
public class LoginErrorHandler{
#ExceptionHandler(HttpClientErrorException.class)
#ResponseBody
public void handleError(HttpClientErrorException e, HttpServletResponse response) throws IOException {
response.sendError(e.getRawStatusCode(), e.getStatusText());
}
}
This you generify all status code that should drop an exception, and it will return in the body.
There are two extensively used and very convenient exception handlers which are provided by Spring framework for centralized exception management in Spring Boot applications.
ResponseEntityExceptionHandler : exception thrown by our endpoint methods(methods annotated with #RequestMapping)
A convenient base class for #ControllerAdvice classes that wish to provide centralized exception handling across all #RequestMapping methods through #ExceptionHandler methods.
ResponseErrorHandler : Spring provides a hook ResponseErrorHandler which can be implemented to handle the exception thrown by external services. To call any external service most likely you will be using a RestTemplate. A RestTemplate can throw three types of exception as listed below :
HttpClientErrorException : For 4xx series status codes or Client errors.
HttpServerErrorException : For 5xx series status codes or server errors
RestClientException : Any other status codes like 3xx series
To simplify things we can handle these exceptions as a catch block separately but it lead to lot of boilerplate code scattered across our service. Strategy interface used by the RestTemplate to determine whether a particular response has an error or not.
There are two steps to using ResponseErrorHandler :
Step 1: Create a custom error handler class by implementing ResponseErrorHandler and implements its methods hasError and handleError
Step 2: We need to inject the ResponseErrorHandler implementation into the RestTemplate instance as follows :
By default in RestTemplate the errorHandler points to DefaultResponseErrorHandler.
Source

Spring MVC: How to modify #Pathvariable(URI) in Interceptor before going to controller?

I was get Controller's #PathVariable in Pre-Handler Interceptor.
Map<String, String> pathVariable = (Map<String, String>) request.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE );
But I wish to modify #PathVariable value(below).
#RequestMapping(value = "{uuid}/attributes", method = RequestMethod.POST)
public ResponseEntity<?> addAttribute(#PathVariable("uuid") String uuid, HttpServletRequest request, HttpServletResponse response) {
//LOGIC
}
How to modify #PathVariable("uuid") value in interceptor before going to controller??
I'm using Spring 4.1 and JDK 1.6. I can't upgrade its.
A general use for interceptors is to apply generic functionality to controllers. I.e. default data shown on all pages, security etc. You want to use it for a single piece of functionality which you generallly shouldn't do.
What are you trying to achieve isn't possible with an interceptor. As first the method to execute is detected based on the mapping data. Before executing the method the interceptor is executed. In this you basically want to change the incoming request and to execute a different method. But the method is already selected, hence it won't work.
As you eventually want to call the same method simply add another request handling method which either eventually calls addAttribute or simply redirects to the URL with the UUID.
#RequestMapping("<your-url>")
public ResponseEntity<?> addAttributeAlternate(#RequestParam("secret") String secret, HttpServletRequest request, HttpServletResponse response) {
String uuid = // determine UUID based on request
return this.addAttribute(uuid,request,response);
}
Try below given code.
public class UrlOverriderInterceptor implements ClientHttpRequestInterceptor {
private final String urlBase;
public UrlOverriderInterceptor(String urlBase) {
this.urlBase = urlBase;
}
private static Logger LOGGER = AppLoggerFactory.getLogger(UrlOverriderInterceptor.class);
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
URI uri = request.getURI();
LOGGER.warn("overriding {0}", uri);
return execution.execute(new MyHttpRequestWrapper(request), body);
}
private class MyHttpRequestWrapper extends HttpRequestWrapper {
public MyHttpRequestWrapper(HttpRequest request) {
super(request);
}
#Override
public URI getURI() {
try {
return new URI(UrlUtils.composeUrl(urlBase, super.getURI().toString())); //change accordingly
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}
}

Categories

Resources