spring - RedirectView 不使用位置的相对路径

标签 spring spring-mvc reverse-proxy

我有一个 Web 服务设置,可以从根目录 ('/') 重定向到页面 ('my/page.html')。

因此,http://localhost:8080/ 应重定向到http://localhost:8080/my/page.html

这是代码:

@RequestMapping(method = RequestMethod.GET, value = "/")
public RedirectView localRedirect()
{
    final RedirectView redirectView = new RedirectView();
    redirectView.setContextRelative(true);
    redirectView.setUrl("/my/page.html");

    return redirectView;
}

我的期望是重定向响应将其 location header 设置为相对路径:/my/page.html。然而,实际上,它被设置为完整路径:http://localhost/my/page.html

这会导致一些问题,因为该应用程序运行在 Docker 容器中,该容器认为它正在为端口 80 提供服务;您可能已经注意到,完整路径在 URL 中删除了 :8080 端口说明符。 Docker 容器在反向代理后面运行,它将对 8080 的请求映射到容器。如果 location header 是相对的并设置为 /my/page.html,则期望浏览器客户端将使用正确的主机名 (localhost:8080 code>),因此它将被反向代理重定向到正确的页面。

正如您从我的代码中看到的,我尝试将 RedirectView 对象中的 ContextRelative 选项设置为 true。我还缺少其他东西吗?

编辑

@RequestMapping(method = RequestMethod.GET, value = "/")
public void localRedirect(HttpServletResponse response) {
    response.setStatus(HttpServletResponse.SC_FOUND);
    response.setHeader("Location", "/my/page.html");
}

我已经使用上面的代码进行了重定向。但是,我仍然很好奇是否有人知道如何使用 Spring 的 RedirectViewRedirectStrategy 来完成上述任务,我很乐意接受该解决方案。

最佳答案

你的代码的行为是正确的,因为你说你的redirectview url在上下文中是相对的,但它是一个有效的url,因此spring将它构建为一个url,/my/page.html不是一个有效的网址。说问题是你应该以全双工方式配置 url 重写并在反向代理中解决问题,事实上,对于代码透视,如果它在 80 端口上提供服务的服务器上运行,代码将映射你的 url在 80 上,否则您应该手写您的网址,如下所示:

@RequestMapping(method = RequestMethod.GET, value = "/")
public RedirectView localRedirect()
{
    final RedirectView redirectView = new RedirectView();

    redirectView.setUrl("http://localhost:8080/my/index.html");
    redirectView.setHosts();
    return redirectView;
}

我尝试在我的电脑上运行两个应用程序实例,一个在 80 上运行,另一个在 8080 上运行,并且重定向工作正常

更新:

当您使用 RedirectView 时,重定向会通过 HttpServletResponse.sendRedirect 调用在服务器端发生,当然,如果您的服务器位于反向代理后面,您的服务器端应用程序将无法知道这一点。 当您在 Controller 中使用set Location Header 时,它是一个纯字符串,不会从您的servlet 环境传递。重定向发生在您的浏览器中,在这种情况下,您会收到一个相对 URL,因为您在 Controller 中设置了一个纯字符串,并且您的浏览器已经知道已被代理的服务器。

更新2 RedirectView 类的核心逻辑是一个名为 sendRedirect(...) 的 protected 方法。

/**
     * Send a redirect back to the HTTP client
     * @param request current HTTP request (allows for reacting to request method)
     * @param response current HTTP response (for sending response headers)
     * @param targetUrl the target URL to redirect to
     * @param http10Compatible whether to stay compatible with HTTP 1.0 clients
     * @throws IOException if thrown by response methods
     */
    protected void sendRedirect(HttpServletRequest request, HttpServletResponse response,
            String targetUrl, boolean http10Compatible) throws IOException {

        String encodedURL = (isRemoteHost(targetUrl) ? targetUrl : response.encodeRedirectURL(targetUrl));
        if (http10Compatible) {
            HttpStatus attributeStatusCode = (HttpStatus) request.getAttribute(View.RESPONSE_STATUS_ATTRIBUTE);
            if (this.statusCode != null) {
                response.setStatus(this.statusCode.value());
                response.setHeader("Location", encodedURL);
            }
            else if (attributeStatusCode != null) {
                response.setStatus(attributeStatusCode.value());
                response.setHeader("Location", encodedURL);
            }
            else {
                // Send status code 302 by default.
                response.sendRedirect(encodedURL);
            }
        }
        else {
            HttpStatus statusCode = getHttp11StatusCode(request, response, targetUrl);
            response.setStatus(statusCode.value());
            response.setHeader("Location", encodedURL);
        }
    }

该方法首先检索 url,然后如果 http10Compatible 为 true,最终将使用 response.sendRedirect(encodedURL);否则只需将您的相对 url 放入位置 header 中,而不传递 servlet api。在您的代码中,您没有提供用于阻止 Servlet api 的 sendRedirect 的事件 if 条件的数据。这可以解释为什么你的代码会出现问题。当然,在代码的任何其他分支中: http10Compatible at false 等等,您的代码只需在 Location header 中放入一个字符串即可,它会起作用,因为在您的浏览器中,将执行重定向的将到达相对网址。

对于这是否是Servlet API的bug的问题我可以放官方接口(interface)代码:

HttpServletResponse.java:

 /**
     * Sends a temporary redirect response to the client using the specified
     * redirect location URL. This method can accept relative URLs; the servlet
     * container must convert the relative URL to an absolute URL before sending
     * the response to the client. If the location is relative without a leading
     * '/' the container interprets it as relative to the current request URI.
     * If the location is relative with a leading '/' the container interprets
     * it as relative to the servlet container root.
     * <p>
     * If the response has already been committed, this method throws an
     * IllegalStateException. After using this method, the response should be
     * considered to be committed and should not be written to.
     *
     * @param location
     *            the redirect location URL
     * @exception IOException
     *                If an input or output exception occurs
     * @exception IllegalStateException
     *                If the response was committed or if a partial URL is given
     *                and cannot be converted into a valid URL
     */
    public void sendRedirect(String location) throws IOException;

您可以阅读评论片段:

This method can accept relative URLs; the servlet
     * container must convert the relative URL to an absolute URL before sending
     * the response to the client

读完它,我可以说这不是一个错误,而是 servlet api 的一个功能,但是它放置了唯一知道其本身的服务器主机,因此它无法在反向代理的情况下工作。

希望这能很好地解释问题。

关于spring - RedirectView 不使用位置的相对路径,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51109259/

相关文章:

java - Spring 4.0.X-beans 中 springDefinitionBuilder 中 setSource 的替代方案

java - 调用 Spring 服务并通过构造函数发送参数

代理通过在 elm-package.json 中设置停止在生产服务器上工作

apache - 如果我将 apache 用作反向代理并使用 https 访问它,Web 应用程序是否受到保护

Apache ProxyPassReverse 捕获外部重定向

java - Thymeleaf: th:each 代表两个表行?

java - 在常规 Spring 项目中使用 Spring boot starter

java - Spring Java中使用一个 Controller 调用2个或多个 View

java - OriginalDestination 可以用作死信队列使用者的选择器吗?

java - 如何在 Spring 应用程序的 web.xml 中正确映射内容?