Get HttpServletRequest into Spring ResponseEntityExceptionHandler - java

I've a REST server made with Spring Boot 2.0.1.
I'm customizing exception handling extending ResponseEntityExceptionHandler.
This is my class
#RestControllerAdvice
public class ApplicationExceptionHandler extends ResponseEntityExceptionHandler {
private Logger log = LogManager.getLogger();
#Autowired
private MessageSource messageSource;
private MessageSourceAccessor messageSourceAccessor = null;
#PostConstruct
public void postConstruct() {
Assert.notNull(messageSource, "MessageSource must not be null!");
this.messageSourceAccessor = new MessageSourceAccessor(messageSource);
}
/**
* Mapping each constraint name with the corrispondent exception code -> message
*/
private static Map<String, ExceptionCode> constraintCodeMap = new HashMap<String, ExceptionCode>() {
private static final long serialVersionUID = -628747907324708275L;
{
put("account_username", ExceptionCode.ACCOUNT_DUPLICATE_USERNAME);
put("paymenttype_code", ExceptionCode.PAYMENTTYPE_DUPLICATE_CODE);
put("ticketblock_number_type", ExceptionCode.TICKETBLOCK_DUPLICATE_CODE);
put("ticket_number", ExceptionCode.TICKET_DUPLICATED_CODE);
put("licenseplate_plate", ExceptionCode.LICENSEPLATE_DUPLICATED);
}
};
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
return response(HttpStatus.BAD_REQUEST, new HttpHeaders(),
buildGenericError(ex, request, HttpStatus.BAD_REQUEST, LocaleContextHolder.getLocale()));
}
#Override
protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
return response(HttpStatus.BAD_REQUEST, new HttpHeaders(),
buildGenericError(ex, request, HttpStatus.BAD_REQUEST, LocaleContextHolder.getLocale()));
}
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
if (ExceptionUtils.getRootCauseMessage(ex).contains("Duplicate entry")) {
/**
* Custom errors and messages for DataIntegrityViolationException checked against the list of indexes names
*/
return response(HttpStatus.CONFLICT, new HttpHeaders(),
buildIntegrityError(ex, request, HttpStatus.CONFLICT, LocaleContextHolder.getLocale()));
} else {
return response(HttpStatus.BAD_REQUEST, new HttpHeaders(),
buildGenericError(ex, request, HttpStatus.BAD_REQUEST, LocaleContextHolder.getLocale()));
}
}
/**
* #see {#link RepositoryRestExceptionHandler}
*
* #param ex
* #param request
* #param locale
* #return
* #throws Exception
*/
#ExceptionHandler(DataIntegrityViolationException.class)
public ResponseEntity<?> handleConflictException(DataIntegrityViolationException ex, HttpServletRequest request, Locale locale)
throws Exception {
/**
* Keep the default Exception format for Violation exception #see {#link RepositoryRestExceptionHandler}
*/
if (ex instanceof RepositoryConstraintViolationException) {
return response(HttpStatus.BAD_REQUEST, new HttpHeaders(),
new RepositoryConstraintViolationExceptionMessage((RepositoryConstraintViolationException) ex, messageSourceAccessor));
}
/**
* Custom errors and messages for DataIntegrityViolationException checked against the list of indexes names
*/
return response(HttpStatus.CONFLICT, new HttpHeaders(), buildIntegrityError(ex, request, HttpStatus.CONFLICT, locale));
}
/**
* Handle the exception when the file size is bigger than the maximum set in the configuration
*
* #param ex
* #param request
* #param locale
* #return
* #throws Exception
*/
#ExceptionHandler(MaxUploadSizeExceededException.class)
public ResponseEntity<?> handleFileUpload(MaxUploadSizeExceededException ex, HttpServletRequest request, Locale locale)
throws Exception {
log.error(String.format("Received a file too big from %s. Error: %s", AppUtils.getRemoteIp(request),
ExceptionUtils.getRootCauseMessage(ex)));
return response(HttpStatus.BAD_REQUEST, new HttpHeaders(),
buildIntegrityError(ex, request, HttpStatus.BAD_REQUEST, LocaleContextHolder.getLocale()));
}
/**
* Build a JSON integrity error compliant to the standard exception
*
* #param exception
* #param request
* #param httpStatus
* #param message
* #return
*/
private JsonException buildIntegrityError(final Throwable exception, final HttpServletRequest request, final HttpStatus httpStatus,
Locale locale) {
return buildIntegrityError(exception, request.getRequestURI(), httpStatus, locale);
}
private JsonException buildIntegrityError(final Throwable exception, final WebRequest request, final HttpStatus httpStatus,
Locale locale) {
return buildIntegrityError(exception, "", httpStatus, locale);
}
/**
* Build a JSON integrity error compliant to the standard exception
*
*/
private JsonException buildIntegrityError(final Throwable exception, String requestUri, final HttpStatus httpStatus, Locale locale) {
String finalMessage = "";
String rootMsg = ExceptionUtils.getRootCauseMessage(exception);
Optional<Map.Entry<String, ExceptionCode>> entry = constraintCodeMap.entrySet().stream()
.filter((it) -> rootMsg.contains(it.getKey())).findAny();
if (entry.isPresent()) {
finalMessage = messageSource.getMessage(entry.get().getValue().getCode(), new Object[] {}, locale);
} else {
finalMessage = messageSource.getMessage(ExceptionCode.INTEGRITY_VIOLATION.getCode(), new Object[] { rootMsg }, locale);
}
JsonException jsonException = new JsonException();
jsonException.setError(httpStatus.getReasonPhrase());
jsonException.setStatus(httpStatus.value());
jsonException.setException(exception.getClass().getName());
jsonException.setMessage(finalMessage);
jsonException.setPath(requestUri);
return jsonException;
}
/**
* Build a JSON integrity error compliant to the standard exception
*
* #param exception
* #param request
* #param httpStatus
* #param message
* #return
*/
private JsonException buildGenericError(final Throwable exception, final HttpServletRequest request, final HttpStatus httpStatus,
Locale locale) {
String rootMsg = ExceptionUtils.getRootCauseMessage(exception);
String finalMessage = messageSource.getMessage(ExceptionCode.INTERNAL_ERROR.getCode(), new Object[] { rootMsg }, locale);
JsonException jsonException = new JsonException();
jsonException.setError(httpStatus.getReasonPhrase());
jsonException.setStatus(httpStatus.value());
jsonException.setException(exception.getClass().getName());
jsonException.setMessage(finalMessage);
jsonException.setPath(request.getRequestURI());
return jsonException;
}
private JsonException buildGenericError(final Throwable exception, final WebRequest request, final HttpStatus httpStatus,
Locale locale) {
String rootMsg = ExceptionUtils.getRootCauseMessage(exception);
String finalMessage = messageSource.getMessage(ExceptionCode.INTERNAL_ERROR.getCode(), new Object[] { rootMsg }, locale);
JsonException jsonException = new JsonException();
jsonException.setError(httpStatus.getReasonPhrase());
jsonException.setStatus(httpStatus.value());
jsonException.setException(exception.getClass().getName());
jsonException.setMessage(finalMessage);
jsonException.setPath("");
return jsonException;
}
private static <T> ResponseEntity<T> response(HttpStatus status, HttpHeaders headers, T body) {
Assert.notNull(headers, "Headers must not be null!");
Assert.notNull(status, "HttpStatus must not be null!");
return new ResponseEntity<T>(body, headers, status);
}
}
I want to override the behaviour for HttpMessageNotReadableException but I have to Override the method handleHttpMessageNotReadable because it is an exception managed by the superclass and I can't create my method annotated with #ExceptionHandler.
The problem is the method exposes WebRequest instead of a HttpServletRequest. I need a HttpServletRequest to get the remote ip address of the client.
Is there a way to do what I did for DataIntegrityViolationException in my class where I can get the HttpServletRequest?

