Saturday, August 26, 2006

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:

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:

importjava.io.InputStream;importjunit.framework.TestCase;importorg.apache.log4j.Logger;importorg.tuckey.web.MockRequest;importorg.tuckey.web.MockResponse;importorg.tuckey.web.filters.urlrewrite.Conf;importorg.tuckey.web.filters.urlrewrite.RewrittenUrl;importorg.tuckey.web.filters.urlrewrite.UrlRewriter;importorg.tuckey.web.filters.urlrewrite.utils.Log;/** * Simple test case to determine if the rewrite configuration works as * expected. */publicclassUrlRewriteConfigurationTestextendsTestCase{privatestaticfinalLoggerlog=Logger.getLogger(UrlRewriteConfigurationTest.class);privatestaticfinalStringREWRITE_CONF="urlrewrite.xml";privateConfconf;/** * Setup the UrlRewriteFilter configuration. */protectedvoidsetUp(){Log.setLevel("DEBUG");// to make the RewriteFilter code log messagesInputStreamistream=getClass().getResourceAsStream("/"+REWRITE_CONF);conf=newConf(istream,REWRITE_CONF);}publicvoidtestRewrite1()throwsException{StringfromUrl="/d/foo/bar1/show.jsp?id=321456";StringtoUrl="/shownew.html?type=bar1&fooId=321456";assertRewriteSuccess(fromUrl,toUrl,conf);}publicvoidtestRewrite2()throwsException{StringfromUrl="/d/foo/bar2/show.jsp?id=321456";StringtoUrl="/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. */privatevoidassertRewriteSuccess(StringfromUrl,StringtoUrl,Confconf)throwsException{UrlRewriterrewriter=newUrlRewriter(conf);MockRequestrequest=newMockRequest(fromUrl);MockResponseresponse=newMockResponse();RewrittenUrlrewrittenUrl=rewriter.processRequest(request,response);assertNotNull("Could not rewrite URL from:"+fromUrl+" to:"+toUrl,rewrittenUrl);StringrewrittenUrlString=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.

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.

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.

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.

About me

I am a programmer interested in Semantic Search, Ontology, Natural Language Processing and Machine Learning. My programming languages of choice are Java, Scala, and Python. I love solving problems and exploring different possibilities with open source tools and frameworks.