在GET之后的POST与重定向之后重定向

时间:2016-10-08 17:54:47

标签: spring http spring-mvc

我正在开发一个Spring项目。这是我的基本控制器:

@Controller
public class Editor {

private static final String EDITOR_URL = "/editor";

@RequestMapping(value = EDITOR_URL, method = {POST, GET})
public ModelAndView edit(HttpServletResponse response,
        HttpServletRequest request,
        RedirectAttributes redirectAttributes, 
        @RequestParam Map<String, String> allRequestParams) {

    // The code is trimmed to keep it short
    // It doesn't really matter where it gets the URL, it works fine
    String redirectURL = getRedirectUrl();
    // redirectURL is going to be /editor/pad.html
    return new ModelAndView("redirect:" + redirectUrl);
}

来自web.xml:

<servlet-mapping>
    <servlet-name>edm</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

我有jetty嵌入式,我正在尝试集成测试:

@Test
public void redirectToEditPadSuccess() throws Exception {

    HttpHeaders requestHeaders = new HttpHeaders();

    UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(END_POINT + "/edm/editor")
            .queryParam("param1", "val1")
            .queryParam("param2", "val2");

    HttpEntity<?> entity = new HttpEntity<>(requestHeaders);

    HttpEntity<String> response = restTemplate.exchange(
            builder.build().encode().toUri(),
            HttpMethod.POST,
            entity,
            String.class);

    HttpHeaders httpResponseHeaders = response.getHeaders();

    List<String> httpReponseLocationHeader = httpResponseHeaders.get("Location");
    assertTrue(httpReponseLocationHeader.size() == 1);

    String redirectLocation = httpReponseLocationHeader.get(0);
    URL redirectURL = new URL(redirectLocation);

    assertEquals("/edm/editor/pad.html", redirectURL.getPath());

}

因此,当我执行上述操作时,它工作正常,我得到一个绿色的OK标志。

现在,控制器接受POST和GET方法。如果我使用GET方法执行测试(用HttpMethod.GET替换HttpMethod.POST),结果将是404.

日志显示:

WARN  org.springframework.web.servlet.PageNotFound - No mapping found for HTTP request with URI [/edm/editor/pad.html] in DispatcherServlet with name 'edm'

我尝试将应用程序调试到DispatcherServlet,奇怪的是,使用GET,在302 /重定向响应之后,再次调用Dispatcher并将其转换为200 - 不知道如何以及为什么。

1 个答案:

答案 0 :(得分:1)

我将尝试解释发生了什么,然后提供解决方案。

首先让我们忘记您正在运行休息案例,并假设该请求来自浏览器。

方案1:浏览器发出GET请求,服务器以重定向进行响应。

在这种情况下,浏览器会将响应状态代码读取为302,并使用Location响应标头发出另一个请求。用户看到快速重新加载但没有发现任何错误。

场景2:浏览器发出POST请求,服务器以重定向进行响应。

在这种情况下,浏览器确实遵循响应代码并发出重定向,但是,第二个请求是GET请求,并且原始请求正文在第二个请求中丢失。这是因为严格按照HTTP标准,浏览器无法在没有用户明确请求的情况下将数据“重新发布”到服务器。 (某些浏览器会提示用户并询问他们是否要重新发布)

现在,在您的代码中,RestTemplate正在使用我认为是默认HttpClientFactory的内容,很可能就是这个:https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/main/java/org/springframework/http/client/SimpleClientHttpRequestFactory.java

这就是RestTemplate处理上述两种情况的方式:

方案1:Rest模板发出GET请求,服务器响应重定向。 这里Rest模板实例将完全像浏览器一样工作。这就是为什么要提出两个请求的原因,第二个请求是/edm/editor/pad.html

Scenario 2 : Rest Template issues a POST request, and the server responds with a redirect. 在这种情况下,Rest模板将在第一次调用后停止,因为它无法自动覆盖您的请求方法并将其更改为GET,并且它无法像浏览器那样提示您获得权限。

解决方案:创建RestTemplate的实例时,将其传递给客户端工厂的重写版本,例如

new RestTemplate(new SimpleClientHttpRequestFactory() {
     protected void prepareConnection(HttpURLConnection conn, String httpMethod) throws IOException { 
       super.prepareConnection(conn, httpMethod);    
       conn.setInstanceFollowRedirects(false); 
     } 
});

这将指示休息模板在第一个请求后停止。

对于冗长的回答感到抱歉,但我希望这能澄清事情。