Приложение JAX-RS в корневом контексте - как это можно сделать?

Я хочу, чтобы приложение JAX-RX запускалось в корневом контексте, поэтому мои URL-адреса будут

http://example.com/restfullPath

а не

http://example.com/rest/restfullPath

Я переключил аннотацию приложения из этого

@ApplicationPath("/rest/*")

к этому

@ApplicationPath("/*")

Но тогда кажется, что он берет файлы обслуживания, такие как /index.html

Есть ли способ запустить JAX-RS в корневом контексте приложения, но все еще есть статические страницы?

Кажется, это было задано до на форуме JBOSS, но решение на самом деле не практично

Ответ 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.

Ответ 2

Вы можете попытаться найти DefaultServlet вашего контейнера сервлета и добавить для него сервлет-сопоставление руками web.xml для обработки файлов страниц, таких как *.html, *.jsp или любой другой.

например. для Tomcat 5.5, описанного здесь: http://tomcat.apache.org/tomcat-5.5-doc/default-servlet.html.

Ответ 3

Я нашел другое решение, которое включает в себя внутренние классы в Джерси, я предполагаю, что он, вероятно, пока еще не является частью спецификации JAX-RS. (на основе: http://www.lucubratory.eu/simple-jerseyrest-and-jsp-based-web-application/)

web.xml

<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
  <display-name>jersey-rest-jsp-frame-1</display-name>

  <filter>
    <filter-name>jersey</filter-name>
    <filter-class>
      com.sun.jersey.spi.container.servlet.ServletContainer
    </filter-class>
    <init-param>
      <param-name>
        com.sun.jersey.config.property.JSPTemplatesBasePath
      </param-name>
      <param-value>/WEB-INF/jsp</param-value>
    </init-param>
    <init-param>
      <param-name>
        com.sun.jersey.config.property.WebPageContentRegex
      </param-name>
      <param-value>
        (/(image|js|css)/?.*)|(/.*\.jsp)|(/WEB-INF/.*\.jsp)|
        (/WEB-INF/.*\.jspf)|(/.*\.html)|(/favicon\.ico)|
        (/robots\.txt)
      </param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>jersey</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

WEB-INF/JSP/index.jsp

<%@ page contentType="text/html; charset=UTF-8" language="java" %>

<html>
<body>
<h2>Hello ${it.foo}!</h2>
</body>
</html>

IndexModel.java

package example;

import com.sun.jersey.api.view.Viewable;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
import java.util.HashMap;

@Path("/")
@Produces(MediaType.TEXT_HTML)
public class IndexModel {

    @GET
    public Response root() {
      return Response.seeOther(URI.create("/index")).build();
    }

    @GET
    @Path("index")
    public Viewable index(@Context HttpServletRequest request) {
      HashMap<String, String> model = new HashMap<String, String>();
      model.put("foo","World");
      return new Viewable("/index.jsp", model);
    }
}

Это похоже на работу, но мне интересно, будет ли он/будет частью спецификации/реализации JAX-RS.

Ответ 4

Цитата @damo для Джерси 2.0 из другого сообщения

"В качестве альтернативы вы могли бы что-то убрать с каким-то перенаправлением. Например, с Фильтр предварительной сопоставления." Никогда не делал ничего подобного, но документация предполагает, что "вы даже можете изменить URI запроса".

Ответ 5

Используйте @ApplicationPath("/") вместо (без звездочки). Это поможет в вашем случае.

Вот пример веб-службы REST:

1. JaxRsActivator.java

package com.stackoverflow;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/")
public class JaxRsActivator extends Application {
}

2. HelloService.java

package com.stackoverflow;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/hello")
public class HelloService {
    @GET
    @Produces(MediaType.TEXT_HTML)
    public String hello() {
        return "hello";
    }
}

Я использовал Eclipse для экспорта этого проекта Dynamic Web в файл WAR с именем helloservice.war и развернул его в WildFly, который работал на моем локальном компьютере. Его URL: http://localhost:8080/helloservice/hello.

При обращении к этой ссылке он возвращается:

hello