从spring异常处理程序中读取httprequest内容

Noa*_*evo 13 java spring spring-mvc exceptionhandler

我使用Spring的@ExceptionHandler注释来捕获控制器中的异常.

有些请求将POST数据保存为写入请求体的纯XML字符串,我想读取该数据以便记录异常.问题是,当我在异常处理程序中请求输入流并尝试从中读取时,流返回-1(空).

异常处理程序签名是:

@ExceptionHandler(Throwable.class)
public ModelAndView exception(HttpServletRequest request, HttpServletResponse response, HttpSession session, Throwable arff)
Run Code Online (Sandbox Code Playgroud)

有什么想法吗?有没有办法访问请求正文?

我的控制器:

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

谢谢

jav*_*nna 11

我已经尝试过你的代码了,当你从以下内容读取时,我在异常处理程序中发现了一些错误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 = "";
Run Code Online (Sandbox Code Playgroud)

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

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();
Run Code Online (Sandbox Code Playgroud)

然后,我能够从InputStream异常处理程序中读取它,它的工作原理!如果您仍然无法阅读InputStream,我建议您检查如何将xml数据发送到请求正文.您应该考虑Inputstream每次请求只能消耗一次,所以我建议您检查没有任何其他呼叫getInputStream().如果你必须两次或多次调用它,你应该写一个HttpServletRequestWrapper这样的自定义来制作请求体的副本,这样你就可以读更多次了.

更新
您的评论帮助我重现了这个问题.你使用注释@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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你应该写一个简单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() {

    }

}
Run Code Online (Sandbox Code Playgroud)

最后,您必须在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>
Run Code Online (Sandbox Code Playgroud)

您只能为真正需要它的控制器启动过滤器,因此您应该根据需要更改URL模式.

如果仅在一个控制器中需要此功能,则还可以在通过@RequestBody注释接收到该控制器中的请求主体时复制该请求主体.


kri*_*arp 8

最近我遇到了这个问题并且稍有不同地解决了它.随着春季启动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() {
    }
}
Run Code Online (Sandbox Code Playgroud)

在链中添加了过滤器

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

在异常处理程序中.

@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);
        ....
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

我遇到了同样的问题,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);
    }

}
Run Code Online (Sandbox Code Playgroud)

com.sun.xml.internal.messaging.saaj.util.TeeInputStream 用来.

在春季mvc配置

<mvc:annotation-driven >
    <mvc:message-converters>
        <bean class="com.company.remote.rest.util.CustomJsonHttpMessageConverter" />
    </mvc:message-converters>
</mvc:annotation-driven>
Run Code Online (Sandbox Code Playgroud)

在@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());
}
Run Code Online (Sandbox Code Playgroud)

我希望它有帮助:)