Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JAX-RS Application on the root context - how can it be done?

Tags:

java

jax-rs

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

like image 429
Eran Medan Avatar asked Jun 03 '12 21:06

Eran Medan


People also ask

How do you set root context?

1.1 Right click on the project, select Properties , Web Project Settings , update the context root here. 1.2 Remove your web app from the server and add it back. The context root should be updated. 1.3 If step 2 is failing, delete the server, create a new server and add back the web app.

What is root application context?

The context root for an application defines the location at which the module can be accessed. The context root is part of the URL you use to connect to the application. A URL reference to an IBM® SPSS® Collaboration and Deployment Services application includes the following elements: URL prefix.

What is a JAX-RS application?

JAX-RS is a Java programming language API designed to make it easy to develop applications that use the REST architecture. The JAX-RS API uses Java programming language annotations to simplify the development of RESTful web services.


2 Answers

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.

like image 90
Ryan Stewart Avatar answered Sep 27 '22 17:09

Ryan Stewart


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.

like image 30
Eran Medan Avatar answered Sep 27 '22 16:09

Eran Medan