The Java™ Servlet API is a fundamental building block of mainstream server-side Java, and part of Java EE technologies such as JAX-RS for web services, JSF (JavaServer Faces), and JSP (JavaServer Pages). Java servlets also stand on their own, providing a range of features supporting dynamic web content. These include filters, web security, and features for handling HTTP requests and responses.
Servlet 4.0 is the latest version of the API, and is a core update in the Java EE 8 specification. As you’ll learn in this tutorial, Servlet 4.0 is HTTP/2-ready and fully embraces server push, as well as extending it to servlet-based technologies like JSF 2.3. This tutorial also gets you started with the new HttpServletMapping
interface, which enables runtime discovery of a servlet’s mapping URL.
Servlets in a nutshell
A Java servlet is a server-side technology that runs over the HTTP protocol. Servlets wait for the client to send a request message to the server, and will then return a response message to the client. The request and response messages consists of two parts:
- The header contains information about the message.
- The body contains the payload of the message, which is its content.
In a typical exchange, the client invokes a servlet by requesting a specific URL from the browser, or from another HTTP client such as curl
.
In Listing 1, the servlet is activated when the servlet path is requested. The request is delegated to the appropriate method, which is determined by the HTTP method. In this case, because the request was a GET
method request, the request is handled by the Java servlet’s doGet()
method.
The servlet path for the following exchange is: http://hostname/applicationroot/showlogoservlet
.
Listing 1. Simple servlet implementation
@WebServlet("/showlogoservlet")
public class SimpleServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
getServletContext()
.getRequestDispatcher("/showlogo.jsp")
.forward(request, response);
}
}
Top new features in Servlet 4.0
Servlet 4.0’s top new features are server push and a new API that discovers a servlet’s URL mappings at runtime.
Server push is the most visible HTTP/2 enhancement, and is exposed in a servlet via the PushBuilder
interface. Server push is also implemented in JavaServer Faces API and invoked during the RenderResponsePhase
lifecycle phase, so that JSF pages can leverage its performance enhancements.
The new servlet mapping discovery interface, HttpServletMapping
, enables a framework to obtain information about the URL that activated a given servlet request. This is likely to be of particular use to frameworks that need this information for their internal workings.
In the next sections I’ll provide an overview of server push and how it works in Java servlets, including server push in JSF 2.3. I’ll also show you an example exchange that highlights the new servlet mapping discovery feature.
Server push in Servlet 4.0
Server push enables the server to anticipate the resource requirements of a client request. It can then send those resources to the client before request processing has completed.
To imagine the benefits of server push, consider a web page that consists of images and other dependencies such as CSS and JavaScript files. The client simply makes a request for the web page. The server then analyzes the page requested, determines the resources required to render it, and proactively sends those to the client’s cache.
It does all of this while it is still processing the original web page request. By the time the client receives a response, the resources it needs are already in the cache.
PushBuilder
Servlet 4.0 exposes server push via the PushBuilder
interface. In order to access it, you need to obtain an instance of PushBuilder
from HttpServletRequest
, by calling the newPushBuilder()
method. Listing 2 shows how to obtain a PushBuilder
instance.
Listing 2. How to obtain a new PushBuilder
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
PushBuilder pushBuilder = request.newPushBuilder();
}
A new instance of PushBuilder
will be returned for each invocation of the newPushBuilder()
method. If server push isn’t available, newPushBuilder()
will return null
. In some cases, the client may refuse server push for a request transaction. Server push also will not work if the client isn’t using a secure connection. Thus, it is important to ensure that you test for a null
return value before calling methods on the PushBuilder
instance.
As its name hints, PushBuilder
implements the Builder pattern. In this implementation, a push request is built by chaining mutator methods together. These mutator methods configure the PushBuilder
instance by setting its HTTP headers, method type (GET
is the only acceptable value), query string, session ID, and resource path (that is, the path to the resource that will be pushed out).
Most request headers from the original HttpServletRequest
instance are simply added to the PushBuilder
instance. Because they are not required for the proper function of server push, the following are not included:
- Conditional headers
- Range headers
- Expect headers
- Authorization headers
- Referrer headers
Now let’s look how to construct and provoke a server push action.
1. Setting the resource to push
The path is the only configuration that must be set before pushing a resource to the client. To set the path you call the path()
method. This method should only be called once, as it mutates the path value of the PushBuilder
object. The path may start with a forward slash (“/”) to indicate that the resource path is an absolute path; otherwise the resource is deemed relative to the context path of the associated request. The path can contain a query string, which will be merged with any string set by the queryString()
method.
2. Pushing the resource
Next you will call the push()
method, which pushes the resource to the client. The push()
method initiates the push “conversation” with the client. Behind the scenes, the client is sent a PUSH_PROMISE
frame, which acts like a notification of intent to send the resource. The client can reject the resource by sending back an RST_STREAM
. This mechanism allows the client to retain control of which resources it receives. The client is thus protected from becoming overloaded with resources that it doesn’t want, or which it already has in its cache.
Once an instance of the PushBuilder
has been obtained it can be reused multiple times, as shown in Listing 3. The path and conditional headers are nulled, but all other fields are left as-is. These can be reused in another server push.
Listing 3. Re-using a PushBuilder instance
PushBuilder pushBuilder = request.newPushBuilder();
if (pushBuilder != null) {
pushBuilder.path("images/hero-banner.jpg").push();
pushBuilder.path("css/menu.css").push();
pushBuilder.path("js/marquee.js").push();
}
In Listing 3 the path to the hero-banner.jpg is set on the PushBuilder
instance via the path()
method, and is pushed to the client by calling push()
. The push()
method is non-blocking and returns immediately so that further resources—in this case menu.css and marquee.js—can be subsequently pushed.
Using server push with JSF
JavaServer Faces already identifies the resource requirements of each page as part of its page-rendering lifecycle, so it is a natural fit for server push. Better yet, from a developer’s perspective, you don’t have to do anything different in order to activate this feature. You get it for free when you upgrade to JSF 2.3.
Listing 4 demonstrates the integration of JSF and server push.
Listing 4. Using server push in a JSF page
<h:head>
<h:outputStylesheet library="css" name="logo.css"/>
<h:outputScript library="js" name="logo.js"/>
<title>JSF 2.3 ServerPush Example</title>
</h:head>
<h:body>
<h:form>
<h:graphicImage library="images" name="logo.jpg"/>
</h:form>
</h:body>
</html>
The JSF page in Listing 4 requires three resources:
- A CSS file called logo.css.
- A JavaScript file called logo.js.
- An image called logo.jpg.
These resources will be pushed to the client one-by-one while the JSF engine is processing and rendering the page. This happens during JSF’s render response phase. The ExternalContextImpl.encodeResourceURL()
method is then called for each resource, and passed the resource’s new URL. A new PushBuilder
object is obtained from the HttpServletRequest
instance associated with the ExternalContext
. If server push is supported, the resource is pushed to the client before the page is rendered to the client.
The HttpServletMapping interface
Servlet 4.0’s new servlet mapping discovery API enables the server to do a runtime check of the URL that has caused a servlet to be invoked. For example, a request to file.ext, /path
and /path/file.ext
will activate the servlet with URL patterns /path/*
and *.ext
.
The HttpServletMapping
interface supports runtime discovery of a servlet’s mapping URL. You can obtain an instance of the interface by calling getHttpServletMapping()
on an instance of HttpServletRequest
. You may use the following methods to obtain information about a servlet’s mapping URL:
getMatchValue()
returns the portion of the URI path that caused the request to be matched.getPattern()
returns theString
representation for the URL pattern.getServletName()
returns theString
representation for the servlet name.getMappingMatch()
returns the type of the match represented as anMappingMatch
enum value, which will be one of:CONTEXT_ROOT
,DEFAULT
,EXACT
,EXTENSION
, orPATH
.
Listing 5 shows the four API methods in action.
Listing 5. Runtime servlet mapping discovery in Servlet 4.0
@WebServlet({"/path/*", "*.ext"})
public class ServletMapping extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException {
HttpServletMapping mapping = request.getHttpServletMapping();
String mapping = mapping.getMappingMatch().name();
String value = mapping.getMatchValue();
String pattern = mapping.getPattern();
String servletName = mapping.getServletName();
}
}
Smaller changes in Servlet 4.0
In addition to server push and the new HttpServletMapping
interface, Servlet 4.0 includes a handful of smaller additions and changes worth noting.
- The
Trailer
response header allows the sender to include additional fields at the end of chunked messages. This is used to supply metadata that might be dynamically generated while the message body is sent, such as a message integrity check, digital signature, or post-processing status. - Servlet 4.0 adds the
GenericFilter
andHttpFilter
abstract classes, which simplify writing filters by providing minimal implementations of the lifecycle methodsinit()
anddestroy()
. - Servlet 4.0 also integrates the new
HTTP Trailer
, which allows the sender to include additional fields at the end of a chunked message. - The
ServletContext
interface has new methods:addJspFile()
adds the servlet with a given JSP file to the servlet context.getSessionTimeout()
andsetSessionTimeout()
provide access to the session timeout.getRequestCharacterEncoding()
andsetRequestCharacterEncoding()
provide access and mutate the default request character encoding for the current servlet context. - The
isRequestedSessionIdFromUrl()
method on theHttpServletRequest
interface has been deprecated. - Default methods have been added to listener interfaces, thanks to the uplift to Java SE 8.
Conclusion
Servlet 4.0 has been released primarily to integrate the new HTTP/2 protocol and its many performance enhancing features. The PushBuilder
interface offers fine-grained control over resources that are pushed to a client, which has already led to interesting cross-cutting implementations. As an example, Jetty 9 has implemented server push with the PushBuilder
API in its PushCacheFilter
web filter. This filter caches resources on first request. It is then able to push subsequent requests to the client, even while the request is still being processed on the server side.
While JSF 2.3 comes with server push built in, JavaServer Pages does not. JSF’s integration of server push is a great boon, allowing developers to focus less on performance hacks and more on designing dynamic web pages. For developers wanting similar functionality in JSP, a bespoke solution such as a web filter is required, such as the PushCacheFilter
web filter in Jetty 9.