Saturday, August 26, 2006

A JUnit test for UrlRewriteFilter

I recently had the need for URL Rewriting. Although my needs were rather simple, I resisted the urge to roll my own and opted instead to use Paul Tuckey's UrlRewriteFilter. I had a little trouble actually setting up the URL patterns, so at one point, I broke down and wrote myself a JUnit test which I could use to test my patterns without having to restart the application server every time. This blog entry contains details of this test.

Basically, the need for URL Rewriting arose because I re-implemented an existing application which was serving XML using JSP files. The JSP file was referred to in the calling URL by name. So for example:

1
http://my.company.com/myapp/d/foo/bar/show.jsp?id=123456

would route the request to ${docroot}/foo/bar/show.jsp on the web application myapp. All the processing logic was contained in the JSP file. When the need arose to create an XML which was slightly different from an existing one, the recommended approach was to copy show.jsp to a sibling directory bar1 and modify the copy to do the job. So the new JSP would now be accessible at:

1
http://my.company.com/myapp/d/foo/bar1/show.jsp?id=123456

Obviously, not the best way to reuse code, and refactoring is also much harder. My approach was to pull most of the common functionality into Java classes on the server and use a single dispatcher which uses a type parameter to decide which format to show. So, the new URL for the first URL above will look like this:

1
http://my.company.com/myapp/shownew.html?type=bar&fooId=123456

However, it turns out that the original design was done for a reason - the intent was to provide friendly and easy to remember URLs to clients. Obviously the new URL structure is not as friendly, and the client(s) should not have to suffer because we switched out the backend. The solution was to rewrite the URL internally, so the human friendly client URL is rewritten to a machine friendly URL for our dispatcher's consumption. The rewriting is not dynamic, there was no clear pattern in the existing URLs, so I planned to have entries in the urlrewrite.xml file for each of them. Something like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE urlrewrite PUBLIC "-//tuckey.org//DTD UrlRewrite 2.6//EN" "http://tuckey.org/res/dtds/urlrewrite2.6.dtd">
<urlrewrite>

    <rule>
        <from>/d/foo/bar1/show.jsp\?id=(\d+)</from>
        <to>/shownew.html\?type=bar1&amp;fooId=$1</to>
    </rule>

    <rule>
        <from>/d/foo/bar2/show.jsp\?id=(\d+)</from>
        <to>/shownew.html\?type=bar2&amp;fooId=$1</to>
    </rule>

</urlrewrite>

The JUnit test reads this configuration file from the classpath, then applies it to a set of specified fromUrl values and asserts that the fromUrl is rewritten with these rules into corresponding specified toUrl values. I sneaked a peek at the JUnit tests in the source distribution of the UrlRewriteFilter to come up with the right sequence of incantations to make the rewrite happen. Here is the code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import java.io.InputStream;

import junit.framework.TestCase;

import org.apache.log4j.Logger;
import org.tuckey.web.MockRequest;
import org.tuckey.web.MockResponse;
import org.tuckey.web.filters.urlrewrite.Conf;
import org.tuckey.web.filters.urlrewrite.RewrittenUrl;
import org.tuckey.web.filters.urlrewrite.UrlRewriter;
import org.tuckey.web.filters.urlrewrite.utils.Log;

/**
 * Simple test case to determine if the rewrite configuration works as
 * expected.
 */
public class UrlRewriteConfigurationTest extends TestCase {

    private static final Logger log = Logger.getLogger(UrlRewriteConfigurationTest.class);
    private static final String REWRITE_CONF = "urlrewrite.xml";

    private Conf conf;

    /**
     * Setup the UrlRewriteFilter configuration.
     */
    protected void setUp() {
        Log.setLevel("DEBUG"); // to make the RewriteFilter code log messages
        InputStream istream = getClass().getResourceAsStream("/" + REWRITE_CONF);
        conf = new Conf(istream, REWRITE_CONF);
    }

    public void testRewrite1() throws Exception {
        String fromUrl = "/d/foo/bar1/show.jsp?id=321456";
        String toUrl = "/shownew.html?type=bar1&fooId=321456";
        assertRewriteSuccess(fromUrl, toUrl, conf);
    }

    public void testRewrite2() throws Exception {
        String fromUrl = "/d/foo/bar2/show.jsp?id=321456";
        String toUrl = "/shownew.html?type=bar2&fooId=321456";
        assertRewriteSuccess(fromUrl, toUrl, conf);
    }

