40

I would like to have my JAX-RX Application start at the root context so my URLs will be

http://example.com/restfullPath

and not

http://example.com/rest/restfullPath

I switched my Application's annotation from this

@ApplicationPath("/rest/*")

to this

@ApplicationPath("/*")

But then it seems that it takes over serving files such as /index.html

Is there a way to run a JAX-RS on the root application context but still have static pages served?

Seems this was asked before on the JBOSS forum, but the solution is not really practical

Eran Medan
  • 44,555
  • 61
  • 184
  • 276

5 Answers5

46

It's probably not so much a bug as a limitation of the Servlet spec. The details of how a JAX-RS @ApplicationPath is handled is implementation specific, and I can't speak for all implementations, but I'd guess the typical approach is to simply use it as a servlet URL pattern. Taking a look at Jersey's ServletContainerInitializer implementation as one example, you'll find that the addServletWithApplication() method is responsible for creating the servlet and mapping to handle requests, and you can see that it does, indeed, use the path from the @ApplicationPath as the Jersey ServletContainer's mapped path.

Unfortunately, since time immemorial, the Servlet spec has allowed only a small handful of ways of mapping servlets to URL paths. The current options with Servlet 3.0, given in Section 12.2 of the spec--sadly only available as a PDF, so not linkable by section--are:

  • /.../* where the initial /... is zero or more path elements
  • *.<ext> where <ext> is some extension to match
  • the empty string, which maps only to the empty path/context root
  • /, the single slash, which indicates the "default" servlet in the context, which handles anything that doesn't match anything else
  • any other string, which is treated as a literal value to match

The same section of the spec also has specific rules for the order in which the matching rules should apply, but the short version is this: to make your resource class answer requests at the context root, you have to use either / or /* as the path. If you use /, then you're replacing the container's default servlet, which would normally be responsible for handling static resources. If you use /*, then you're making it too greedy and saying it should match everything all the time, and the default servlet will never be invoked.

So if we accept that we're inside the box determined by the limitations of servlet URL patterns, our options are fairly limited. Here are the ones I can think of:

1) Use @ApplicationPath("/"), and explicitly map your static resources by name or by extension to the container's default servlet (named "default" in Tomcat and Jetty, not sure about others). In a web.xml, it would look like

<!-- 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>

or with a ServletContextInitializer, like

public class MyInitializer implements ServletContainerInitializer {
    public void onStartup(Set<Class<?>> c, ServletContext ctx) {
        ctx.getServletRegistration("default").addMapping("*.html");
        ctx.getServletRegistration("default").addMapping("/index.html");
    }
}

Because of the way the matching rules are written, an extension pattern wins over the default servlet, so you'd only need to add a mapping per static file extension as long as there's no overlap between those and any "extensions" that might occur in your API. This is pretty close to the undesirable option mentioned in the forum post you linked, and I just mention it for completeness and to add the ServletContextInitializer part.

2) Leave your API mapped to /rest/*, and use a Filter to identify requests for the API and forward them to that path. This way, you break out of the servlet URL pattern box and can match URLs any way you want. For example, assuming that all your REST calls are to paths that either begin with "/foo" or are exactly "/bar" and all other requests should go to static resources, then something like:

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() {}
}

With the above, you essentially translate requests as follows:

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) Realize that the previous option is basically URL rewriting and use an existing implementation, such as Apache's mod_rewrite, the Tuckey rewrite filter, or ocpsoft Rewrite.

Ryan Stewart
  • 126,015
  • 21
  • 180
  • 199
1

You can try to look for DefaultServlet of your servlet container and add servlet-mapping for it by hands in web.xml to handle page files such as *.html, *.jsp or any other.

E.g. for Tomcat 5.5 it's described here: http://tomcat.apache.org/tomcat-5.5-doc/default-servlet.html.

yatul
  • 1,103
  • 12
  • 27
1

I have found another solution that involves internal Jersey classes, I assume it's probably just not yet part of the JAX-RS spec. (based on: 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);
    }
}

This seems to work, but I wonder if it is / will be part of JAX-RS spec / implementation.

Eran Medan
  • 44,555
  • 61
  • 184
  • 276
0

Quoting @damo for Jersey 2.0 from another post

"Alternatively, you might be able to pull something off with some kind of redirection. For example, with a Pre-matching Filter. I've never done anything like this, but the documentation suggests that "you can even modify request URI"."

allegjdm93
  • 592
  • 6
  • 24
0

Use @ApplicationPath("/") instead (without asterisk). It will help in your case.

Here is a sample REST web service:

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";
    }
}

I used Eclipse to export this Dynamic Web project to a WAR file named as helloservice.war and deployed it to WildFly which was running on my local machine. Its URL: http://localhost:8080/helloservice/hello.

When accessing this link it returned:

hello
Thach Van
  • 1,381
  • 16
  • 19
  • 1
    I don't see how this addresses the question. But here's the blog that this answer's code is from, if anyone is curious: http://buraktas.com/resteasy-example-without-using-a-web-xml/ – Alkanshel Jun 06 '16 at 20:58
  • I wrote it based on my experience. Your link doesn't have any code that you believe it's the same to my code here. My code is a generic template, not a specific proprietary code. It you want to advertise, this is not the right place to do that. – Thach Van Dec 03 '16 at 19:32