我们正在构建API并正在使用Spring RestController
和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
等http服务器后面,其中虚拟主机或类似的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"
} ]
}
这是错误的,因为响应中的链接包含网络应用上下文,如果客户端要关注该链接,则会获得404
有谁知道如何在这方面控制Spring HATEOAS
的行为?基本上我需要能够控制它在链接中生成的Web上下文名称。
我做了一些讨论,可以看到使用自定义标题X-Forwarded-Host
,您可以控制主机和端口,但我看不到任何类似的能够控制上下文。< / p>
我们考虑的其他选项涉及将应用程序部署到ROOT上下文或固定的命名上下文,然后相应地设置我们的虚拟主机。然而,这些感觉就像妥协而不是解决方案,因为理想情况下我们希望在同一容器上托管应用程序的多个版本(例如:placesapi-packaged-war-1.0.0-RELEASE,placesapi-packaged-war-1.0.1- RELEASE,placesapi-packaged-war-2.0.0-RELEASE等)并根据http请求标题将虚拟主机转发到正确的应用程序。
对此的任何想法都将非常感激,
干杯
森
答案 0 :(得分:3)
首先,如果您不了解,您可以通过创建包含该行的webapp/META-INF/context.xml
来控制Web应用程序的上下文(至少在Tomcat下):
<Context path="/" />
...这将使应用程序上下文与您正在使用的内容相同(/
)。
但是,这不是你的问题。我提出了一个similar question a little while back。因此,从我可以收集到的内容来看,没有用于手动控制生成的链接的开箱即用机制。相反,我创建了自己的ControllerLinkBuilder
修改版本,它使用application.properties
中定义的属性构建了URL的基础。如果在应用程序本身上设置上下文不是一个选项(即如果您在同一个Tomcat实例下运行多个版本),那么我认为这是您唯一的选择,如果{{1没有正确构建您的网址。
答案 1 :(得分:3)
有一个非常相似的问题。我们希望我们的公共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应用程序提供请求。您可以使用标题等执行此操作。具体对我们来说,我们只能修改请求网址,因此我们更改了负载均衡器以重写x.com/store到主机/ our-api /存储此额外/存储让我们知道请求来自反向代理,因此需要使用公共上下文根。您可以再次使用另一个标识符(自定义标头,存在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;
}
}
}
这是一个黑客,如果有什么依赖于知道请求中的REAL上下文路径它可能会错误...但它一直很好地为我们工作。
答案 2 :(得分: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/