它可能不是一个错误,而是 Servlet 规范的限制。如何处理 JAX-RS 的细节@ApplicationPath
是特定于实现的,我不能代表所有实现,但我猜典型的方法是简单地将其用作 servlet URL 模式。以 Jersey 的 ServletContainerInitializer 实现为例,您会发现该addServletWithApplication()
方法负责创建 servlet 和映射以处理请求,您可以看到它确实使用来自@ApplicationPath
Jersey ServletContainer 的映射的路径小路。
不幸的是,自远古以来,Servlet 规范只允许少数几种将 servlet 映射到 URL 路径的方法。Servlet 3.0 的当前选项,在规范的第 12.2 节中给出——遗憾的是只能作为 PDF 提供,因此不能按部分链接——是:
/.../*
其中初始/...
是零个或多个路径元素
*.<ext>
<ext>
匹配的扩展名在哪里
- 空字符串,仅映射到空路径/上下文根
/
,单斜杠,表示上下文中的“默认”servlet,它处理与其他任何内容不匹配的任何内容
- 任何其他字符串,它被视为要匹配的文字值
规范的同一部分也对匹配规则的应用顺序有特定的规则,但简短的版本是这样的:为了让你的资源类在上下文根处响应请求,你必须使用/
或/*
作为路径。如果您使用/
,那么您将替换容器的默认 servlet,它通常负责处理静态资源。如果您使用/*
,那么您就太贪心了,并说它应该始终匹配所有内容,并且永远不会调用默认的 servlet。
因此,如果我们接受我们处于 servlet URL 模式的限制所决定的范围内,那么我们的选择就相当有限。以下是我能想到的:
1)使用@ApplicationPath("/")
,并通过名称或扩展名将静态资源显式映射到容器的默认servlet(在Tomcat和Jetty中命名为“default”,不确定其他)。在 web.xml 中,它看起来像
<!-- All html files at any path -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<!-- Specifically index.html at the root -->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/index.html</url-pattern>
</servlet-mapping>
或使用ServletContextInitializer,例如
public class MyInitializer implements ServletContainerInitializer {
public void onStartup(Set<Class<?>> c, ServletContext ctx) {
ctx.getServletRegistration("default").addMapping("*.html");
ctx.getServletRegistration("default").addMapping("/index.html");
}
}
由于匹配规则的编写方式,扩展模式胜过默认 servlet,因此您只需要为每个静态文件扩展添加一个映射,只要它们与可能出现的任何“扩展”之间没有重叠你的 API。这与您链接的论坛帖子中提到的不受欢迎的选项非常接近,我只是为了完整性而提到它并添加 ServletContextInitializer 部分。
2) 将您的 API 映射到/rest/*
,并使用过滤器来识别对 API 的请求并将它们转发到该路径。这样,您就可以打破 servlet URL 模式框,并且可以以任何您想要的方式匹配 URL。例如,假设您的所有 REST 调用都指向以“/foo”开头或恰好是“/bar”的路径,并且所有其他请求都应该转到静态资源,那么类似于:
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.regex.Pattern;
@WebFilter(urlPatterns = "/*")
public class PathingFilter implements Filter {
Pattern[] restPatterns = new Pattern[] {
Pattern.compile("/foo.*"),
Pattern.compile("/bar"),
};
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (request instanceof HttpServletRequest) {
String path = ((HttpServletRequest) request).getServletPath();
for (Pattern pattern : restPatterns) {
if (pattern.matcher(path).matches()) {
String newPath = "/rest/" + path;
request.getRequestDispatcher(newPath)
.forward(request, response);
return;
}
}
}
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void destroy() {}
}
有了上述内容,您基本上可以将请求翻译如下:
http://example.org/foo -> http://example.org/rest/foo
http://example.org/foox -> http://example.org/rest/foox
http://example.org/foo/anything -> http://example.org/rest/foo/anything
http://example.org/bar -> http://example.org/rest/bar
http://example.org/bart -> http://example.org/bart
http://example.org/index.html -> http://example.org/index.html
3) 意识到前面的选项基本上是 URL 重写并使用现有的实现,例如Apache 的 mod_rewrite、Tuckey 重写过滤器或ocpsoft Rewrite。