Basics of Servlets Tutorial
In this article we will cover the basics of Servlets using Java 8, in a servlet 3.1 compliant container.
We will demonstrate some of the basics of servlet usage in a Http context, via a simple web project that combines numerous simple example servlets all accessible via your favorite browser or via postman.
Table Of Contents
1. Introduction
Forming part of the Java EE API, servlets give Java the ability to provide dynamic content for clients that work on a request / response programming model.
Because of this generic approach they are capable of responding to any type of request but more commonly fill the role of providing dynamic content in web applications. Servlets can be used in any servlet container (eg: Tomcat, Jetty) as well as Java EE application servers and the javax.servlet
and javax.servlet.http
packages contain all the relevant abstractions for this API.
Critical to the usage of servlets is the need for a servlet container or Java EE application server as these provide the actual implementations for the servlet API to work. Options exist for embedding servlet containers inside of ones application or the more old fashioned way of deploying said application into the servlet container or application server.
In this article we will be making use of the “cargo.maven.plugin” to run our web application from the command line via the command mvn cargo:run
.
2. Technologies used
The example code in this article was built and run using:
- Java 8
- Maven 3.3.9
- STS (3.9.0.RELEASE)
- Ubuntu 16.04 (Windows, Mac or Linux will do fine)
3. Setup
To confirm that the correct version of Java is installed can be done by executing the following on the command line:
java -version
STS (3.9.0.RELEASE) comes with an embedded maven installed, that is of sufficient version. Should you wish to compile on the command line using another maven install as I do, confirming the correct version of maven can be done by executing the following on the command line:
mvn --version
As stated earlier we will be using the “cargo.maven.plugin” to deploy and run the application using a Tomcat 8.x container, the plugin will take care of the heavy lifting of downloading Tomcat 8.x and deploying our application to it.
4. Servlet Specification
The servlet specification has been implemented by many vendors (eg: Tomcat, Jetty) and although the specification does evolve, vendors do eventually provide implementations for us to deploy our web applications into.
The keyword is specification and indeed our project has a dependency on the servlet api 3.1 specification, we however, do not need to to include it in our shipped package as the container into which it will be deployed contains the implementation.
Coming by way of JSR 340, the servlet 3.1 specification iterated upon the big release of 3.0 (JSR 315) allowing our web applications to leverage non blocking IO and HTTP protocol upgrade mechanisms among other features.
Another great feature coming in the servlet 3.0 specification release was no longer needing a web.xml
as descriptor for all our custom web abstractions (Servlets, Filters, Listeners
, init-params etc). Most of the meta-data / configuration can now be done via annotations. In the sample project we still use a web.xml
but merely to configure the login process for the container to respect when trying to access a secure route.
Web.xml
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <!-- will challenge the user with basic authentication for any secure routes --> <login-config> <auth-method>BASIC</auth-method> <realm-name>servletbasics</realm-name> </login-config> </web-app>
5. Servlet Containers
Servlet containers implement the servlet specification (ie: provide implementation classes for the API) thus it is not needed to ship our products with implementations of the servlet specification. In the sample project we leverage maven (by way of the “cargo.maven.plugin“) to bootstrap our application with a Tomcat 8.5.3 container (implementing servlet 3.1 specification).
Maven Cargo plugin configuration for Tomcat 8.x
<plugin> <groupId>org.codehaus.cargo</groupId> <artifactId>cargo-maven2-plugin</artifactId> <configuration> <container> <containerId>tomcat8x</containerId> <artifactInstaller> <groupId>org.apache.tomcat</groupId> <artifactId>tomcat</artifactId> <version>${tomcat.version}</version> </artifactInstaller> </container> <configuration> <type>standalone</type> <home> ${project.build.directory}/apache-tomcat-${tomcat.version} </home> <properties> <cargo.servlet.port>8080</cargo.servlet.port> <cargo.logging>high</cargo.logging> <!-- Configure the users allowed to login via basic authentication. Takes form of user:password:role --> <cargo.servlet.users> rick:deckard:admin </cargo.servlet.users> </properties> </configuration> <deployables> <deployable> <groupId>${project.groupId}</groupId> <artifactId>${project.artifactId}</artifactId> <type>war</type> <properties> <context>/servletbasics</context> </properties> </deployable> </deployables> </configuration> </plugin>
- line 16: where to put the downloaded tomcat
- lines 24-26: users for authentication
- line 35: the context path of the application
6. Servlet Lifecycle
The following is the lifecycle of a typical servlet.
- The servlet is instantiated by the container and it’s
init(...)
method is called once. Typically servlets are instantiated once and are heavily concurrent in usage, although a container could pool multiple servlets that implement theSingleThreadModel
to cope with heavy load. - The servlets
service(...)
method is invoked for each request, if your servlet implements theHttpServlet
interface then the request is delegated to whatever convenience method you have implemented that matched the given request verb. - The
destroy(...)
method is invoked allowing us to hook into the lifecycle and terminate any resources used by the servlet gracefully. - The garbage collector reaps said servlet.
Orthodox usage of servlets is to have the container instantiate one and thread requests through it, because of this, ensure you use your servlets in thread safe way…
7. Servlet Filters
Servlet Filters are designed to intercept requests to servlets, jsp’s or even static HTML files. They also intercept responses back to clients and thus can be used to modify requests / responses or sometimes even block or redirect them based on specific criteria.
Some examples of this include:
- Authentication: intercepting requests to guard against unauthenticated users
- Compression: compressing responses back to clients
- Changing interchange format of request/response bodies
- Tracing requests / responses (we do in the sample project)
Sample Filter that blocks requests with a specific header value present
// valid for the enumerator route // we also specify a header to look out for to block requests if the header is present @WebFilter(urlPatterns = "/enumerator", initParams = { @WebInitParam(name = BlockingFilter.DISALLOW_HEADER_KEY, value = "poisonpill") }) public class BlockingFilter implements Filter { static final String DISALLOW_HEADER_KEY = "disallow-key"; private String disallowHeaderValue; @Override public void init(final FilterConfig filterConfig) throws ServletException { // get the header value this.disallowHeaderValue = filterConfig.getInitParameter(DISALLOW_HEADER_KEY); } @Override public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { final String value = ((HttpServletRequest) request).getHeader(this.disallowHeaderValue); final boolean disallow = Objects.isNull(value) ? false : true; // continue the request via the filter pipeline if the header is not present if (!disallow) { chain.doFilter(request, response); } else { // do not continue the filter pipeline but respond back to client final HttpServletResponse resp = (HttpServletResponse) response; resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED); resp.setContentType("text/plain"); resp.getWriter().write("Poison pill detected, stopping request"); } } @Override public void destroy() { } }
- line 3: our Filter is only intercepting requests for the
enumerator
route. We also configure a header value to guard against, should the value be present, we block the request - line 13: set the header value to match for, in this case
poisonpill
- line 22-31: should the header value match, we block the request and immediately respond back to the client, otherwise we continue the request by invoking the FilterChain, which is an abstract concept representing the remainder of the Filter pipeline and ultimate target servlet / jsp / HTML page
8. Servlet Listeners
The servlet specification allows us to define WebListener‘s which can react to certain events that occur in our web application. The events can be at a session, request, application-wide level and different types of WebListeners are designed to react to different events.
The following WebListeners exist for the different scenario’s:
Scope | WebListener Interface | Event |
---|---|---|
Web context | javax.servlet.ServletContextListener javax.servlet.ServletContextAttributeListener | ServletContextEvent ServletContextAttributeEvent |
Session | javax.servlet.http.HttpSessionListener javax.servlet.http.HttpSessionActivationListener javax.servlet.http.HttpSessionAttributeListener | HttpSessionEvent HttpSessionEvent HttpSessionBindingEvent |
Request | javax.servlet.ServletRequestListener javax.servlet.ServletRequestAttributeListener | ServletRequestEvent ServletRequestAttributeEvent |
Sample WebListener that caches an ExecutorService in the ServletContext
//a web listener that creates an ExecutorService on startup and adds it to the servlet context for servlets to use @WebListener public class LifecycleServletContextListener implements ServletContextListener { @Override public void contextInitialized(final ServletContextEvent sce) { sce.getServletContext().setAttribute(Constants.THREAD_POOL_EXECUTOR, Executors.newFixedThreadPool(2)); } @Override public void contextDestroyed(final ServletContextEvent sce) { final ExecutorService executor = (ExecutorService) sce.getServletContext().getAttribute(Constants.THREAD_POOL_EXECUTOR); executor.shutdown(); } }
- line 7: we create an ExecutorService and cache it in the ServletContext for all servlets in the application to utilize on the context initialized event.
- lines 12-13: we shutdown the ExecutorService on the destroyed context event
9. Servlet Context
The ServletContext serves as an application wide (non-distributed) context or API through which servlets interface with the container. All servlets are given access to the ServletContext upon initialization and this affords the servlets the chance to access attributes that it may require.
10. Async Servlet
Asynchronous processing is particularly useful under heavy load or situations where reading and writing large amounts of data is done at different speeds between client and server meaning one of the two entities are potentially sitting idle waiting for input from the other.
In the servlet 3.0 specification we were introduced to asynchronous processing inside of servlets, allowing long running tasks to be done in a separate thread to allow the request thread to return to the pool from whence it came to handle other requests.
With the servlet 3.1 specification we were given the feature of being able to read and write data between client and server in an asynchronous manner allowing potentially long reads and writes between client and server to be handled asynchronously in a non blocking manner, particularly useful with large streams of data which could block when reads and writes are done at different speeds. These features are facilitated via the ReadListener and WriteListener interfaces.
As part of the servlet 3.1 specification we have asynchronous processing support for servlets and filters.
Asynchronous servlet sample
// only handles GET requests // we initialize the configuration for this servlet with a WebInitParam representing the timeout in milliseconds for the asynchronous processing to take // we also indicate to the container that this servlet supports asynchronous processing @WebServlet(urlPatterns = "/snail/snailservlet", initParams = { @WebInitParam(name = "timeout", value = "5000") }, asyncSupported = true) public class SnailServlet extends HttpServlet { private static final String TIMEOUT_PARAM_KEY = "timeout"; private static final long serialVersionUID = 1L; protected final void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException { // required to put the request into asynchronous mode final AsyncContext asyncCtx = request.startAsync(); // not needed, but we are interested in the events generated from the // asynchronous processing asyncCtx.addListener(new SnailAsyncListener()); // we set our timeout for processing asyncCtx.setTimeout(Long.valueOf(getServletConfig().getInitParameter(TIMEOUT_PARAM_KEY))); // we retrieve our thread pool executor from the application wide // servlet context final ExecutorService executor = (ExecutorService) request.getServletContext().getAttribute(Constants.THREAD_POOL_EXECUTOR); // submit a runnable containing the AsyncContext for flusing the // response to executor.execute(new SnailHandler(asyncCtx)); } private static final class SnailHandler implements Runnable { private AsyncContext asyncContext; // receives a handle to the AsyncContext in order to flush the response. public SnailHandler(final AsyncContext asyncCtx) { this.asyncContext = asyncCtx; } @Override public void run() { PrintWriter out = null; try { // our snail is on a Sunday cruise Thread.sleep(Constants.DELAY); // retrieve the response from the given AsyncContext out = this.asyncContext.getResponse().getWriter(); out.write("Phew, decomposition is setting in waiting for this to complete"); System.out.printf("\nThread %s completed asynchronous processing", Thread.currentThread().getName()); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { if (!Objects.isNull(out)) { out.flush(); } this.asyncContext.complete(); } } } }
- A brief synopsis of the
SnailServlet
shows how we put the request into asynchronous mode, set a generalAsyncListener
to trap the events generated during asynchronous processing, use a different thread pool to execute the long running task and, when completed, write the response (ignoring any errors) back to the client.
11. Running the Program
The sample project can be downloaded and extracted to your file system. Once inside the project root folder you can do the following:
- Build the project by executing:
mvn clean install package
- Run the the project by executing:
mvn cargo:run
Apart from the WebListeners, which mostly log the events they are listening for, all the servlets write some content back to the browser / postman via a text/plain
content type. For convenience the sample project has the exported collection I used with postman to handle all the requests.
You can import these into postman and invoke the application once started. All will work save for the secureservlet
URL which will need to be invoked in the browser to trigger the basic authentication prompt.
The file is called servlet_basics.postman_collection.json
and is located in the root of the sample project folder. The file contents follows:
Sample Requests for Postman when invoking servlets
{ "variables": [], "info": { "name": "servlet_basics", "_postman_id": "1c08180e-cce3-7fff-d572-8ef3045f72d4", "description": "", "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" }, "item": [ { "name": "secure", "request": { "url": "http://localhost:8080/servletbasics/secure/secureservlet", "method": "GET", "header": [], "body": {}, "description": "Requires basic authentication.\nTo prove it works, paste same URL in browser and when challenged:\n\tuser: rick\n\tpassword: deckard" }, "response": [] }, { "name": "jsonbody", "request": { "url": "http://localhost:8080/servletbasics/jsonbodyservlet", "method": "POST", "header": [ { "key": "Content-Type", "value": "application/json", "description": "" } ], "body": { "mode": "raw", "raw": "{\n\t\"name\": \"joe\",\n\t\"age\": \"30\"\n}" }, "description": "Tests a json body post - the results are echoed back in text/plain" }, "response": [] }, { "name": "enumerator-ok", "request": { "url": { "raw": "http://localhost:8080/servletbasics/enumerator?kim=wilde&jennifer=rush", "protocol": "http", "host": [ "localhost" ], "port": "8080", "path": [ "servletbasics", "enumerator" ], "query": [ { "key": "kim", "value": "wilde", "equals": true, "description": "" }, { "key": "jennifer", "value": "rush", "equals": true, "description": "" } ], "variable": [] }, "method": "GET", "header": [], "body": {}, "description": "Enumerates all query string parameters from the query string in text/plain" }, "response": [] }, { "name": "enumerator-poisonpill", "request": { "url": { "raw": "http://localhost:8080/servletbasics/enumerator?kim=wilde&jennifer=rush", "protocol": "http", "host": [ "localhost" ], "port": "8080", "path": [ "servletbasics", "enumerator" ], "query": [ { "key": "kim", "value": "wilde", "equals": true, "description": "" }, { "key": "jennifer", "value": "rush", "equals": true, "description": "" } ], "variable": [] }, "method": "GET", "header": [ { "key": "poisonpill", "value": "true", "description": "" } ], "body": {}, "description": "Contains a header (poisonpill) which will cease the reqeust processing pipeline and return a 401 to the user." }, "response": [] }, { "name": "snail", "request": { "url": "http://localhost:8080/servletbasics/snail/snailservlet", "method": "GET", "header": [], "body": {}, "description": "Invokes a long running servlet to demonstrate asynchronous processing." }, "response": [] } ] }
12. Summary
In this tutorial we covered some of the basics of servlets using the servlet 3.1 specification while running it in a Tomcat 8.5.3 container using maven on the command line.
We covered the most important abstractions for using servlets in a Http context and we demonstrated the usage of these abstractions with a set of sample servlets all contained within the sample application.
13. Download the Source Code
This was a Tutorial on the Basics of Servlets.
You can download the full source code of this example here: Basics of Servlets Tutorial