使用@RequestBody并转发到另一个端点会抛出异常Stream关闭

时间:2017-05-09 06:40:14

标签: java spring

我的Java spring REST API控制器如下所示:

public void signup(@RequestBody RequestBody requestBody) throws IOException, ServletException {

我得到了这个例外:

Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Stream closed; nested exception is java.io.IOException: Stream closed

这是因为我想将请求体强制转换为RequestBody类(打开请求输入流并完成它),并将其转发/重定向到另一个端点。

实际的控制器是:

    @RequestMapping(value = "/signup", method = RequestMethod.POST)
    public void signup(@RequestBody CustomUserDetails user, HttpServletRequest request, HttpServletResponse response) {

        String userName = user.getUsername();
        logger.debug("User signup attempt with username: " + userName);

        try{
            if(customUserDetailsService.exists(userName))
            {
                logger.debug("Duplicate username " + userName);
userName + " already exists");
                String newUrl = "login";
                RequestDispatcher view = request.getRequestDispatcher(newUrl);
                view.forward(request, response);
            } else {
                customUserDetailsService.save(user);
                authenticateUserAndSetSession(user, response);
            }
        } catch(Exception ex) {

        }
    }

我应该如何处理?

5 个答案:

答案 0 :(得分:5)

您可以转发到 ExceptionHandler 中的登录页面,如下所示:

@RequestMapping(value = "/signup", method = RequestMethod.POST)
public void signup(@RequestBody CustomUserDetails user, HttpServletResponse response) {

    String userName = user.getUsername();
    logger.debug("User signup attempt with username: " + userName);

    //try{
    if (customUserDetailsService.exists(userName)) {
        logger.debug("Duplicate username " + userName);
        throw new SignupException(userName + " already exists");
    } else {
        customUserDetailsService.save(user);
        authenticateUserAndSetSession(user, response);
    }
    /*} catch(Exception ex) {

    }*/
}

在同一个Controller中定义一个ExceptionHandler:

@ExceptionHandler(SignupException.class)
public String duplicateName() {
    return "login";
}

并且SignupException可以是这样的:

public class SignupException extends RuntimeException {
    public SignupException(String message) {
        super(message);
    }

    public SignupException() {
    }
}

答案 1 :(得分:4)

问题的根源是@RequestBody RequestBody requestBodyHttpServletRequest request

不允许在同一请求中打开两次输入流。在您的情况下,系统应打开输入流以提取请求主体,然后向前传播以重用。

要处理它,您应该避免多次使用相同的请求流。可能的解决方案是:

  1. 换行请求
  2. 复制请求正文
  3. 使用spring native forward

答案 2 :(得分:3)

请求正文对象是一个只能读取一次的流。因此,转发它并非易事。解决此问题的一种方法是创建一个过滤器,该过滤器读取输入流并将输入流替换为可以多次读取的内容。例子可以在另一个答案中找到:

How can I read request body multiple times in Spring 'HandlerMethodArgumentResolver'?

至于你的方法,还有另一个问题:

public void signup(@RequestBody RequestBody requestBody)

据我所知,RequestBody是一个注释,你不能像那样映射它。但是要获取原始数据,可以将其映射为String。

public void signup(@RequestBody String requestBody)

然后你可以手动对要使用String请求体转发它的api进行REST调用。只需确保将内容类型设置为原始类型,我假设在这种情况下将是JSON。

答案 3 :(得分:2)

我认为您正在尝试使用RequestBody转发到网址,请检查一下 Spring 3.2 forward request with new object获得答案。

创建对象并将其作为第一个控制器中的属性添加到请求中,

request.setAttribute("user",user),
return "forward:/login";

答案 4 :(得分:1)

  1. RequestBody是一个注释,用于处理您的请求对象作为预期的类反序列化。它们通过提取消息转换的逻辑并使其成为一个方面来帮助您避免样板代码。
  2. 您不能将RequestBody 直接作为任何控制器中的对象。它不是用这种方式设计的。
  3. 虽然无法在RequestBody中获得RestController,但您无法判断这是好事还是坏事。
  4. 如果你必须使用新的控制器/端点来处理。那么我认为更好的方法是将CustomUserDetails作为@RequestBody在控制器中然后处理它。 然后调用嵌套方法或服务进一步处理,而不是考虑转发到另一个控制器。然后从控制器返回响应。