Mix protocols transparently in Struts

Extend Struts to implement a mixed protocol solution for Web applications

Sponsored by the Apache Software Foundation, Struts is an open source framework developed by the Jakarta Project. As stated on its homepage, Struts encourages architectures based on the Model-View-Controller (MVC), or Model 2, design paradigm. As many Web application developers have discovered, part of Struts's power lies in how easily developers can add extensions to its controller API classes as well as the tag libraries that comprise its view capabilities.

In "Mix Protocols Transparently in Web Applications" (JavaWorld, February 2002), I proposed extending Struts to incorporate a solution for mixing the HTTP and HTTPS protocols in Web applications. In this article, I walk through the steps necessary to extend Struts in that way and leverage the framework to add even more features to our solution.

Review

In my previous article, I discussed how many Web applications today transmit sensitive data, such as financial information, between the browser and Web server, in both directions. These applications usually employ the Secure Sockets Layer (SSL) and its companion protocol, HTTP over SSL (HTTPS), to prevent anyone monitoring Web transmission from viewing application data. Developing, deploying, and maintaining an application with mixed protocols can prove difficult, so I developed a solution utilizing J2EE's (Java 2 Platform, Enterprise Edition) redirect mechanism for MVC Web applications using servlets and JSPs (JavaServer Pages). The solution's full algorithm completes the following:

  • It determines the protocol and port number used to request the Web resource
  • If that protocol matches the protocol and port number we want for this resource, it does nothing
  • Otherwise, it checks for a query string
  • If no query string exists, it checks for request body parameters
  • If request body parameters exist, it converts these parameters to a query string
  • If any request attributes exist, it temporarily moves them to the session in a Map object
  • Finally, the algorithm performs a redirect to the same URL, using the correct protocol, modifying the specified port number, if necessary, and adding any existing or generated query string

Near the end of my previous article, I demonstrated the same mechanism for a Struts application and suggested extending Struts to incorporate the solution.

The Struts framework

Before we begin extending Struts, we must first understand how Struts works (for this article, we use the 1.0.1 release). In a typical Struts MVC application, a request made to a Struts action (in the controller role) might invoke some database or business operation and then forward the resulting JavaBean data objects (the model) to a JSP (the view) for display. The relationships between the classes that comprise the actions and the JSPs are defined at deployment time in an XML configuration file typically named struts-config.xml.

To better see how this happens, let's look closer at the Struts classes and files involved. The ActionServlet class defined in the org.apache.struts.action package provides the power behind the Struts framework. By processing all Struts requests and then calling the proper Struts action, this servlet serves as a Struts application's main controller. The servlet entry in our servlet's web.xml deployment descriptor looks like this:

  <servlet id="Servlet_1">
    <servlet-name>action</servlet-name>
    <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
    <!-- Struts Config -->
    <init-param>
      <param-name>config</param-name>
      <param-value>/WEB-INF/struts-config.xml</param-value>
    </init-param>
    <!- other initialization parameters -->
    <load-on-startup>1</load-on-startup>
  </servlet>

The config initialization parameter defines the location of the Struts definition file, struts-config.xml.