For getting HttpServletRequest,you can do:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
Hope this helps.

Related

Spring form input values return null after read it once in filter, despite using request wrapper

In my spring-boot project, I use freemarker templates for sample forms. I needed to add filter in order to read payload and do some stuff. I know if you read payload in filter, you need to reset request body. Because it can be read once. Since I encountered this problem before, I knew that I must have used wrapper. I expected solve my problem as before. However, in the controller, all fields in input objects are null.
What am I missing in here ?
My filter:
public class KfsInMsgFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
Map<String, String[]> extraParams = new TreeMap<String, String[]>();
WrappedRequest wrappedRequest = new WrappedRequest(request, extraParams);
String body = IOUtils.toString(new BufferedReader(new InputStreamReader(wrappedRequest.getInputStream(), Constants.UTF_8)));
// doing some stuff using body
// ....
// resetting payload
wrappedRequest.resetStream(body.getBytes(Constants.UTF_8));
...
}
}
WrappedRequest class:
#Slf4j
public class WrappedRequest extends HttpServletRequestWrapper {
private final Map<String, String[]> modifiableParameters;
private ResettableServletInputStream servletStream;
private byte[] rawData;
private HttpServletRequest request;
private String payload;
/**
* Create a new request wrapper that will merge additional parameters into
* the request object without prematurely reading parameters from the
* original request.
*
* #param request
* #param additionalParams
*/
public WrappedRequest(final HttpServletRequest request,
final Map<String, String[]> additionalParams) {
super(request);
this.request = request;
this.modifiableParameters = new TreeMap<String, String[]>();
this.modifiableParameters.putAll(additionalParams);
this.servletStream = new ResettableServletInputStream();
}
/**
* #param newRawData
*/
public void resetStream(byte[] newRawData) {
servletStream.stream = new ByteArrayInputStream(newRawData);
}
/**
* #return
* #throws IOException
*/
#Override
public ServletInputStream getInputStream() throws IOException {
if (rawData == null) {
rawData = IOUtils.toByteArray(this.request.getReader());
servletStream.stream = new ByteArrayInputStream(rawData);
}
return servletStream;
}
/**
* #return
* #throws IOException
*/
#Override
public BufferedReader getReader() throws IOException {
if (rawData == null) {
rawData = IOUtils.toByteArray(this.request.getReader());
servletStream.stream = new ByteArrayInputStream(rawData);
}
return new BufferedReader(new InputStreamReader(servletStream, Constants.UTF_8));
}
/**
* #return
*/
private String getBodyAsString() {
StringBuffer buff = new StringBuffer();
buff.append(" BODY_DATA START [ ");
char[] charArr = new char[getContentLength()];
try {
BufferedReader reader = new BufferedReader(getReader());
reader.read(charArr, 0, charArr.length);
reader.close();
} catch (IOException e) {
log.error("", e);
}
buff.append(charArr);
buff.append(" ] BODY_DATA END ");
return buff.toString();
}
/**
* #return
*/
public String getPayload() {
return payload;
}
/**
* #param payload
*/
public void setPayload(String payload) {
this.payload = payload;
}
private static class ResettableServletInputStream extends ServletInputStream {
private InputStream stream;
#Override
public int read() throws IOException {
return stream.read();
}
#Override
public boolean isFinished() {
return false;
}
#Override
public boolean isReady() {
return false;
}
#Override
public void setReadListener(ReadListener readListener) {
}
}
}
Body I expected to get in controller:
What I get:
#PostMapping(value = "/edit")
public String editPlatform(EditInfo editInfo, Model model) {
Optional<Platform> p = platformService.findById(editInfo.getId());
List<SafeCustodyOffice> officeList = safeCustodyOfficeService.getAll();
if (p.isPresent()) {
model.addAttribute("platform", p.get());
model.addAttribute("offices", officeList);
return "platform-edit";
} else {
throw new KfsException(ErrorCodes.KFS19);
}
}
Important Edit:
I discovered someting I found interesting and gives me clues about the problem. This may be makes more sense for anybody but me.
I see that the content type of input changes the result like this:
Is there any workaround to make row 5 combination work like row 3?

