从spring异常处理程序中读取httprequest内容 [英] Reading httprequest content from spring exception handler

查看:335
本文介绍了从spring异常处理程序中读取httprequest内容的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用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中读取

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