To correctly distinguish Struts action requests, we create a servlet-mapping entry in the deployment descriptor, indicating that any URL request matching the pattern /do/* should forward to ActionServlet. That entry looks like this:

  <servlet-mapping>
    <servlet-name>action</servlet-name>
    <url-pattern>/do/*</url-pattern>
  </servlet-mapping>

For example, the ActionServlet will process a request to the URL /do/myAction, whereas it won't process a request to /doMyAction.

A typical action definition entry in the struts-config.xml file resembles this:

    <action path="/myAction" type="test.ssl.MyAction" >
      <forward name="success" path="/home.jsp" />
      <forward name="failure" path="/error.jsp" />
    </action>

This entry specifies that the test.ssl.MyAction class will execute the action named myAction. In addition to whatever request-processing logic MyAction contains, it also includes logic to determine which Web resource named in the forward tags to forward the request to. The MyAction class, or any class defined as an action, must extend the org.apache.struts.action.Action class. The Action class includes a method named perform() for processing HTTP (or HTTPS) requests. When extending Action to define a Struts action, you should override perform() to define logic similar to what you might define in a servlet's doGet() or doPost() methods. ActionServlet calls this perform() method when it processes a Struts action request.

When a Struts Web application deploys, the ActionServlet reads the struts-config.xml file and creates a mapping for each action defined therein for its specified class type and forward page elements. Each mapping is stored within an ActionMapping class instance, also found in the org.apache.struts.action package. ActionMapping contains a property for every attribute or child element of the action file element. All ActionMapping instances are then stored in an ActionMappings class instance. Figure 1 displays the diagram for these four classes.

Figure 1. UML diagram of the Struts 1.0 action-processing classes

Putting it all together, we see that when the ActionServlet receives a request, it searches its associated ActionMappings object to find the specific ActionMapping instance with the same name as the Struts action specified in the request. The ActionServlet then creates an instance of the Action subclass mapped to that action (if an instance does not already exist) and calls the perform() method on that instance.

For good measure, Struts also includes numerous custom tags to aid in JSP development. These tags are grouped into four libraries: bean, html, logic, and template.

Extend Struts

To incorporate our protocol-switching mechanism as a Struts extension, we first need a way to specify which action requests should transmit securely via HTTPS and which should transmit using HTTP. We specify that information in the struts-config.xml file.

Adding an attribute to the action tag element for specifying this secure/nonsecure choice appears tempting. However, that would require us to alter that file's document type definition (DTD), an action we should avoid. Fortunately, Struts provides a mechanism for easily adding properties to an action mapping: the set-property tag element. This subelement of the action tag element allows us to specify a property name and value for our action definition. After adding this element, our action definition now looks like this:

    <action path="/myAction" type="test.ssl.MyAction" >
      <set-property property="secure" value="true"/>
      <forward name="success" path="/home.jsp" />
      <forward name="failure" path="/error.jsp" />
    </action>

We will use the secure property as a flag where a value of true specifies transmission via HTTPS and false specifies HTTP. In our mixed protocol solution for this case, if ActionServlet receives a request for myAction via HTTP, ActionServlet will redirect that request back to myAction via HTTPS.

To use this new property, we define our own extension to the ActionMapping class named SecureActionMapping, which simply specifies the secure property addition. Here is the class definition:

package org.apache.struts.action;
public class SecureActionMapping extends ActionMapping {
    protected boolean secure;
    public void setSecure(boolean b){
        this.secure = b;
    }
    public boolean getSecure(){
        return this.secure;
    }
}

To actually use SecureActionMapping, we must tell the ActionServlet to use that class instead of ActionMapping. Struts again provides a mechanism for doing that: the ActionServlet's servlet entry in the web.xml deployment descriptor file. We simply need to add another initialization parameter, mapping, to tell ActionServlet which class to use for action mappings. The restriction: The class specified in the mapping element must extend ActionMapping. The ActionServlet's web.xml entry now looks like this:

  <servlet id="Servlet_1">
    <servlet-name>action</servlet-name>
    <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
    <!-- Struts Config -->
    <init-param>
      <param-name>config</param-name>
      <param-value>/WEB-INF/struts-config.xml</param-value>
    </init-param>
    <init-param>
      <param-name>mapping</param-name>
      <param-value>org.apache.struts.action.SecureActionMapping</param-value>
    </init-param>
    <!- other initialization parameters -->
    <load-on-startup>1</load-on-startup>
  </servlet>

Now when ActionServlet parses the struts-config.xml file, it creates a SecureActionMapping instance for every action defined in that file. When it encounters a set-property element for the secure property, ActionServlet uses introspection to find the secure property mutator method, setSecure(), on that SecureActionMapping instance. ActionServlet's parsing logic is also smart enough to convert the specified value into the correct Boolean value.

SecureActionServlet

In addition to the standard config and mapping initialization parameters, we must specify two new properties: our application's port numbers for the HTTP and HTTPS protocols. We specify these the same way we specify the other initialization parameters. The values we assign to the port number parameters are stored in the two properties we define for our extension to the ActionServlet class.

This new extension, called SecureActionServlet, initializes these new properties. The servlet entry in the web.xml now has the following code:

  <servlet id="Servlet_1">
    <servlet-name>action</servlet-name>
    <servlet-class>org.apache.struts.action.SecureActionServlet</servlet-class>
    <!-- Struts Config -->
    <init-param>
      <param-name>config</param-name>
      <param-value>/WEB-INF/struts-config.xml</param-value>
    </init-param>
    <init-param>
      <param-name>mapping</param-name>
      <param-value>org.apache.struts.action.SecureActionMapping</param-value>
    </init-param>
    <!- other initialization parameters -->
    <init-param>
      <param-name>http-port</param-name>
      <param-value>8080</param-value>
    </init-param>
    <init-param>
      <param-name>https-port</param-name>
      <param-value>8443</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

Note that we substituted the name of our new SecureActionServlet subclass where ActionServlet was previously specified.

The default values for the port number properties, used in cases where values are not assigned through the initialization parameters, are the standard HTTP and HTTPS port numbers of 80 and 443, respectively. SecureActionServlet implements the logic necessary to determine whether to perform a redirect for channeling an action request to the proper protocol. This logic first finds the SecureActionMapping object for a Struts action. Next, it checks the value of that object's secure property to determine whether it must execute a redirect to the specified protocol for calling the Action subclass's perform() method. All other ActionServlet behavior remains unchanged in its subclass. The full source code for SecureActionServlet appears below:

1 2 Page 1
Page 1 of 2