Servlet 4.0's mapping API previewed in Tomcat 9.0 m4

Datetime:2016-08-23 03:53:11          Topic: Tomcat           Share
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





About List