Wednesday, March 21, 2018

Tomcat Customization

Introduction

Recently I had a business requirement to intercept the raw request to a servlet container.  The normal entry point to a servlet through the ServletContext was inadequate.  Having worked with Tomcat for a long time, since its earlier version 3.0, I downloaded the source code, and the stepped through the code.  While stepping through the code I noticed that the method service(org.apache.coyote.Request req, org.apache.coyote.Response resof class org.apache.catalina.connector.CoyoteAdapter initiates the request processing and the raw request could be intercepted in this method, and hard coded and is instantiated in the class org.apache.catalina.connector.Connector.  While tomcat server configuration file conf/server.xml provides xml attributes for customization of Connector, could not figure out a way to customize CoyoteAdapter.  With these observations, I added a new parameter to the Connector class to dynamically instantiate the adapter class as against the hard coded CoyoteAdpater class.  With this dynamic instantiation of the AdapterClass I was able to achieve the required customization of Tomcat.  Details of the code changes are detailed in this blog.

Server Configuration


conf/server.xml is the configuration file for Tomcat.  The connector element as shown below is the one which provided the configuration parameters for Tomcat. 

<Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />

For dynamically instantiating the adapter, I added a new parameter called adapterClassName.  With the addition of the new parameter the Connector element changed to:


<Connector port="8080" protocol="HTTP/1.1"
               adapterClassName="<my custom adapter full class name>"
               connectionTimeout="20000"
               redirectPort="8443" />

Modifications to Connector

To read adapterClassName from the server configuration file added a new attribute 
adapterClassName to the Connector class, org.apache.catalina.connector.Connector and provided accessor and mutator methods.
To provide CoyoteAdpater as the default adapter and to dynamically instantiate the adapter based on the attribute specified in the server configuration, code related to adapter instantiation was changed from:
        adapter = new CoyoteAdapter(this);
to:
if(adapterClassName == null) {
adapter = new CoyoteAdapter(this);
} else {
        try {
Class<?> clazz = Class.forName(adapterClassName);
Constructor<Adapter> constructor = (Constructor<Adapter>) clazz.getConstructor(this.getClass());
adapter = (Adapter) constructor.newInstance(this);
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
As can be seen dynamic instantiation of custom adapter was no different than the existing instantiation except that reflection was used to instantiate the adapter.

Extending CoyoteAdpater

The class CoyoteAdpater is about 300 lines of code.  Instead of trying to rewrite the code, I went with the option of extending class and override the service method.

Compiling Tomcat

Since the custom adapter class is dynamically invoked the Tomcat's libraries are independent of the custom adapter and hence existing ant scripts in Tomcat's source code did not require any changes to compile the code.

Compiling Custom Adpater

Custom adpater class is dependent on several Tomcat libraries.  As such a separate Java project was created, with Tomcat libraries as dependencies.  A jar file containing the custom container was created, added to Tomcat lib folder.  Probably the jar file can be added to Tomcat's endorsed directory.  With these few changes I was able to see that custom adapter was instantiated and was working similar to CayoteAdapter.
The CustomAdpater can potentially be used for customizing Tomcat, and there could be several uses.  A few of them would be a proxy server for Microservices where microservice internal location could be determined from a variety of resources.

Summary

Intercepting raw http request in Tomcat requires Tomcat customization.  A simple solution using dynamic invocation was provided to intercept http raw request, and Tomcat customization was presented.  The minimal code changes required for Tomcat customization are presented.



No comments:

Post a Comment