Spring boot - Log every 4xx and 5xx including the request

I am trying to log (WARNING and ERROR with slf4j) every 4xx and 5xx and the request made from the client including headers and the payload.
I also want to log the response which my application responds with, no matter if it's an exception message generated by Spring itself, or a custom message that I've returned from my controller.
These are my controllers that I am using for testing:
#RequestMapping(path = "/throw", method = RequestMethod.GET)
public String Fail(){
String nul = null;
nul.toCharArray();
return "Hello World";
}
#RequestMapping(path = "/null", method = RequestMethod.GET)
public ResponseEntity Custom() {
return ResponseEntity.notFound().build();
}
I have tried following methods:
ControllerAdvice
Found out that this is only for handling exceptions. I need to handle any 4xx and 5xx response returned from my controller.
Using filter
By using CommonsRequestLoggingFilter I can log the request, including the payload. However, this does not log when an exception is thrown (which is handled by Spring).
Using interceptor
With interceptor I should be able to intercept both incoming and outgoing data with the following code:
private static final Logger log = LoggerFactory.getLogger(RequestInterceptor.class);
class RequestLog {
public String requestMethod;
public String requestUri;
public String requestPayload;
public String handlerName;
public String requestParams;
RequestLog(String requestMethod, String requestUri, String requestPayload, String handlerName, Enumeration<String> requestParams) {
this.requestMethod = requestMethod;
this.requestUri = requestUri;
this.requestPayload = requestPayload;
this.handlerName = handlerName;
StringBuilder stringBuilder = new StringBuilder();
while (requestParams.hasMoreElements()) {
stringBuilder
.append(";")
.append(requestParams.nextElement());
}
this.requestParams = stringBuilder.toString();
}
}
class ResponseLog {
public int responseStatus;
public String responsePayload;
public ResponseLog(int responseStatus, String responsePayload) {
this.responseStatus = responseStatus;
this.responsePayload = responsePayload;
}
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestUri = request.getRequestURI();
String requestPayload = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
Enumeration<String> requestParams = request.getParameterNames();
String requestMethod = request.getMethod();
String handlerName = handler.toString();
RequestLog requestLog = new RequestLog(requestMethod, requestUri, requestPayload, handlerName, requestParams);
String serialized = new ObjectMapper().writeValueAsString(requestLog);
log.info("Incoming request:" + serialized);
return super.preHandle(request, response, handler);
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws IOException {
int responseStatus = response.getStatus();
boolean is4xx = String.valueOf(responseStatus).startsWith("4");
boolean is5xx = String.valueOf(responseStatus).startsWith("5");
if (is4xx || is5xx || ex != null) {
String responseBody = getResponseBody(response);
ResponseLog responseLog = new ResponseLog(responseStatus, responseBody);
String serialized = new ObjectMapper().writeValueAsString(responseLog);
log.warn("Response to last request:" + serialized);
}
}
private String getResponseBody(HttpServletResponse response) throws UnsupportedEncodingException {
String responsePayload = "";
ContentCachingResponseWrapper wrappedRequest = new ContentCachingResponseWrapper(response);
byte[] responseBuffer = wrappedRequest.getContentAsByteArray();
if (responseBuffer.length > 0) {
responsePayload = new String(responseBuffer, 0, responseBuffer.length, wrappedRequest.getCharacterEncoding());
}
return responsePayload;
}
When requesting /throw I get following log from the interceptor:
2017-12-11 21:40:15.619 INFO 12220 --- [nio-8080-exec-1] c.e.demo.interceptor.RequestInterceptor : Incoming request:{"requestMethod":"GET","requestUri":"/throw","requestPayload":"","handlerName":"public java.lang.String com.example.demo.controllers.IndexController.Fail()","requestParams":""}
2017-12-11 21:40:15.635 WARN 12220 --- [nio-8080-exec-1] c.e.demo.interceptor.RequestInterceptor : Response to last request:{"responseStatus":200,"responsePayload":""}
*stackTrace because of nullpointer...*
2017-12-11 21:40:15.654 INFO 12220 --- [nio-8080-exec-1] c.e.demo.interceptor.RequestInterceptor : Incoming request:{"requestMethod":"GET","requestUri":"/error","requestPayload":"","handlerName":"public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)","requestParams":""}
2017-12-11 21:40:15.675 WARN 12220 --- [nio-8080-exec-1] c.e.demo.interceptor.RequestInterceptor : Response to last request:{"responseStatus":500,"responsePayload":""}
With request to /null:
2017-12-11 21:48:14.815 INFO 12220 --- [nio-8080-exec-3] c.e.demo.interceptor.RequestInterceptor : Incoming request:{"requestMethod":"GET","requestUri":"/null","requestPayload":"","handlerName":"public org.springframework.http.ResponseEntity com.example.demo.controllers.IndexController.Custom()","requestParams":""}
2017-12-11 21:48:14.817 WARN 12220 --- [nio-8080-exec-3] c.e.demo.interceptor.RequestInterceptor : Response to last request:{"responseStatus":404,"responsePayload":""}
There are two issues here:
Response body is always null (even though the client receives error response from Spring). How can I fix this?
Seems like Spring is redirecting to /error when an exception occurs
TL;DR: I need to log the request to my Spring application and the response (including the payload) to client. How can I solve this problem?
Possible solution using both Filter and ControllerAdvice:
Filter:
#Component
public class LogFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(LogFilter.class);
private static final int DEFAULT_MAX_PAYLOAD_LENGTH = 1000;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, final FilterChain filterChain) throws ServletException, IOException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
logRequest(request);
filterChain.doFilter(requestWrapper, responseWrapper);
logResponse(responseWrapper);
}
private void logResponse(ContentCachingResponseWrapper responseWrapper) {
String body = "None";
byte[] buf = responseWrapper.getContentAsByteArray();
if (buf.length > 0) {
int length = Math.min(buf.length, DEFAULT_MAX_PAYLOAD_LENGTH);
try {
body = new String(buf, 0, length, responseWrapper.getCharacterEncoding());
responseWrapper.copyBodyToResponse();
} catch (IOException e) {
e.printStackTrace();
}
}
int responseStatus = responseWrapper.getStatusCode();
boolean is4xx = String.valueOf(responseStatus).startsWith("4");
boolean is5xx = String.valueOf(responseStatus).startsWith("5");
if(is4xx) logger.warn("Response: statusCode: {}, body: {}", responseStatus, body);
else if (is5xx) logger.error("Response: statusCode: {}, body: {}", responseStatus, body);
}
private void logRequest(HttpServletRequest request) {
String body = "None";
try {
body = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
} catch (IOException e) {
e.printStackTrace();
}
logger.warn("Incoming request {}: {}", request.getRequestURI() , body);
}
}
ControllerAdvice:
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
CustomException customException = new CustomException(NOT_FOUND, ex.getMessage(), ex.getLocalizedMessage(), ex);
ex.printStackTrace();
return new ResponseEntity<>(customException, customException.getStatus());
}
#ResponseBody
#ExceptionHandler(Exception.class)
protected ResponseEntity<Object> handleSpringExceptions(HttpServletRequest request, Exception ex) {
CustomException customException = new CustomException(INTERNAL_SERVER_ERROR, ex.getMessage(), ex.getLocalizedMessage(), ex);
ex.printStackTrace();
return new ResponseEntity<>(customException, customException.getStatus());
}
#Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
CustomException customException = new CustomException(INTERNAL_SERVER_ERROR, ex.getMessage(),ex.getLocalizedMessage(), ex);
ex.printStackTrace();
return new ResponseEntity<>(customException, customException.getStatus());
}
}
The filter can log any request and response that we have handled inside the controller, however the response payload seems to be always empty when an exception is thrown (because Spring handles it and creates a custom message). I am not sure how this works under the hood, but I managed to overcome this problem by using ControllerAdvice in addition (the response is passed through the filter...). Now I can log any 4xx and 5xx properly. If someone has better solution I will accept that instead.
Note: CustomException is just a class with fields that I want to send to the client.
public class CustomException{
public String timestamp;
public HttpStatus status;
public String exceptionMessage;
public String exceptionType;
public String messageEn;
public String messageNo;
...
}

