Reading httprequest content from spring exception handler

I am using Spring @ExceptionHandler annotation to throw exceptions in my controllers.

Some requests store POST data as a simple XML string written to the request body, I want to read this data to catch an exception. The problem is that when I request the input stream in the exception handler and try to read it, the stream returns -1 (empty).

Exception Handler Signature:

 @ExceptionHandler(Throwable.class) public ModelAndView exception(HttpServletRequest request, HttpServletResponse response, HttpSession session, Throwable arff) 

Any thoughts? Is there a way to access the request body?

My controller:

 @Controller @RequestMapping("/user/**") public class UserController { static final Logger LOG = LoggerFactory.getLogger(UserController.class); @Autowired IUserService userService; @RequestMapping("/user") public ModelAndView getCurrent() { return new ModelAndView("user","response", userService.getCurrent()); } @RequestMapping("/user/firstLogin") public ModelAndView firstLogin(HttpSession session) { userService.logUser(session.getId()); userService.setOriginalAuthority(); return new ModelAndView("user","response", userService.getCurrent()); } @RequestMapping("/user/login/failure") public ModelAndView loginFailed() { LOG.debug("loginFailed()"); Status status = new Status(-1,"Bad login"); return new ModelAndView("/user/login/failure", "response",status); } @RequestMapping("/user/login/unauthorized") public ModelAndView unauthorized() { LOG.debug("unauthorized()"); Status status = new Status(-1,"Unauthorized.Please login first."); return new ModelAndView("/user/login/unauthorized","response",status); } @RequestMapping("/user/logout/success") public ModelAndView logoutSuccess() { LOG.debug("logout()"); Status status = new Status(0,"Successful logout"); return new ModelAndView("/user/logout/success", "response",status); } @RequestMapping(value = "/user/{id}", method = RequestMethod.POST) public ModelAndView create(@RequestBody UserDTO userDTO, @PathVariable("id") Long id) { return new ModelAndView("user", "response", userService.create(userDTO, id)); } @RequestMapping(value = "/user/{id}", method = RequestMethod.GET) public ModelAndView getUserById(@PathVariable("id") Long id) { return new ModelAndView("user", "response", userService.getUserById(id)); } @RequestMapping(value = "/user/update/{id}", method = RequestMethod.POST) public ModelAndView update(@RequestBody UserDTO userDTO, @PathVariable("id") Long id) { return new ModelAndView("user", "response", userService.update(userDTO, id)); } @RequestMapping(value = "/user/all", method = RequestMethod.GET) public ModelAndView list() { return new ModelAndView("user", "response", userService.list()); } @RequestMapping(value = "/user/allowedAccounts", method = RequestMethod.GET) public ModelAndView getAllowedAccounts() { return new ModelAndView("user", "response", userService.getAllowedAccounts()); } @RequestMapping(value = "/user/changeAccount/{accountId}", method = RequestMethod.GET) public ModelAndView changeAccount(@PathVariable("accountId") Long accountId) { Status st = userService.changeAccount(accountId); if (st.code != -1) { return getCurrent(); } else { return new ModelAndView("user", "response", st); } } /* @RequestMapping(value = "/user/logout", method = RequestMethod.GET) public void perLogout(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { userService.setOriginalAuthority(); response.sendRedirect("/marketplace/user/logout/spring"); } */ @ExceptionHandler(Throwable.class) public ModelAndView exception(HttpServletRequest request, HttpServletResponse response, HttpSession session, Throwable arff) { Status st = new Status(); try { Writer writer = new StringWriter(); byte[] buffer = new byte[1024]; //Reader reader2 = new BufferedReader(new InputStreamReader(request.getInputStream())); InputStream reader = request.getInputStream(); int n; while ((n = reader.read(buffer)) != -1) { writer.toString(); } String retval = writer.toString(); retval = ""; } catch (IOException e) { e.printStackTrace(); } return new ModelAndView("profile", "response", st); } } 

thank

+9
java spring spring-mvc
Mar 03 2018-11-11T00:
source share
3 answers

I tried my code and I found some errors in the exception handler when you read from InputStream :

 Writer writer = new StringWriter(); byte[] buffer = new byte[1024]; //Reader reader2 = new BufferedReader(new InputStreamReader(request.getInputStream())); InputStream reader = request.getInputStream(); int n; while ((n = reader.read(buffer)) != -1) { writer.toString(); } String retval = writer.toString(); retval = ""; 

I replaced your code with the following:

 BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream())); String line = ""; StringBuilder stringBuilder = new StringBuilder(); while ( (line=reader.readLine()) != null ) { stringBuilder.append(line).append("\n"); } String retval = stringBuilder.toString(); 

Then I can read from InputStream in the exception handler, it works! If you still cannot read from InputStream , I suggest you check how your XML POST data is in the request body. You should consider that you can only consume an InputStream once per request, so I suggest you check if there is another call to getInputStream() . If you need to call him two or more times, you should write a custom HttpServletRequestWrapper like this to make a copy of the request body so that you can read it more times.

UPDATE
Your comments helped me reproduce this issue. You are using the @RequestBody annotation, so it is true that you are not calling getInputStream() , but Spring is calling it to retrieve the request body. Take a look at the org.springframework.web.bind.annotation.support.HandlerMethodInvoker class: if you use @RequestBody , this class calls the resolveRequestBody method, etc ... finally, you can no longer read InputStream from your ServletRequest . If you still want to use both @RequestBody and getInputStream() in your own method, you must wrap the request with a custom HttpServletRequestWrapper to make a copy of the request body so you can manually read it more times. This is my cover:

 public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper { private static final Logger logger = Logger.getLogger(CustomHttpServletRequestWrapper.class); private final String body; public CustomHttpServletRequestWrapper(HttpServletRequest request) { super(request); StringBuilder stringBuilder = new StringBuilder(); BufferedReader bufferedReader = null; try { InputStream inputStream = request.getInputStream(); if (inputStream != null) { bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String line = ""; while ((line = bufferedReader.readLine()) != null) { stringBuilder.append(line).append("\n"); } } else { stringBuilder.append(""); } } catch (IOException ex) { logger.error("Error reading the request body..."); } finally { if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException ex) { logger.error("Error closing bufferedReader..."); } } } body = stringBuilder.toString(); } @Override public ServletInputStream getInputStream() throws IOException { final StringReader reader = new StringReader(body); ServletInputStream inputStream = new ServletInputStream() { public int read() throws IOException { return reader.read(); } }; return inputStream; } } 

