【问题标题】:How to control the web context of Spring Hateoas generated links?如何控制 Spring Hateoas 生成链接的 web 上下文?
【发布时间】:2015-05-07 20:02:21
【问题描述】:

我们正在构建一个 API,并且正在使用 Spring RestControllers 和 Spring HATEOAS
当 war 文件部署到容器并且向 http://localhost:8080/placesapi-packaged-war-1.0.0-SNAPSHOT/places 发出 GET 请求时,HATEOAS 链接如下所示:

{
  "links" : [ {
    "rel" : "self",
    "href" : "http://localhost:8080/placesapi-packaged-war-1.0.0-SNAPSHOT/places",
    "lastModified" : "292269055-12-02T16:47:04Z"
  } ]
}

因为 Web 上下文是已部署应用程序的上下文(例如:placesapi-packaged-war-1.0.0-SNAPSHOT

在真实的运行时环境(UAT 及以上)中,容器很可能位于 http 服务器后面,例如 Apache,其中虚拟主机或类似主机位于 Web 应用程序的前端。像这样的:

<VirtualHost Nathans-MacBook-Pro.local>
   ServerName Nathans-MacBook-Pro.local

   <Proxy *>
     AddDefaultCharset Off
     Order deny,allow
     Allow from all
   </Proxy>

   ProxyPass / ajp://localhost:8009/placesapi-packaged-war-1.0.0-SNAPSHOT/
   ProxyPassReverse / ajp://localhost:8009/placesapi-packaged-war-1.0.0-SNAPSHOT/

</VirtualHost>

使用上述方法,当我们向http://nathans-macbook-pro.local/places 发出 GET 请求时,得到的响应如下所示:

{
  "links": [ {
    "rel": "self",
    "href": "http://nathans-macbook-pro.local/placesapi-packaged-war-1.0.0-SNAPSHOT/places",
    "lastModified": "292269055-12-02T16:47:04Z"
  } ]
}

这是错误的,因为响应中的链接包含 Web 应用程序上下文,如果客户端点击该链接,他们会收到 404

有谁知道如何控制Spring HATEOAS 在这方面的行为?基本上我需要能够控制它在链接中生成的网络上下文名称。

我做了一些探索,可以看到使用自定义标头 X-Forwarded-Host 您可以控制主机和端口,但我看不到任何类似的能够控制上下文的东西。

我们考虑过的其他选项包括将应用程序部署到 ROOT 上下文或固定命名上下文,然后相应地设置我们的虚拟主机。然而,这些感觉像是妥协而不是解决方案,因为理想情况下我们希望在同一个容器上托管多个版本的应用程序(例如:placesapi-packaged-war-1.0.0-RELEASE、placesapi-packaged-war-1.0.1- RELEASE、placesapi-packaged-war-2.0.0-RELEASE 等)并让虚拟主机根据 http 请求标头转发到正确的应用程序。

非常感谢您对此的任何想法,
干杯

内森

【问题讨论】:

    标签: spring spring-hateoas webcontext


    【解决方案1】:

    首先,如果您不知道,您可以通过创建包含以下行的 webapp/META-INF/context.xml 来控制 Web 应用程序的上下文(至少在 Tomcat 下):

    <Context path="/" />
    

    ...这将使应用程序上下文设置为与您正在使用的相同 (/)。

    但是,这不是你的问题。我提出了similar question a little while back。因此,据我所知,没有现成的机制来手动控制生成的链接。相反,我创建了自己的修改版ControllerLinkBuilder,它使用application.properties 中定义的属性构建了URL 的基础。如果在您的应用程序本身上设置上下文不是一个选项(即,如果您在同一个 Tomcat 实例下运行多个版本)那么我认为这是你唯一的选择,如果 ControllerLinkBuilder 不是正确构建您的网址。

    【讨论】:

    • 注意上下文路径在部署时不起作用,只需将战争放在监视目录中即可。建议在将战争复制到监视目录之前重命名战争
    【解决方案2】:

    有一个非常相似的问题。我们希望我们的公共 URL 是 x.com/store,并且在内部我们的集群中主机的上下文路径是 host/our-api。生成的所有 URL 都包含 x.com/our-api 而不是 x.com/store,并且无法从公共肮脏的互联网上解析。

    首先请注意,我们得到 x.com 的原因是因为我们的反向代理不会重写 HOST 标头。如果是这样,我们需要将 X-Forwarded-Host 标头设置为 x.com,以便 HATEOAS 链接构建器生成正确的主机。这是特定于我们的反向代理的。

    就获得工作路径而言...我们不想使用自定义 ControllerLinkBuilder。相反,我们在 servlet 过滤器中重写上下文。在我分享该代码之前,我想提出最棘手的事情。我们希望我们的 api 在直接访问托管战争的 tomcat 节点时生成可用链接,因此 url 应该是 host/our-api 而不是 host/store。为了做到这一点,反向代理需要向 Web 应用程序提示请求来自反向代理。您可以使用标头等来执行此操作。特别是对我们而言,我们只能修改请求 url,因此我们将负载均衡器更改为将 x.com/store 重写为 host/our-api/store 这个额外的 /store 让我们知道该请求来自反向代理,因此需要使用公共上下文根。同样,您可以使用另一个标识符(自定义标头、X-Forwared-Host 的存在等)来检测情况..或者您可能不关心让各个节点返回可用的 URL(但它非常适合测试)。

    public class ContextRewriteFilter extends GenericFilterBean {
    
    
        @Override
        public void doFilter(ServletRequest req, ServletResponse res, final FilterChain chain) throws IOException, ServletException {
    
            HttpServletRequest request = (HttpServletRequest)req;
    
            //There's no cleanup to perform so no need for try/finally
            chain.doFilter(new ContextRewriterHttpServletRequestWrapper(request), res);
    
        }
    
    
        private static class ContextRewriterHttpServletRequestWrapper extends HttpServletRequestWrapper {
    
            //I'm not totally certain storing/caching these once is ok..but i can't think of a situation
            //where the data would be changed in the wrapped request
            private final String context;
            private final String requestURI;
            private final String servletPath;
    
    
    
            public ContextRewriterHttpServletRequestWrapper(HttpServletRequest request){
                super(request);
    
                String originalRequestURI = super.getRequestURI();
                //If this came from the load balancer which we know BECAUSE of the our-api/store root, rewrite it to just be from /store which is the public facing context root
                if(originalRequestURI.startsWith("/our-api/store")){
                    requestURI = "/store" + originalRequestURI.substring(25);
                }
                else {
                    //otherwise it's just a standard request
                    requestURI = originalRequestURI;
                }
    
    
                int endOfContext = requestURI.indexOf("/", 1);
                //If there's no / after the first one..then the request didn't contain it (ie /store vs /store/)
                //in such a case the context is the request is the context so just use that
                context = endOfContext == -1 ? requestURI : requestURI.substring(0, endOfContext);
    
                String sPath = super.getServletPath();
                //If the servlet path starts with /store then this request came from the load balancer
                //so we need to pull out the /store as that's the context root...not part of the servlet path
                if(sPath.startsWith("/store")) {
                    sPath = sPath.substring(6);
                }
    
                //I think this complies with the spec
                servletPath = StringUtils.isEmpty(sPath) ? "/" : sPath;
    
    
            }
    
    
            @Override
            public String getContextPath(){
                return context;
            }
    
            @Override
            public String getRequestURI(){
    
                return requestURI;
            }
    
            @Override
            public String getServletPath(){
                return servletPath;
            }
    
        }
    }
    

    这是一个 hack,如果有任何事情取决于知道请求中的 REAL 上下文路径,它可能会出错......但它对我们来说工作得很好。

    【讨论】:

      【解决方案3】:
      ProxyPass /placesapi-packaged-war-1.0.0-SNAPSHOT
      ajp://localhost:8009/placesapi-packaged-war-1.0.0-SNAPSHOT/   
      ProxyPassReverse /placesapi-packaged-war-1.0.0-SNAPSHOT
      ajp://localhost:8009/placesapi-packaged-war-1.0.0-SNAPSHOT/
      

      【讨论】:

        猜你喜欢
        • 2018-01-15
        • 2013-06-28
        • 2016-04-14
        • 2019-05-21
        • 2016-05-23
        • 2019-09-15
        • 2019-07-11
        • 1970-01-01
        • 2023-03-28
        相关资源
        最近更新 更多