servlet

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.
 
 
 
 
 
 

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.

  1. 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 the SingleThreadModel to cope with heavy load.
  2. The servlets service(...) method is invoked for each request, if your servlet implements the HttpServlet interface then the request is delegated to whatever convenience method you have implemented that matched the given request verb.
  3. The destroy(...) method is invoked allowing us to hook into the lifecycle and terminate any resources used by the servlet gracefully.
  4. 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:

ScopeWebListener InterfaceEvent
Web contextjavax.servlet.ServletContextListener

javax.servlet.ServletContextAttributeListener

ServletContextEvent

ServletContextAttributeEvent

Sessionjavax.servlet.http.HttpSessionListener

javax.servlet.http.HttpSessionActivationListener

javax.servlet.http.HttpSessionAttributeListener

HttpSessionEvent

HttpSessionEvent

HttpSessionBindingEvent

Requestjavax.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();
    }
}

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 general AsyncListener 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.

Download
You can download the full source code of this example here: Basics of Servlets Tutorial

JJ

Jean-Jay Vester graduated from the Cape Peninsula University of Technology, Cape Town, in 2001 and has spent most of his career developing Java backend systems for small to large sized companies both sides of the equator. He has an abundance of experience and knowledge in many varied Java frameworks and has also acquired some systems knowledge along the way. Recently he has started developing his JavaScript skill set specifically targeting Angularjs and also bridged that skill to the backend with Nodejs.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button