    /**
     * Assertion to rewrite the URL using the UrlRewriteFilter and verify
     * that fromUrl is rewritten to toUrl using rewriting rules in conf.
     * @param fromUrl the URL to be rewritten from.
     * @param toUrl the URL to be rewritten to.
     * @param conf the UrlRewriteFilter configuration.
     * @throws Exception if one is thrown.
     */
    private void assertRewriteSuccess(String fromUrl, String toUrl, Conf conf) throws Exception {
        UrlRewriter rewriter = new UrlRewriter(conf);
        MockRequest request = new MockRequest(fromUrl);
        MockResponse response = new MockResponse();
        RewrittenUrl rewrittenUrl = rewriter.processRequest(request, response);
        assertNotNull("Could not rewrite URL from:" + fromUrl + " to:" + toUrl, rewrittenUrl);
        String rewrittenUrlString = rewrittenUrl.getTarget();
        log.debug("URL Rewrite from:[" + fromUrl + "] to [" + rewrittenUrlString + "]");
        assertEquals("Rewrite from:" + fromUrl + " to:" + toUrl + " did not succeed", toUrl, rewrittenUrlString);
    }
}

This test succeeds and we have a working urlrewrite.xml file as a side effect. Making small changes to the source and target regular expressions and hitting [Alt]-[Shift]-X-T to run the JUnit test (in Eclipse) is far more convenient than having to restart the application server each time we need to test a change in the regular expression.

If you are in the process of creating your urlrewrite.xml file, I am sure you will find this JUnit test useful.

10 comments (moderated to prevent spam):

Anonymous said...

Very nice thank you! Helps me a lot!

Sujit Pal said...

Thanks, glad it helped. For me, it helped mainly by cutting down the update-deploy-restart cycle for the appserver.

-sujit

Charlie said...

There was a lot missing from the example. In Netbeans 5.5 I had to add to the test libraries the servlet-api.jar and also the the urlrewrite-2.6.0.jar to get the mock objects. They are not in the 3.04 urlrewrite jar. Also the log4j stuff was not initialized correctly and needed several things to make it happy. The istream was null, I had to fix that too so it would read the xml file. Finally it worked.

Sujit Pal said...

Hi Charlie, thanks for your comments. I work within maven2 on an existing web application, where the servlet-api.jar was already part of the classpath, and where the xml file lives in src/main/resources, so it is accessible to the getResourceAsStream() under the specified path. And obviously, since we are testing the functionality of urlrewrite, we will need the jar file for it. So I guess I consider these things a given. Sorry you had trouble setting it up.

Elmo said...

Hi Sujit,

Great example code, it's going to be really useful for testing inbound rules. Have you had any success testing outbound rules?

Regards

Elmo

Sujit Pal said...

Hi Elmo, thanks for the comment, and I am glad you think its going to be useful. I have never had a need to test outbound rules, so I can't say for sure if same sequence of calls would work for outbound rules, although the differences, if any, should be relatively small. We no longer use UrlRewrite though, the developer who took over the project after me found he could do it more efficiently using Apache HTTPD rewrite rules, so we now use that instead.

Elmo said...

This is what I came up with adfter a look at the docs for Urlrewrite.

protected static void assertOutboundRewriteSuccess(String fromUrl, String toUrl, Conf conf) {
UrlRewriter rewriter = new UrlRewriter(conf);
MockRequest request = new MockRequest(fromUrl);
MockResponse response = new MockResponse();
UrlRewriteWrappedResponse wrappedResponse = new UrlRewriteWrappedResponse(response, request, rewriter);
String result = wrappedResponse.encodeURL(fromUrl);
log.debug("URL Rewrite from:[" + fromUrl + "] to [" + result + "]");
assertEquals("Rewrite from:" + fromUrl + " to:" + toUrl + " did not succeed", toUrl, result);
}

Copy and paste into an IDE and reformat the code to make it legible :-)

Sujit Pal said...

Thanks Elmo, this is extremely useful. Thanks for the code.

Elmo said...

Hi again Sujit,

I moved on to JUnit4 so I decided to refresh your solution so I could decouple if from the TestCase class. Hope you approve :-)

Cheers

E

Sujit Pal said...

Hi Elmo, thanks for doing this and thanks for the link to your JUnit4 based solution. Of course I approve :-).