【问题标题】:Reading httprequest content from spring exception handler从 Spring 异常处理程序中读取 httprequest 内容
【发布时间】:2011-07-08 03:12:15
【问题描述】:

我正在使用 Spring 的 @ExceptionHandler 注释在我的控制器中捕获异常。

一些请求将 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","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);
    }
}

谢谢

【问题讨论】:

    标签: java spring spring-mvc exceptionhandler


    【解决方案1】:

    我试过你的代码,发现异常处理程序中有一些错误,当你从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 = "";
    

    我已经用这个替换了你的代码:

    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 来复制请求正文,这样你就可以阅读更多次。

    更新
    你的 cmets 帮助我重现了这个问题。您使用注解@RequestBody,因此您确实没有调用getInputStream(),但Spring 调用它来检索请求的正文。看看org.springframework.web.bind.annotation.support.HandlerMethodInvoker 类:如果你使用@RequestBody 这个类调用resolveRequestBody 方法,等等......最后你不能再从ServletRequest 中读取InputStream。如果您仍想在自己的方法中同时使用@RequestBodygetInputStream(),则必须将请求包装到自定义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;
        }
    }
    

    那么你应该写一个简单的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() {
    
        }
    
    }
    

    最后,你必须在你的 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 注解收到请求时,在该控制器中复制请求正文。

    【讨论】:

    • 感谢您的回复,我仍然无法阅读请求正文。没有抛出异常,只是读者返回 null 意味着它是空的。也许我错过了有关春季异常处理程序的一些信息。您如何阅读请求?
    • 我刚刚将您的代码(连同我的更正)复制到了一个新项目中。我通过带有 enctype="multipart/form-data" 和输入 type="file" 的静态 html 表单调用控制器来上传文件。我添加了一个 RequestMapping 注释和只抛出 RuntimeException 的相应方法。在异常处理程序方法中,我能够记录通过表单上传的整个文件。当您进入控制器时,您的输入流似乎是空的,或者已经通过另一个对 getInputStream() 的调用而被消耗。什么版本的 Spring?
    • @javanna Spring 3.0 版,我没有明确地调用 getInputStream()。例如,引发异常的方法之一是 create() 方法,它接收 UserDTO 并将其保存到数据库。异常在服务中被抛出并在控制器中被捕获。还是没有新进展:)
    • 我已经用 Spring 3.0.5 对其进行了测试。我想帮助你,但我无法重现你的问题,所以这对我来说非常困难。我应该查看整个代码和 spring 配置来理解并重现问题。
    • @javanna - 没问题,你想看什么配置文件?
    【解决方案2】:

    最近我遇到了这个问题,但解决方法略有不同。带spring boot 1.3.5.RELEASE

    过滤器是使用 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() {
        }
    }
    

    将过滤器添加到链中

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

    在异常处理程序中。

    @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);
            ....
        }
    }
    

    【讨论】:

    • 为什么需要if (request instanceof ServletRequestWrapper)
    【解决方案3】:

    我遇到了同样的问题,如上所述用HttpServletRequestWrapper 解决了它,效果很好。但后来,我找到了另一个扩展 HttpMessageConverter 的解决方案,在我的例子中是 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

    在 spring mvc 配置中

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

    在@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());
    }
    

    希望对你有帮助:)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-03-20
      • 1970-01-01
      • 2011-10-08
      • 2019-12-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多