Ответ 1
Вероятно, это не столько ошибка, сколько ограничение спецификации Servlet. Информация о том, как обрабатывается JAX-RS @ApplicationPath
, специфична для реализации, и я не могу говорить для всех реализаций, но я предполагаю, что типичный подход состоит в том, чтобы просто использовать его в качестве шаблона URL сервлета. В качестве одного примера рассмотрим реализацию Jersey ServletContainerInitializer, вы обнаружите, что метод addServletWithApplication()
отвечает за создание сервлета и отображение для обработки запросов, и вы можете убедитесь, что он действительно использует путь от @ApplicationPath
в качестве маршрута, установленного Джерси ServletContainer.
К сожалению, с незапамятных времен спецификация Servlet допускает только небольшое количество способов сопоставления сервлетов в URL-адресах. Текущие параметры с сервлета 3.0, приведенные в разделе Раздел 12.2 спецификации, которые доступны только в виде PDF, поэтому не связаны между собой по разделам:
-
/.../*
, где начальный/...
равен нулю или более элементам пути -
*.<ext>
где<ext>
- некоторое расширение для соответствия - пустая строка, которая отображает только пустой пул/контекст root
-
/
, единственная косая черта, которая указывает на сервлет по умолчанию в контексте, который обрабатывает все, что не соответствует чему-либо еще. - любая другая строка, которая рассматривается как буквальное значение для соответствия
В том же разделе спецификации также есть определенные правила для порядка, в котором должны применяться правила сопоставления, но короткая версия такова: для того, чтобы ваши запросы ответа класса ресурса находились в корне контекста, вы должны использовать либо /
или /*
в качестве пути. Если вы используете /
, вы заменяете сервлет-контейнер по умолчанию, который обычно отвечает за обработку статических ресурсов. Если вы используете /*
, то вы делаете его слишком жадным и говорите, что он должен все время совпадать, а сервлет по умолчанию никогда не будет вызываться.
Итак, если мы согласны с тем, что мы находимся внутри поля, определяемого ограничениями шаблонов URL сервлета, наши варианты довольно ограничены. Вот те, о которых я могу думать:
1) Используйте @ApplicationPath("/")
и явно сопоставьте свои статические ресурсы по имени или по расширению с сервлетом по умолчанию в контейнере (с именем "по умолчанию" в Tomcat и Jetty, не уверен в других). В 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");
}
}
Из-за того, как написаны правила сопоставления, шаблон расширения выигрывает по сервлету по умолчанию, поэтому вам нужно будет только добавить сопоставление для статического расширения файла, если между ними и любыми "расширениями" нет совпадения может возникнуть в вашем API. Это довольно близко к нежелательному варианту, указанному в сообщении форума, который вы связали, и я просто упомянул об этом для полноты и добавил часть ServletContextInitializer.
2) Оставьте свой API сопоставленным с /rest/*
и используйте фильтр для идентификации запросов API и пересылки их на этот путь. Таким образом, вы выходите из окна шаблона 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.