Exception handling using handlers spring boot

I am a newbie with spring and spring boot. After creating a simple REST API to perform CRUD operations on Users i have also created custom exception handlers to catch any exceptions occurred in the application. Here is the code which i have written
The controller class
public class UserController {
#Autowired
private IUserService userService;
#ApiOperation(value = "View list of all users", response = Iterable.class)
#RequestMapping(value = "/users", method = RequestMethod.GET)
public #ResponseBody List<User> getAll() throws EntityNotFoundException {
return userService.query();
}
#ApiOperation(value = "View a specific user", response = User.class)
#RequestMapping(value = "/users/{id}", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public #ResponseBody List<User> getUser(#PathVariable(value = "id") String userid) throws EntityNotFoundException {
return userService.query(userid);
}
#ApiOperation(value = "create a user")
#RequestMapping(value = "/users", method = RequestMethod.POST, produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<HttpStatus> createUser(#Valid #RequestBody User user) throws Exception {
userService.add(user);
return ResponseEntity.ok(HttpStatus.OK);
}
#ApiOperation(value = "update a user")
#RequestMapping(value = "/users", method = RequestMethod.PUT, produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<User> updateUser(#Valid #RequestBody User entity) throws Exception {
User user = userService.update(entity);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(user);
}
#ApiOperation(value = "delete a user")
#RequestMapping(value = "/userid/{id}", method = RequestMethod.DELETE, produces = {
MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<HttpStatus> deleteUser(#PathVariable(value = "id") String userid) throws Exception {
String result = userService.remove(userid);
if (result.equals(null)) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(HttpStatus.OK);
}
}
Exceptions handlers annotated class with #Controlleradvice
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
/**
* Handles javax.validation.ConstraintViolationException. Thrown
* when #Validated fails.
*
* #param ex
* the ConstraintViolationException
* #return the ApiError object
*/
#ExceptionHandler(javax.validation.ConstraintViolationException.class)
protected ResponseEntity<Object> handleConstraintViolation(javax.validation.ConstraintViolationException ex) {
ApiError apiError = new ApiError(BAD_REQUEST);
apiError.setMessage("Validation error");
return buildResponseEntity(apiError);
}
/**
* Handles EntityNotFoundException. Created to encapsulate errors with more
* detail than javax.persistence.EntityNotFoundException.
*
* #param ex
* the EntityNotFoundException
* #return the ApiError object
*/
#ExceptionHandler(EntityNotFoundException.class)
protected ResponseEntity<Object> handleEntityNotFound(EntityNotFoundException ex) {
ApiError apiError = new ApiError(NOT_FOUND);
apiError.setMessage(ex.getMessage());
return buildResponseEntity(apiError);
}
/**
* Handle Exception, handle generic Exception.class
*
* #param ex
* the Exception
* #return the ApiError object
*/
#ExceptionHandler(MethodArgumentTypeMismatchException.class)
protected ResponseEntity<Object> handleMethodArgumentTypeMismatch(MethodArgumentTypeMismatchException ex,
WebRequest request) {
ApiError apiError = new ApiError(BAD_REQUEST);
apiError.setMessage(String.format("The parameter '%s' of value '%s' could not be converted to type '%s'",
ex.getName(), ex.getValue(), ex.getRequiredType().getSimpleName()));
apiError.setDebugMessage(ex.getMessage());
return buildResponseEntity(apiError);
}
private ResponseEntity<Object> buildResponseEntity(ApiError apiError) {
return new ResponseEntity<>(apiError, apiError.getStatus());
}
}
But, when any exception occurs the application is redirected to /error page rather than being handled by the exception handler class. Can anyone advice on the right way of doing this.
Which exceptions are not been catch by the handler? Are the ConstraintViolationExceptions?
When you do:
#Valid #RequestBody User entity
You are firing the validation in the method, so if the constraints you use to decorate your User class are not fulfilled a MethodArgumentNotValidException will be thrown, not a ConstraintViolationException.
As you are extending ResponseEntityExceptionHandler you should check the code of such class which defines the following method:
#ExceptionHandler({
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
})
#Nullable
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception {
HttpHeaders headers = new HttpHeaders();
if (ex instanceof HttpRequestMethodNotSupportedException) {
HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED;
return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request);
}
else if (ex instanceof HttpMediaTypeNotSupportedException) {
HttpStatus status = HttpStatus.UNSUPPORTED_MEDIA_TYPE;
return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, headers, status, request);
}
else if (ex instanceof HttpMediaTypeNotAcceptableException) {
HttpStatus status = HttpStatus.NOT_ACCEPTABLE;
return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, headers, status, request);
}
else if (ex instanceof MissingPathVariableException) {
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
return handleMissingPathVariable((MissingPathVariableException) ex, headers, status, request);
}
else if (ex instanceof MissingServletRequestParameterException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, headers, status, request);
}
else if (ex instanceof ServletRequestBindingException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleServletRequestBindingException((ServletRequestBindingException) ex, headers, status, request);
}
else if (ex instanceof ConversionNotSupportedException) {
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
return handleConversionNotSupported((ConversionNotSupportedException) ex, headers, status, request);
}
else if (ex instanceof TypeMismatchException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleTypeMismatch((TypeMismatchException) ex, headers, status, request);
}
else if (ex instanceof HttpMessageNotReadableException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, headers, status, request);
}
else if (ex instanceof HttpMessageNotWritableException) {
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, headers, status, request);
}
else if (ex instanceof MethodArgumentNotValidException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleMethodArgumentNotValid((MethodArgumentNotValidException) ex, headers, status, request);
}
else if (ex instanceof MissingServletRequestPartException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleMissingServletRequestPart((MissingServletRequestPartException) ex, headers, status, request);
}
else if (ex instanceof BindException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleBindException((BindException) ex, headers, status, request);
}
else if (ex instanceof NoHandlerFoundException) {
HttpStatus status = HttpStatus.NOT_FOUND;
return handleNoHandlerFoundException((NoHandlerFoundException) ex, headers, status, request);
}
else if (ex instanceof AsyncRequestTimeoutException) {
HttpStatus status = HttpStatus.SERVICE_UNAVAILABLE;
return handleAsyncRequestTimeoutException((AsyncRequestTimeoutException) ex, headers, status, request);
}
else {
// Unknown exception, typically a wrapper with a common MVC exception as cause
// (since #ExceptionHandler type declarations also match first-level causes):
// We only deal with top-level MVC exceptions here, so let's rethrow the given
// exception for further processing through the HandlerExceptionResolver chain.
throw ex;
}
}
So the MethodArgumentNotValidException is been catch here and the handling of such exception is pass to the handleMethodArgumentNotValid method.
The default handleMethodArgumentNotValid just call:
return handleExceptionInternal(ex, null, headers, status, request);
The better option here is to override handleMethodArgumentNotValid in RestExceptionHandler

