一些请求将POST数据保存为写入请求体的纯XML字符串,我想读取该数据以记录异常.
问题是当我在异常处理程序中请求输入流并尝试读取它时,流返回-1(空).
异常处理程序的签名是:
@ExceptionHandler(Throwable.class) public ModelAndView exception(HttpServletRequest request,HttpServletResponse response,HttpSession session,Throwable arff)
有什么想法吗?有没有办法访问请求体?
我的控制器:
@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",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",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",status); } @RequestMapping("/user/logout/success") public ModelAndView logoutSuccess() { LOG.debug("logout()"); Status status = new Status(0,"Successful logout"); return new ModelAndView("/user/logout/success",status); } @RequestMapping(value = "/user/{id}",method = RequestMethod.POST) public ModelAndView create(@RequestBody UserDTO userDTO,@PathVariable("id") Long id) { return new ModelAndView("user",userService.create(userDTO,id)); } @RequestMapping(value = "/user/{id}",method = RequestMethod.GET) public ModelAndView getUserById(@PathVariable("id") Long id) { return new ModelAndView("user",userService.getUserById(id)); } @RequestMapping(value = "/user/update/{id}",method = RequestMethod.POST) public ModelAndView update(@RequestBody UserDTO userDTO,userService.update(userDTO,id)); } @RequestMapping(value = "/user/all",method = RequestMethod.GET) public ModelAndView list() { return new ModelAndView("user",userService.list()); } @RequestMapping(value = "/user/allowedAccounts",method = RequestMethod.GET) public ModelAndView getAllowedAccounts() { return new ModelAndView("user",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",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,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",st); } }
谢谢
解决方法
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 = "";
我用这个代替了你的代码:
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();
然后我可以从异常处理程序中的InputStream中读取,它可以工作!
如果您仍然无法从InputStream读取,建议您检查如何将xml数据发送到请求正文.
您应该考虑每个请求只能使用Inputstream一次,因此建议您检查是否没有任何其他调用getInputStream().如果你必须调用它两次或更多次,你应该编写一个这样的自定义HttpServletRequestWrapper来创建一个请求体的副本,这样你可以再读一次.
UPDATE
您的意见帮助我重现了这个问题.您使用注释@RequestBody,因此您不要调用getInputStream(),但是Spring会调用它来检索请求的正文.看看类org.springframework.web.bind.annotation.support.HandlerMethodInvoker:如果你使用@RequestBody这个类调用resolveRequestBody方法,等等…最后你不能再从你的ServletRequest中读取InputStream.如果您仍然希望在您自己的方法中使用@RequestBody和getInputStream(),则必须将请求打包到自定义HttpServletRequestWrapper以创建请求正文的副本,以便您可以多次手动读取它.
这是我的包装
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; } }
那么你应该编写一个简单的过滤器来包装请求:
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() { } }
最后,您必须在web.xml中配置过滤器:
<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>
您只能为真正需要的控制器启动过滤器,因此您应该根据需要更改URL格式.
如果您只需要一个控制器中的此功能,您也可以通过@RequestBody注释收到控制器中的请求正文的副本.