Rewrite to the edge - getting the most out of it! On GlassFish!

Markus Eisele
2
A great topic for modern application development is rewriting. Since the introduction of Java Server Faces and the new lightweight programming model in Java EE 6 you are struggling with pretty and simple, bookmarkable URLs. PrettyFaces was out there since some time and even if it could be called mature at the 3.3.3 version I wasn't convinced.

Mainly because of the fact that I had to configure it in xml. If you ever did a JSF project you know that this is something you do on top later on. Or never. With the last option being the one I have seen a lot. Rewrite is going to change that. Programmatic, easy to use and highly customizable. Exactly what I was looking for.

Getting Started
Nothing is easy as getting started with stuff coming from one of the RedHat guys. Fire up NetBeans, create a new Maven based Webapp, add JSF and Primefaces to the mix and run it on GlassFish.
First step for adding rewriting magic to your application is to add the rewrite dependencies to your project.

        <dependency>
            <groupId>org.ocpsoft.rewrite</groupId>
            <artifactId>rewrite-servlet</artifactId>
            <version>1.1.0.Final</version>
        </dependency>
That isn't enough since I am going to use it together with JSF, you also need the jsf-integration.

  <dependency>
            <groupId>org.ocpsoft.rewrite</groupId>
            <artifactId>rewrite-integration-faces</artifactId>
            <version>1.1.0.Final</version>
        </dependency>
Next implement your own ConfigurationProvider. This is the central piece where most of the magic happens.Let's call it TricksProvider for now and we also extend the abstract HttpConfigurationProvider. A simple first version looks like this:
public class TricksProvider  extends HttpConfigurationProvider
{
   @Override
   public int priority()
   {
     return 10;
   }

   @Override
   public Configuration getConfiguration(final ServletContext context)
   {
     return ConfigurationBuilder.begin()
        .addRule(Join.path("/").to("/welcomePrimefaces.xhtml"));
    }
}
Now you have to register your ConfigurationProvider. You do this by adding a simple textfile named org.ocpsoft.rewrite.config.ConfigurationProvider to your applications /META-INF/services/ folder. Add the fully qualified name of your ConfigurationProvider implementation to it and you are done. If you fire up your application.

The Rewriting Basics
While copying the above provider you implicitly added your first rewriting rule. By requesting http://host:8080/yourapp/ you get directly forwarded to the Primefaces welcome page generated by NetBeans. All rules are based on the same principle. Every single rule consists of a condition and an operation. Something like "If X happens, do Y". Rewrite knows two different kinds of Rules. Some preconfigured ones (Join) starting with "addRule()" and a fluent interface starting with defineRule(). This is a bit confusing because the next major release will deprecate the defineRule() and rename it to addRule(). So most the examples you find (especially the test cases in the latest trunk) are not working with the 1.1.0.Final.
Rewrite knows about two different Directions. Inbound and Outbound. Inbound is most likely working like every rewriting engine you know (e.g. mod_rewrite). A request arrives and is forwarded or redirected to the resources defined in your rules. The Outbound direction is little less. It basically has a hook in the encodeURL() method of the HttpServletRequest and rewrites the links you have in your pages (if they get rendered with the help of encodeURL at all). JSF is doing this out of the box. If you are thinking to use it with JSPs you have to make sure to call it yourself.

Forwarding .html to .xhtml with some magic
Let's look at some stuff you could do with rewrite. First we add the following to the TricksProvider:
.defineRule()
.when(Direction.isInbound()
.and(Path.matches("{name}.html").where("name").matches("[a-zA-Z/]+")))
.perform(Forward.to("{name}.xhtml"));
This is a rule which is looking at inbound requests and checks for all Patch matches {name}.html which confirm to the regular expression pattern [a-zA-Z/]+ and Forwards those to {name}.xhtml files.
If this rule is in place all requests to http://host:8080/yourapp/something.html will end up being forwarded to something.xhtml. Now your users will no longer know that you are using fancy JSF stuff underneath and believe you are working with html :) If a url which isn't matching the regular expression is requested, for example something like http://host:8080/yourapp/something123.html this simply isn't forwarded and if the something123.html isn't present in your application you will end up receiving a 404 error.

Rewriting Outbound Links
The other way round you could also add the following rule:
.defineRule()
.when(Path.matches("test.xhtml")
.and(Direction.isOutbound()))
.perform(Substitute.with("test.html"))
You imagine what this is doing, right? If you have a facelet which contains something like this:
   <h:outputLink value="test.xhtml">Normal Test</h:outputLink>
The link that is rendered to the user will be rewritten to test.html. This is the most basic action for outbound links you will ever need. Most of the magic happens with inbound links. Not a big surprise looking at the very limited reach of the encodeURL() hook.

The OutputBuffer
The most astonishing stuff in rewrite is called OutputBuffer. At least until the release we are working with at the moment. It is going to be renamed in 2.0 but for now let's simply look at what you could do. The OutputBuffer is your hook to the response. Whatever you would like to do with the response before it actually arrives at your client's browser could be done here. Thinking about transforming the markup? Converting css? Or even GZIP compression? Great, that is exactly what you could do. Let's implement a simple ZipOutputBuffer
public class ZipOutputBuffer implements OutputBuffer {

