I Am using Spring's @ExceptionHandler
annotation to catch exceptions in my controllers.
Some requests hold POST data as plain XML string written to the request body, I want to read that data in order to log the exception. The problem is that when i request the inputstream in the exception handler and try to read from it the stream returns -1 (empty).
The exception handler signature is:
@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 you
I've tried your code and I've found some mistakes in the exception handler, when you read from the
InputStream
:I've replaced your code with this one:
Then I'm able to read from
InputStream
in the exception handler, it works! If you can't still read fromInputStream
, I suggest you to check how you POST xml data to the request body. You should consider that you can consume theInputstream
only one time per request, so I suggest you to check that there isn't any other call togetInputStream()
. If you have to call it two or more times you should write a customHttpServletRequestWrapper
like this to make a copy of the request body, so you can read it more times.UPDATE
Your comments has helped me to reproduce the issue. You use the annotation @RequestBody, so it's true that you don't call
getInputStream()
, but Spring invokes it to retrieve the request's body. Have a look at the classorg.springframework.web.bind.annotation.support.HandlerMethodInvoker
: if you use@RequestBody
this class invokesresolveRequestBody
method, and so on... finally you can't read anymore theInputStream
from yourServletRequest
. If you still want to use both@RequestBody
andgetInputStream()
in your own method, you have to wrap the request to a customHttpServletRequestWrapper
to make a copy of the request body, so you can manually read it more times. This is my wrapper:Then you should write a simple
Filter
to wrap the request:Finally, you have to configure your filter in your web.xml:
You can fire your filter only for controllers that really needs it, so you should change the url-pattern according to your needs.
If you need this feature in only one controller, you can also make a copy of the request body in that controller when you receive it through the
@RequestBody
annotation.