Spring RestTemplate exchange throws UnhandledHttpStatusException

Overview:
I am going to use RestTemplate to invoke a get request from external REST webservice.
My code is as follows:
#Slf4j
#Component("AccMemberDetailsApiControllerImpl")
public class AccMemberDetailsApiControllerImpl implements MemberDetailsApiController {
private static final String CONTENT_TYPE_HEADER_NAME = "Content-Type";
private static final String AUTHORIZATION_HEADER_NAME = "Authorization";
private static final String USERID_PARAMETER_NAME = "userId";
private static final String VEHICLEID_PARAMETER_NAME = "vehicleId";
private static final ObjectMapper mapper = new ObjectMapper();
/**
* This constant is used to check whether or not the response from ACC is an empty JSON string
*/
private static final String EMPTY_RESPONSE = "{}";
#Value("${com.blss.memberServices.provider.posServiceURL}")
private String accPosServiceURL;
#Autowired
private RestTemplate restTemplate;
#Autowired
private AccTokenUtility accTokenUtility;
#Autowired
private ResourceMessage resourceMessage;
void setAccTokenUtility(AccTokenUtility accTokenUtility) {
this.accTokenUtility = accTokenUtility;
}
void setResourceMessage(ResourceMessage resourceMessage) {
this.resourceMessage = resourceMessage;
}
/**
* #see MemberDetailsApiController#getMemberDetails(String, String)
*/
#Override
public MemberDetailsModel getMemberDetails(String storeId, String membershipIdentifier) {
/**
* Getting CAD token
*/
String token = accTokenUtility.getCadToken();
/**
* Preparing the request
*/
HttpHeaders headers = new HttpHeaders();
// headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.set(CONTENT_TYPE_HEADER_NAME, MediaType.APPLICATION_JSON_VALUE);
headers.set(AUTHORIZATION_HEADER_NAME, token);
HttpEntity<String> entity = new HttpEntity<>(headers);
/**
* Creating the dispatch URL by means of userId and vehicleId
*/
String dispatchURL = accPosServiceURL + "DispatchedEvent/{userId}/{vehicleId}";
/**
* Creating the URL variables and being valued by corresponding method parameters
*/
Map<String, String> parameters = new HashMap<>();
// parameters.put(USERID_PARAMETER_NAME, storeId);
parameters.put(USERID_PARAMETER_NAME, "mr2");
// parameters.put(VEHICLEID_PARAMETER_NAME, membershipIdentifier);
parameters.put(VEHICLEID_PARAMETER_NAME, "VEH1");
/**
* Calling the rest webservice and returning response with body of type {#link AccMemberDetails}
*/
ResponseEntity<String> response;
MemberDetailsModel memberDetailsModel = null;
AccMemberDetails accMemberDetails;
try {
response = restTemplate.exchange(dispatchURL, HttpMethod.GET, entity, String.class, parameters);
if (response == null || StringUtils.isBlank(response.getBody()) || EMPTY_RESPONSE.equals(response.getBody())) {
throw new ResourceNotFoundException(resourceMessage.getMessage(MEMBER_ERROR_NOT_FOUND, storeId, membershipIdentifier));
} else {
accMemberDetails = deserialize(response.getBody(), AccMemberDetails.class);
String accErrorMessage = accMemberDetails.getUserMessage();
if (!StringUtils.isBlank(accErrorMessage)) {
throw new InternalServerException(resourceMessage.getMessage(MEMBER_ERROR_MESSAGE_FROM_API, "ACC", accErrorMessage));
}
memberDetailsModel = convert(accMemberDetails);
}
} catch (RestClientException e) {
handleExceptions(e, storeId, membershipIdentifier);
}
return memberDetailsModel;
}
/**
* This method is responsible for deserializing string REST response into an object of type {#link AccMemberDetails}
*/
<T> T deserialize(final String response, final Class<T> responseClass) {
try {
return mapper.readValue(response, responseClass);
} catch (IOException e) {
throw new InternalServerException(resourceMessage.getMessage(MEMBER_ERROR_MAP_RESPONSE_OBJECT), e);
}
}
/**
* This method is responsible for converting an instance of type {#link AccMemberDetails} to an instance of type
* {#link MemberDetailsModel}
*
* #param accMemberDetails an instance of type {#link AccMemberDetails}
* #return an instance of type {#link MemberDetailsModel}
*/
MemberDetailsModel convert(AccMemberDetails accMemberDetails) {
MemberDetailsModel memberDetailsModel = new MemberDetailsModel();
memberDetailsModel.setEventId(accMemberDetails.getEventId());
memberDetailsModel.setMemberName(accMemberDetails.getMemberName());
memberDetailsModel.setMembershipNumber(accMemberDetails.getMembershipNumber());
memberDetailsModel.setMembershipLevel(accMemberDetails.getPricingLevel());
return memberDetailsModel;
}
/**
* This method is responsible for handling Exceptions may be thrown by ACC REST webservice
*
* #param e an instance of type {#link RestClientException}
* #param storeId an instance of type {#link String} and used in building exception messages
* #param membershipIdentifier an instance of type {#link String} and used in building exception messages
*/
private void handleExceptions(RestClientException e, String storeId, String membershipIdentifier) {
if (e instanceof HttpStatusCodeException) {
HttpStatusCodeException httpStatusCodeException = (HttpStatusCodeException) e;
HttpStatus httpStatusCode = httpStatusCodeException.getStatusCode();
if (404 == httpStatusCode.value()) {
throw new ResourceNotFoundException(resourceMessage.getMessage(MEMBER_ERROR_NOT_FOUND, storeId, membershipIdentifier), e);
} else if (500 == httpStatusCode.value()) {
throw new InternalServerException(resourceMessage.getMessage(MEMBER_SERVER_ERROR, "ACC"), e);
} else {
throw new InternalServerException(resourceMessage.getMessage(MEMBER_HTTP_STATUS_CODE_ERROR, "HttpStatusCodeException", "ACC"), e);
}
} else {
throw new InternalServerException(resourceMessage.getMessage(MEMBER_REST_CLIENT_ERROR, "RestClientException", "ACC"), e);
}
}
Problem
However I got UnhandledHttpStatusException after calling "restTemplate.exchange(dispatchURL, HttpMethod.GET, entity, String.class, parameters);" in the code snippet. the exception stack trace is as follows:
Caused by: org.springframework.web.client.UnknownHttpStatusCodeException: Unknown status code [443] null
at org.springframework.web.client.DefaultResponseErrorHandler.getHttpStatusCode(DefaultResponseErrorHandler.java:60)
at org.springframework.web.client.DefaultResponseErrorHandler.hasError(DefaultResponseErrorHandler.java:50)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:629)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:597)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:565)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:484)
at com.blss.retailServices.memberServices.controllers.impl.acc.AccMemberDetailsApiControllerImpl.getMemberDetails(AccMemberDetailsApiControllerImpl.java:110)
Now I would be grateful if anyone could suggest me a solution.
I called this webservice with curl by using "-v" in order to get more info in response. As a result, I got the same exception (443) from their side. So, It sounds like they should have a better exception handler to return meaningful exception messages.