    private final static Logger LOGGER = Logger.getLogger(ZipOutputBuffer.class.getName());

    @Override
    public InputStream execute(InputStream input) {
        String contents = Streams.toString(input);
        LOGGER.log(Level.FINER, "Content {0} Length {1}", new Object[]{contents, contents.getBytes().length});
        byte[] compressed = compress(contents);
        LOGGER.log(Level.FINER, "Length: {0}", compressed.length);
        return new ByteArrayInputStream(compressed);
    }

    public static byte[] compress(String string) {
        ByteArrayOutputStream os = new ByteArrayOutputStream(string.length());
        byte[] compressed = null;
        try {
            try (GZIPOutputStream gos = new GZIPOutputStream(os)) {
                gos.write(string.getBytes());
            } 
            compressed = os.toByteArray();
            os.close();
        } catch (IOException iox) {
            LOGGER.log(Level.SEVERE, "Compression Failed: ", iox);
        }
        return compressed;
    }
}
As you can see, I am messing around with some streams and use the java.util.zip.GZIPOutputStream to shrink the stream received in this method. Next we have to add the relevant rule to the TricksProvider:
.defineRule()
.when(Path.matches("/gziptest").and(Direction.isInbound()))
.perform(Forward.to("test.xhtml")
.and(Response.withOutputBufferedBy(new ZipOutputBuffer())
.and(Response.addHeader("Content-Encoding", "gzip"))
.and(Response.addHeader("Content-Type", "text/html"))))
An inbound rule (we are not willing to rewrite links in pages here .. so it has to be inbound) which adds the ZipOutputBuffer to the Response. Also take care for the additional response header (both) unless you want to see your browser complaining about the content I have mixed up :) That is it. The request http://host:8080/yourapp/gziptest now delivers the test.xhtml with GZIP compression. That is 2,6KB vs. 1,23 KB!! Less than half of the size !! It's not very convenient to work with streams and byte[]. And I am not sure if this will work with larger page sizes in terms of memory fragmentation, but it is an easy way out if you don't have a compression filter in place or only need to compress single parts of your application.

Enhance Security with Rewrite
But that is not all you could do: You could also enhance the security with rewrite. Lincoln has a great post up about securing your application with rewrite. There are plenty of possible examples around how to use this. I Came up with a single use-case where didn't want to use the welcome-file features and prefer to dispatch  users individually. While doing this I would also inspect their paths and check if the stuff they are entering is malicious or not. You could either do it with the .matches() condition or with a custom constraint. Add the following to the TricksProvider:
Constraint<String> selectedCharacters = new Constraint<String>() {
        @Override
        public boolean isSatisfiedBy(Rewrite event,
                EvaluationContext context, String value) {
            return value.matches("[a-zA-Z/]+");
        }
    };

And define the following rule:
.defineRule()
.when(Direction.isInbound()
.and(Path.matches("{path}").where("path").matches("^(.+)/$")
.and(Path.captureIn("checkChar").where("checkChar").constrainedBy(selectedCharacters))))
.perform(Redirect.permanent(context.getContextPath() + "{path}index.html"))
Another inbound modification. Checking the path if it is has a folder pattern and capturing it in a variable which is checked against the custom constraints. Great! Now you have a save and easy forwarding mechanism in place. All http://host:8080/yourapp/folder/ request are now rewritten to http://host:8080/yourapp/index.html. If you look at the other rules from above you see, that the .html is forwarded to .xhtml ... and you are done!

Bottom Line
I like working with rewrite a lot. It feels easier than configuring the xml files of prettyfaces and I truly enjoyed the support of Lincoln and Christian during my first steps with it. I am curious to see what the 2.0 is coming up with and I hope that I get some more debug output for the rules configuration just to see what is happening. The default is nothing and it could be very tricky to find the right combination of conditions to have a working rule.
Looking for the complete sources? Find them on github. Happy to read about your experiences.

Where is the GlassFish Part?
Oh, yeah. I mentioned it in the headline, right? That should be more like a default. I was running everything with latest GlassFish 3.1.2.2 so you can be sure that this is working. And NetBeans is at 7.2 at the moment and you should give it a try if you haven't. I didn't came across a single issue related to GlassFish and I am very pleased to stress this here. Great work! One last remark: Before you are going to implement the OutputBuffer like crazy take a look at what your favorite appserver has in stock already. GlassFish knows about GZIP compression already and it simply can be switched on! Might be a good idea to think twice before implementing here.

Post a Comment

2Comments

  1. Markus, this is a fantastic article! Great and very useful information.

    I'd just like to add a note that you don't need a separate outbound rule to achieve your .html conversion.

    You can just use a single Join!

    .addRule(Join.path("{name}.html").to("{name}.xhtml").where("name").matches("[a-zA-Z/]+")));

    Cheers, thanks again! Very awesome.

    ~Lincoln

    ReplyDelete
  2. Excellent article about this very interesting framework!

    I am definitely going to put the "Rewriting Outbound Links" section into practice.

    ReplyDelete
Post a Comment