Playing with UrlRewrite Filter
Introduction
Nowadays, every modern web development environment offers a way to manipulate URL on the fly using a rule-based configuration rather than hard coded program logic. Probably every approach has its origins in famous Apache mod_rewrite module. In the Java world, de facto standard is a wonderful Url Rewrite Filter. It can do everything what you expect from such tool and has some delicious topping as a benefit:
- analyze URL against a pattern (regexp or wildcard)
- analyze all possible HTTP data like cookies, request parameters, host, remote info, etc
- change data like cookie, session, request attributes
- redirect or forward to static or dynamically(based on analysis data) formed URL
- run your own rolled Java code (e.g. logging, statistics)
During the last few days I got more familiar with this powerful tool and want to show the value it can bring into any Java based Web application. I won’t describe the syntax of the configuration file because there is a comprehensive manual which outlines all options. Also I’m not going to describe simplest use cases, let’s start from something interesting like the first step in integration with affiliate partner.
For instance, in case of integration with Commission Junction affiliate service provider, your application have to set a cookie which identifies a user that come from an affiliate partner, and then redirect the user to the page stated as a request parameter.
So, there are some steps I want to implement with UrlRewrite library:
1. User came to your site by clicking on a banner with link http://example.com/?CJURL=http%3a%2f%2fexample.com%2fregister%2fnew.html (parameter is encoded string http://example.com/register/new.html) Tip: to url encode test data you can use a simple online encoding tool.
2. Your application recognizes a CJURL request parameter and set a cookie (expire time >= 24h) to know that the user came from CJ affiliate program
3. Redirect the user to the landing page equal to CJURL parameter
Setting cookies
This snippet sets cookie if a user comes from our affiliate partner. Despite of the fact that in UrlRewrite manual stated that expire time have to be set in minutes, it actually is in seconds. Probably it is just a typo in the manual, but be careful and alway test real expire date with your browser. It is possible to set all parameters (value can be in the format “[value][:domain[:lifetime[:path]]]”), although we use only two parameters.
<condition type="query-string">^CJURL=(.*)$</condition>
<from>^(.*)$</from>
<!– Affiliate service provider –>
<!– expire time in seconds–>
<set type="cookie" name="asp">cj::86400</set>
</rule>
Redirecting to a request parameter value
It seems to be a simple task to get a value from request query and redirect to url created from that parameter. But you can’t do it explicitly in UrlRewrite. Setting request parameters in
To make it clear I’ll give some illustration:
1. The ideal solution if UrlRewrite supported it would be:
<condition type="query-string">^CJURL=(.*)$</condition>
<from>^(.*$)</from>
<to type="redirect">%{parameter:CJURL}</to>
But as I stated earlier, unfortunately we can’t use request parameter value in <to> clause.
2. If we had a simple parameter like [a-zA-z0-9] without special symbols used in regular URL we can parse it from query string. This approach can be used in beautifying urls according to REST style:
<rule match-type="wildcard">
<from>/*/*/*</from>
<to type="forward">/$1.do?$2=$3</to>
</rule>
But we need to decode this strings, because we can’t redirect to http%3a%2f%2fexample.com before we convert it to http://example.com. This conversion is done by container when you ask for a request parameter value. Therefore this solution WON’T work either:
<condition type="query-string">^CJURL=(.*)$</condition>
<from>^(.*)?CJURL=(.*)$</from>
<to type="redirect">$2</to>
</rule>
3. Luckily we can use in <to> clause a request attribute. The only problem we have to solve is to write the request parameter value to request attribute somehow, and then read this attribute in <to> clause. I didn’t investigate heavily is it possible using UrlRewrite declarative facilities and employed another great option: calling self rolled Java code. The final version is something like:
urlrewrite.xml:
<rule>
<note>Commission Junction affiliate program</note>
<condition type="query-string">^CJURL=(.*)$</condition>
<from>^(.*)?CJURL=(.*)$</from>
<run class="com.example.web.util.RequestToAttributeSetter">
<init-param>
<param-name>parameterName</param-name>
<param-value>CJURL</param-value>
</init-param>
</run>
</rule>
<rule>
<condition type="query-string">^CJURL=(.*)$</condition>
<from>^(.*)?CJURL=(.*)$</from>
<to type="redirect">%{attribute:CJURL}</to>
<!– Affiliate service provider –>
<!– expire time in minutes –>
<set type="cookie" name="asp">cj::86400</set>
</rule>
Java code:
import javax.servlet.ServletConfig;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
/**
* Workaround of UrlRewrite filter restriction which doesn’t allow setting request parameter as
* an redirect target in <to> tag. <br/>
* Similar solution: http://sujitpal.blogspot.com/2006/09/search-and-replace-with.html
* @author Andrey Gomilko
*/
public class RequestToAttributeSetter {
private String parameterName;
public void run(ServletRequest request, ServletResponse response) {
if (parameterName != null) {
HttpServletRequest req = (HttpServletRequest) request;
Object value = req.getParameter(parameterName);
req.setAttribute(parameterName, value);
}
}
public void init(ServletConfig config) {
this.parameterName = config.getInitParameter("parameterName");
}
public void destroy() {
}
}
Unit testing of UrlRewrite configuration
Let’s assume you have already written your own urlrewrite.xml file with filter configuration and want to test it. If you decide testing rules on the working server (e.g. Tomcat), you’ll have to restart it every time you have changed something. More convenient way is to use JUnit tests for this purpose. Fortunately URLRewrite has all necessary internal classes and request/response mock classes to dry run this filter without container and analyze the result. The very good example which covers basic use cases is A JUnit test for UrlRewriteFilter. To test rules corresponding to CJ example we need to test cookies in addition to standard tests. Since our UrlRewrite rule is based on the host HTTP header value, you have to set header values to the MockRequest because it doesn’t parse propagated url itself. To test CJ example, I got as a base the test snippet from A JUnit test for UrlRewriteFilter and added some specific methods:
UrlRewriter rewriter = new UrlRewriter(conf);
MockRequest request = new MockRequest(fromUrl);
try{
URL url = new URL(fromUrl);
request.addHeader("host", url.getHost());
if (url.getQuery() != null) {
request.setQueryString(url.getQuery());
StringTokenizer st = new StringTokenizer(url.getQuery(), "&");
while(st.hasMoreElements()) {
String token = st.nextToken();
String[] parts = token.split("=");
String name = parts[0];
String value = parts[1];
request.addParameter(name, java.net.URLDecoder.decode(value, "UTF-8"));
}
}
} catch(MalformedURLException me){
// do nothing
}
MockResponse response = new MockResponse();
RewrittenUrl rewrittenUrl = rewriter.processRequest(request, response);
assertNotNull("Could not redirect URL from:" + fromUrl + " to:" + toUrl, rewrittenUrl);
assertEquals("Redirect from:" + fromUrl + " to:" + toUrl + " did not succeed", toUrl
, rewrittenUrl.getTarget());
assertTrue(rewrittenUrl.isRedirect());
List cookies = response.getCookies();
assertEquals(1, cookies.size());
Cookie setCookie = (Cookie)cookies.get(0);
assertEquals(cookie.getMaxAge(), setCookie.getMaxAge());
assertEquals(cookie.getName(), setCookie.getName());
assertEquals(cookie.getValue(), setCookie.getValue());
}
public void testCJRewriteDecoded() throws Exception {
String fromUrl = "http://example.com/?CJURL=http%3a%2f%2fexample.com%2fregister%2fnew.html";
String toUrl = "http://example.com/reqister/new.html";
Cookie cookie = new Cookie("asp", "cj");
cookie.setMaxAge(86400); // 24h
assertRedirect(fromUrl, toUrl, cookie);
}
Conclusion
The main idea of such tools is to reduce coding and transfer all possible application logic to the well-proven tools which can be easily tuned through configuration files. This approach eliminates number of possible bugs and keep tied logic in one place. So, before writing your own filter, think once more about UrlRewrite, probably it’ll fit your needs!
And stay tuned!