Then you should write a simple Filter to wrap the query:

 public class MyFilter implements Filter { public void init(FilterConfig fc) throws ServletException { } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(new CustomHttpServletRequestWrapper((HttpServletRequest)request), response); } public void destroy() { } } 

Finally, you need to configure your filter in the web.xml file:

 <filter> <filter-name>MyFilter</filter-name> <filter-class>test.MyFilter</filter-class> </filter> <filter-mapping> <filter-name>MyFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 

You can only run your filter on the controllers that it really needs, so you should modify the url template to suit your needs.

If you need this function in only one controller, you can also make a copy of the request body in this controller when you receive it using the @RequestBody annotation.

+9
Mar 07 2018-11-11T00:
source

I had the same problem and it was solved using HttpServletRequestWrapper as described above and it worked perfectly. But then I found another solution with the HttpMessageConverter extension, in my case it was MappingJackson2HttpMessageConverter .

 public class CustomJsonHttpMessageConverter extends MappingJackson2HttpMessageConverter{ public static final String REQUEST_BODY_ATTRIBUTE_NAME = "key.to.requestBody"; @Override public Object read(Type type, Class<?> contextClass, final HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { final ByteArrayOutputStream writerStream = new ByteArrayOutputStream(); HttpInputMessage message = new HttpInputMessage() { @Override public HttpHeaders getHeaders() { return inputMessage.getHeaders(); } @Override public InputStream getBody() throws IOException { return new TeeInputStream(inputMessage.getBody(), writerStream); } }; RequestContextHolder.getRequestAttributes().setAttribute(REQUEST_BODY_ATTRIBUTE_NAME, writerStream, RequestAttributes.SCOPE_REQUEST); return super.read(type, contextClass, message); } } 

com.sun.xml.internal.messaging.saaj.util.TeeInputStream .

In spring mvc config

 <mvc:annotation-driven > <mvc:message-converters> <bean class="com.company.remote.rest.util.CustomJsonHttpMessageConverter" /> </mvc:message-converters> </mvc:annotation-driven> 

In the @ExceptionHandler method

 @ExceptionHandler(Exception.class) public ResponseEntity<RestError> handleException(Exception e, HttpServletRequest httpRequest) { RestError error = new RestError(); error.setErrorCode(ErrorCodes.UNKNOWN_ERROR.getErrorCode()); error.setDescription(ErrorCodes.UNKNOWN_ERROR.getDescription()); error.setDescription(e.getMessage()); logRestException(httpRequest, e); ResponseEntity<RestError> responseEntity = new ResponseEntity<RestError>(error,HttpStatus.INTERNAL_SERVER_ERROR); return responseEntity; } private void logRestException(HttpServletRequest request, Exception ex) { StringWriter sb = new StringWriter(); sb.append("Rest Error \n"); sb.append("\nRequest Path"); sb.append("\n----------------------------------------------------------------\n"); sb.append(request.getRequestURL()); sb.append("\n----------------------------------------------------------------\n"); Object requestBody = request.getAttribute(CustomJsonHttpMessageConverter.REQUEST_BODY_ATTRIBUTE_NAME); if(requestBody != null) { sb.append("\nRequest Body\n"); sb.append("----------------------------------------------------------------\n"); sb.append(requestBody.toString()); sb.append("\n----------------------------------------------------------------\n"); } LOG.error(sb.toString()); } 

Hope this helps :)

+5
Feb 09 '13 at 11:37
source

I recently ran into this problem and solved it a little differently. Using spring boot 1.3.5.RELEASE

The filter is implemented using the spring class ContentCachingRequestWrapper . This shell has a getContentAsByteArray () method, which can be called several times.

 import org.springframework.web.util.ContentCachingRequestWrapper; public class RequestBodyCachingFilter implements Filter { public void init(FilterConfig fc) throws ServletException { } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(new ContentCachingRequestWrapper((HttpServletRequest)request), response); } public void destroy() { } } 

Added filter to chain

 @Bean public RequestBodyCachingFilter requestBodyCachingFilter() { log.debug("Registering Request Body Caching filter"); return new RequestBodyCachingFilter(); } 

In the exception handler.

 @ControllerAdvice(annotations = RestController.class) public class GlobalExceptionHandlingControllerAdvice { private ContentCachingRequestWrapper getUnderlyingCachingRequest(ServletRequest request) { if (ContentCachingRequestWrapper.class.isAssignableFrom(request.getClass())) { return (ContentCachingRequestWrapper) request; } if (request instanceof ServletRequestWrapper) { return getUnderlyingCachingRequest(((ServletRequestWrapper)request).getRequest()); } return null; } @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(Throwable.class) public @ResponseBody Map<String, String> conflict(Throwable exception, HttpServletRequest request) { ContentCachingRequestWrapper underlyingCachingRequest = getUnderlyingCachingRequest(request); String body = new String(underlyingCachingRequest.getContentAsByteArray(),Charsets.UTF_8); .... } } 
+2
Oct 07 '16 at 16:13
source



All Articles