Saturday, April 2, 2016

Servlet 4.0's mapping API previewed in Tomcat 9.0 m4

Without doubt one of the most important Servlet implementations is done by Tomcat. Tomcat serves, or has served, as the base for Servlet functionality in a number of Java EE application servers and is one of the most frequently used standalone Servlet containers.

Contrary to what is often thought, Tomcat is not the reference implementation for Servlet; the build-in Servlet container of GlassFish is. Even though important parts of that GlassFish Servlet implementation are in fact based on Tomcat it's a different code base. Normally GlassFish being the RI implements new spec level functionality first, but unfortunately during the Java EE 8/Servlet 4.0 cycle the GlassFish team is working on other assignments and hasn't worked out a schedule to incorporate this new functionality.

Luckily Tomcat has taken the initiative and in Tomcat 9.0.0.M4 the proposed Servlet 4.0 Mapping API has been implemented.

This API allows Servlet users to find out via which mapping a Servlet was being called. E.g. a Servlet can be mapped by a user to both "/foo/*" and "*.bar" among others. Especially for frameworks it can be important to know what the mapping was, since not rarely something (typically a template file) has to be loaded based on what the * from the above example was. And not only that, if links have to be generated they often need to use the same mapping that was used to call the Servlet.

The current way to do this is a little hairy and therefor quite error prone; it requires checking against the many components of the request URI. The new proposed API greatly simplifies this via the new HttpServletRequest#getMapping() method:

public default Mapping getMapping() {
    // ...
}
The new Mapping type that can be seen in the signature of the above method looks as follows:
/**
 * Represents how the request from which this object was obtained was mapped to
 * the associated servlet.
 *
 * @since 4.0
 */
public interface Mapping {

    /**
     * @return The value that was matched or the empty String if not known.
     */
    String getMatchValue();

    /**
     * @return The {@code url-pattern} that matched this request or the empty
     *         String if not known.
     */
    String getPattern();

    /**
     * @return The type of match ({@link MappingMatch#UNKNOWN} if not known)
     */
    MappingMatch getMatchType();
}
The MappingMatch is an enumeration of the different types of possible mappings as shown below:
/**
 * Represents the ways that a request can be mapped to a servlet
 *
 * @since 4.0
 */
public enum MappingMatch {

    CONTEXT_ROOT,
    DEFAULT,
    EXACT,
    EXTENSION,
    IMPLICIT,
    PATH,
    UNKNOWN
}
To test out the new functionality the following test Servlet was used:
@WebServlet({"/path/*", "*.ext", "", "/", "/exact"})
public class Servlet extends HttpServlet {
    
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        
        Mapping mapping = request.getMapping();
        
        response.getWriter()
                .append("Mapping match:")
                .append(mapping.getMatchType().name())
                .append("\n")
                .append("Match value:")
                .append(mapping.getMatchValue())
                .append("\n")
                .append("Pattern:")
                .append(mapping.getPattern());
    }

}

As can be seen this Servlet is mapped via a path mapping ("/path/*"), extension mapping ("*.ext"), context root mapping (""), default mapping ("/") and a single exact mapping ("/exact"). We deployed this Servlet via an application called "servlet4" to a Tomcat 9.0 M4 instance and subjected it to a couple of requests via a browser. The results are shown below:

Path mapping
http://localhost:8080/servlet4/path/foo

Mapping match:PATH
Match value:/foo
Pattern:/path/*

Extension mapping
http://localhost:8080/servlet4/foo.ext

Mapping match:EXTENSION
Match value:/foo
Pattern:*.ext

Context root mapping
http://localhost:8080/servlet4

Mapping match:CONTEXT_ROOT
Match value:
Pattern:

Default (fallback) mapping
http://localhost:8080/servlet4/doesnotexist

Mapping match:DEFAULT
Match value:/
Pattern:/

Exact mapping
http://localhost:8080/servlet4/exact

Mapping match:EXACT
Match value:/exact
Pattern:/exact
To test the implicit mapping (as per Servlet spec 12.2.1), the following JSP file was used:
<%
Mapping mapping = request.getMapping();

response.getWriter()
        .append("Mapping match:")
        .append(mapping.getMatchType().name())
        .append("\n")
        .append("Match value:")
        .append(mapping.getMatchValue())
        .append("\n")
        .append("Pattern:")
        .append(mapping.getPattern());
%>
This was however seen by Tomcat as a regular extension mapping:

Implicit mapping
http://localhost:8080/servlet4/page.jsp

Mapping match:EXTENSION 
Match value:/page 
Pattern:*.jsp
Implicit mappings are a little bit difficult to represent. Are they a mapping themselves, or is it just extra information? I.e. can you speak of an "implicit extension mapping" and an "implicit path mapping"?

Conclusion

At the moment it's unfortunately difficult to commit code to the Servlet RI (GlassFish), but Tomcat 9.0 M4 can be used to get an early glimpse of one of the new Servlet APIs. As the examples have shown, the new Mapping API now makes it trivial to find out via which mapping a Servlet was selected. The "implicit" mapping type however may still need some discussion.

Arjan Tijms