July 18th, 2007 at 9:20 am
For testing regular expressions, I also find the Java applet on the Jakarta regexp site useful:
http://jakarta.apache.org/regexp/applet.html
August 15th, 2007 at 5:48 am
Jeee, I didn`t manage to read your post dear, but now you have no right to tell me that I don`t read your blog:)))
January 18th, 2008 at 6:41 am
Thank you for posting this information. It was very useful to me. I followed the link to the JUnit stuff and managed to get the Mock objects et al working in my Netbeans 5.5 environment. I added a comment on that to help others. I agree that the UrlRewrite tool is a very nice addition to my web developer bag of tricks. So far it has worked very well for me.
February 1st, 2008 at 11:44 am
hi, i am very new to urlrewrite. currently i am working to enable redirect from my J2EE application. i have configured web.xml with appropriate filter tags and filter mapping and i have also include my urlrewrite.xml in /WEB-INF/urlrewrite.xml. i am having problem with QUERY_String. i have tested my rules with simple url’s but i just can’t get Query_string url to work.
my quesry String url is follow’s:
/msh/msh_frame.jsp?url=mysite/test.htm >> which should be redirecting to another host as >> http://www.site.com/mysite/test.htm
the rule is defined under urlrewrite.xml as follows:
^/msh_frame.jsp?url=([^&;]*)$
http://www.mssm.edu/$1
i am pretty much sure that i am missing some syntax for query strings or my rule is not accurate. can i get any help. thanks in advance.
khan