How to serialize Java object into JSON and return it in servlet filter?

I have this javax.servlet.Filter to check whether client is allowed to access API REST resource.
#Component
public class AuthorizationRequestFilter implements Filter {
public static final String AUTHORIZATION_TOKEN = "X-Access-Token";
#Autowired
#Qualifier("loginService")
private ILoginService loginService;
private void throwUnauthorized(ServletResponse res) throws IOException {
HttpServletResponse response = (HttpServletResponse) res;
response.reset();
response.setHeader("Content-Type", "application/json;charset=UTF-8");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
private void throwForbidden(ServletResponse res) throws IOException {
HttpServletResponse response = (HttpServletResponse) res;
response.reset();
response.setHeader("Content-Type", "application/json;charset=UTF-8");
response.sendError(HttpServletResponse.SC_FORBIDDEN);
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
String accessToken = request.getHeader(AUTHORIZATION_TOKEN);
if (StringUtils.isEmpty(accessToken)) {
throwUnauthorized(res);
} else {
AccountLoginData account = loginService.find(accessToken);
if (account == null) {
throwForbidden(res);
}
}
chain.doFilter(req, res);
}
#Override
public void destroy() {
}
#Override
public void init(FilterConfig arg0) throws ServletException {
}
}
it works but I would like to in these two throw*() methods write to the client JSON with appropriate information. In another part of this application I use these response message objects to inform client what happened.
For example, when record has not been found:
public class NotFoundResponseMessage extends ResponseMessage {
public NotFoundResponseMessage(String message) {
super(HttpStatus.NOT_FOUND, 1, message);
}
}
and
public class ResponseMessage {
private int status;
private int code;
private String message;
private String reason;
public ResponseMessage(int status, int code, String message, String reason) {
Assert.notNull(reason, "Reason must not be null.");
Assert.isTrue(status > 0, "Status must not be empty.");
this.status = status;
this.code = code;
this.message = message;
this.reason = reason;
}
}
My Question
I would like to return JSON with serialized objects (UnauthorizedResponseMessage and ForbiddenResponseMessage) in my javax.servlet.Filter authorization / authentication filter. I use Spring Boot and Jackson library.
How can I manually serialize ResponseMessage into its JSON representation?
How can I write out this JSON back to the client in my filter class?
Edit 1:
private void throwUnauthorized(ServletResponse res) throws IOException {
HttpServletResponse response = (HttpServletResponse) res;
response.reset();
response.setHeader("Content-Type", "application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("{\"foo\":\"boo\"}");
}
Now I can write out JSON but HTTP 500 is returned, because:
java.lang.IllegalStateException: getWriter() has already been called for this response
at org.apache.catalina.connector.Response.getOutputStream(Response.java:544)
Using Jackson convert Object to JSON, the following is an example
ObjectMapper mapper = new ObjectMapper();
String Json = mapper.writeValueAsString(object);
I had the same problem, the complete solution is the following:
try {
restResponse = service.validate(httpReq);
} catch (ForbiddenException e) {
ObjectMapper mapper = new ObjectMapper();
ResponseObject object = new ResponseObject();
object.setStatus(HttpServletResponse.SC_FORBIDDEN);
object.setMessage(e.getMessage());
object.setError("Forbidden");
object.setTimestamp(String.valueOf(new Date().getTime()));
HttpServletResponse httpResp = (HttpServletResponse) response;
httpResp.reset();
httpResp.setHeader("Content-Type","application/json;charset=UTF-8");
httpResp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
String json = mapper.writeValueAsString(object);
response.getWriter().write(json);
return;
}
and the result is:
Just throw your exceptions from the filter and annotate the thrown exception with #ResponseStatus. This way it automatically gets translated to the given http error code. (you can also define the error message)
Code example:
#ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Error while trying to add the feed.")
public class AddFeedException extends Exception {
private static final long serialVersionUID = 290724913968202592L;
public AddFeedException(Throwable throwable) {
super(throwable);
}
}

Categories

Resources