从spring异常处理程序中读取httprequest内容 [英] Reading httprequest content from spring exception handler
问题描述
我使用Spring的 @ExceptionHandler
注释来捕获控制器中的异常。
I Am using Spring's @ExceptionHandler
annotation to catch exceptions in my controllers.
某些请求保存POST数据作为写入请求体的纯XML字符串,我想读取该数据以便记录异常。
问题是当我在异常处理程序中请求输入流并尝试从中读取时,流返回-1(空)。
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).
异常处理程序签名是:
@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?
我的控制器:
@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);
}
}
谢谢
推荐答案
我已经尝试过你的代码,当你从 InputStream中读取$ c时,我在异常处理程序中发现了一些错误$ c>:
I've tried your code and I've found some mistakes in the exception handler, when you read from the 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've replaced your code with this one:
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数据发送到请求正文。
您应该考虑每次请求只能消耗一次输入流
,所以我建议您检查是否有其他任何对<$ c的调用$ C>的getInputStream()。如果你必须调用它两次或更多次,你应该编写一个自定义的 HttpServletRequestWrapper
来制作一个请求体的副本,这样你就可以多读一遍。
Then I'm able to read from InputStream
in the exception handler, it works!
If you can't still read from InputStream
, I suggest you to check how you POST xml data to the request body.
You should consider that you can consume the Inputstream
only one time per request, so I suggest you to check that there isn't any other call to getInputStream()
. If you have to call it two or more times you should write a custom HttpServletRequestWrapper
like this to make a copy of the request body, so you can read it more times.
更新
您的评论有助于我重现此问题。你使用注释@RequestBody,所以你不调用 getInputStream()
,但是Spring调用它来检索请求的主体。看看类 org.springframework.web.bind.annotation.support.HandlerMethodInvoker
:如果你使用 @RequestBody
这个类调用 resolveRequestBody
方法,等等......最后你不能再读取 InputStream
了的ServletRequest
。如果您仍想在自己的方法中同时使用 @RequestBody
和 getInputStream()
,则必须包装请求到自定义 HttpServletRequestWrapper
来制作请求正文的副本,这样你就可以多次手动读取它。
这是我的包装器:
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 class org.springframework.web.bind.annotation.support.HandlerMethodInvoker
: if you use @RequestBody
this class invokes resolveRequestBody
method, and so on... finally you can't read anymore the InputStream
from your ServletRequest
. If you still want to use both @RequestBody
and getInputStream()
in your own method, you have to wrap the request to a custom HttpServletRequestWrapper
to make a copy of the request body, so you can manually read it more times.
This is my wrapper:
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 request:
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中配置过滤器:
Finally, you have to configure your filter in your 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>
您只能为真正需要的控制器启动过滤器,因此您应该更改网址模式根据您的需要。
You can fire your filter only for controllers that really needs it, so you should change the url-pattern according to your needs.
如果您只在一个控制器中需要此功能,您还可以在通过<接收到该控制器时在该控制器中复制该请求正文。 code> @RequestBody 注释。
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.
这篇关于从spring异常处理程序中读取httprequest内容的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!