Showing posts with label urlrewrite-filter. Show all posts
Showing posts with label urlrewrite-filter. Show all posts

Saturday, September 16, 2006

Search and Replace with UrlRewriteFilter

In a previous post, I wrote about a generic JUnit test for testing rewrite rules for Paul Tuckey's UrlRewriteFilter. In the course of using the filter, I found that while it is very easy to take a URL and tear it up into peices, then rearrange these peices into a new URL, it is very hard to do a simple search and replace on one or more of these peices. This article discusses the approach I took to do this.

Imagine that we have a website of science fiction book reviews, where users can browse our reviews by specifying the author name and book name in the URL. We assume also that we are running our website on a J2EE Servlet based environment where we can use the UrlRewriteFilter. Users looking for Isaac Asimov's Foundation Trilogy series would probably look at the following URLs.

1
2
3
/asimov/foundation.html
/asimov/foundation_and_empire.html
/asimov/second_foundation.html

Now imagine that you want to change the above URLs to be hyphenated instead of underscore separated because apparently the Googlebot likes hyphens better than underscores. Matt Cutts has a blog article here that explains why. So our new URLs would look like this:

1
2
3
/asimov/foundation.html
/asimov/foundation-and-empire.html
/asimov/second-foundation.html

Sound simple, right? Just do a s/_/-/g on the incoming URL. However, it is not as simple as it sounds. UrlRewriteFilter relies on regular expressions to split up the peices of the incoming URL, and backreferences to rearrange these peices into the rewritten URL. However, because UrlRewriteFilter has no support for inline replacement of backreferences (although it would seem like a useful thing to have), there is nothing you can do with the backreferences once you have matched and split the incoming URL. So our initial rule:

1
2
3
4
  <rule>
     <from>/(\w+)/(\w+(_\w+)*)\.html</from>
     <to>/$1/$2.html</to>
  </rule> 

matches the incoming URLs, but the URL rewrite has no visible effect. What we really want is to take the second back reference ($2) and pass it through our s/_/-/g substitution. We can achieve the same effect by passing it through an external class. The methods of the external class looks a lot like those of a Servlet. I call these FLets since they run within the context of a Filter. Here is the source for RegexReplaceFLet:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package org.urlrewrite;

import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class RegexReplaceFLet {

    private String searchFor;
    private String replaceWith;
    
    public void init(ServletConfig config) {
        this.searchFor = config.getInitParameter("searchFor");
        this.replaceWith = config.getInitParameter("replaceWith");
    }
    
    public void run(HttpServletRequest request, HttpServletResponse response) {
        String source = (String) request.getAttribute("source");
        String target = source.replaceAll(searchFor, replaceWith);
        request.setAttribute("target", target);
    }
}

Our rule will initialize our FLet by calling its init() method, where the init-params will be used to initialize the FLet. Then the run() method is invoked, which will read the "source" request attribute which contains our $2 backreference, do the regexp replace on it and populate the target request attribute.

The next step is getting to the request attribute. This is not documented in the manual, but a quick look at the UrlRewriteFilter sources told me that the request attribute "foo" can be got at from within the rule using %{attribute:foo}. So our new rule looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
  <rule>
    <from>/(\w+)/(\w+(_\w+)*)\.html</from>
    <set name="source">$2</set>
    <run class="org.urlrewrite.RegexReplaceFLet" neweachtime="true">
      <init-param>
        <param-name>searchFor</param-name>
        <param-value>_</param-value>
      </init-param>
      <init-param>
        <param-name>replaceWith</param-name>
        <param-value>-</param-value>
      </init-param>
    </run>
  </rule>
  <rule>
    <from>/(\w+)/(\w+(_\w+)*)\.html</from>
    <to>/$1/%{attribute:target}.html</to>
  </rule> 

Notice that there are two rules. The first rule has no "to" element. The second rule simply matches on the same "from" value and outputs the result. Why do we have two rules? Because the "to" element in the first rule does not see the attribute value of target that was set by RegexReplaceFLet. This could be a design quirk of rules engines in general, which usually precalculate some things for performance. However, spreading the rewrite logic between two rules solves the problem.

I hope this article has helped. It does appear that this kind of problem is quite rare, however, since I could not find any discussion of this problem on the Internet. Presumably, UrlRewriteFilter is more often used for rearranging parts of the URL, not replacing parts of them. However, this ability makes the UrlRewriteFilter much more powerful and versatile than it already is. The idea presented in this article can also be extended to do much more fancy rewriting, such as database lookups to get the id from a name embedded in the URL and pass the id to the rewritten URL.

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.