java 从 spring 异常处理程序读取 httprequest 内容
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5182417/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Reading httprequest content from spring exception handler
提问by Noam Nevo
I Am using Spring's @ExceptionHandler
annotation to catch exceptions in my controllers.
我正在使用 Spring 的@ExceptionHandler
注释来捕获控制器中的异常。
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).
一些请求将 POST 数据保存为写入请求正文的纯 XML 字符串,我想读取该数据以记录异常。问题是,当我在异常处理程序中请求输入流并尝试从中读取时,流返回 -1(空)。
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
谢谢
采纳答案by javanna
I've tried your code and I've found some mistakes in the exception handler, when you read from the InputStream
:
我已经尝试了你的代码,当你从以下内容中读取时,我发现异常处理程序中存在一些错误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();
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.
然后我可以从InputStream
异常处理程序中读取,它有效!如果您仍然无法读取InputStream
,我建议您检查如何将 xml 数据 POST 到请求正文。您应该考虑到Inputstream
每个请求只能消耗一次,因此我建议您检查是否没有任何其他调用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:
更新
您的评论帮助我重现了这个问题。您使用注解@RequestBody,因此您确实没有调用getInputStream()
,但Spring 会调用它来检索请求的正文。看看这个类org.springframework.web.bind.annotation.support.HandlerMethodInvoker
:如果你使用@RequestBody
这个类调用resolveRequestBody
方法,等等......最后你不能再InputStream
从你的ServletRequest
. 如果您仍然想在自己的方法中同时使用@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;
}
}
Then you should write a simple Filter
to wrap the request:
然后你应该写一个简单的Filter
来包装请求:
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 have to configure your filter in your web.xml:
最后,您必须在 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.
您只能为真正需要它的控制器触发过滤器,因此您应该根据需要更改 url-pattern。
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.
如果您只需要在一个控制器中使用此功能,您还可以在通过@RequestBody
注解收到请求正文时在该控制器中制作一份请求正文的副本。
回答by krishnakumarp
Recently I faced this issue and solved it slightly differently. With spring boot 1.3.5.RELEASE
最近我遇到了这个问题,并以稍微不同的方式解决了它。带弹簧靴 1.3.5.RELEASE
The filter was implemented using the Spring class ContentCachingRequestWrapper. This wrapper has a method getContentAsByteArray()which can be invoked multiple times.
过滤器是使用 Spring 类ContentCachingRequestWrapper 实现的。这个包装器有一个可以多次调用的方法getContentAsByteArray()。
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 the filter to the 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);
....
}
}
回答by hakan
I had the same problem and solved it with HttpServletRequestWrapper
as described above and it worked great. But then, I found another solution with extending HttpMessageConverter, in my case that was MappingHymanson2HttpMessageConverter
.
我遇到了同样的问题,并HttpServletRequestWrapper
按照上述方法解决了它,而且效果很好。但是后来,我找到了另一个扩展 HttpMessageConverter 的解决方案,在我的例子中是MappingHymanson2HttpMessageConverter
.
public class CustomJsonHttpMessageConverter extends MappingHymanson2HttpMessageConverter{
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
is used.
com.sun.xml.internal.messaging.saaj.util.TeeInputStream
用来。
In spring mvc config
在 spring mvc 配置中
<mvc:annotation-driven >
<mvc:message-converters>
<bean class="com.company.remote.rest.util.CustomJsonHttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
In @ExceptionHandler method
在@ExceptionHandler 方法中
@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());
}
I hope it helps :)
我希望它有帮助:)