URL Rewriting with the Lift Framework

With my on-going effort to write more documentation and articles for Lift I’ve decided to write a walk through of Lifts dispatching and rewriting mechanisms.

Before we start this discussion, its important that you know (and understand) the importance of partial functions in scala. If your not familiar, check out this article - it should fill you in on all the particulars.

Application Boot

If your not familiar with Lift or are new, you should understand that anything of consequence that changes the application environment must in someway hook into the boot-up cycle - the default looks something like this:

class Boot {
  def boot {
    // where lift will look for snippets
    LiftRules.addToPackages("eu.getintheloop.tutorial")
  }
}

Ok, so not a great deal going on there… this is however a bare-bones lift boot class. In our case, we want to add a rewrite so that the following mappings take place

/product/some-product-link
// maps onto
./webapp/product-display.html

Its important to note that rewriting is just that, its not used for redirects or any other such activity - its 100% for URI translation and interpretation.

Adding a Rewrite

Pretty much all of application configuration within Lift is done through the LiftRules object - its the central place for configuration PFs and operation vars. So, how do we use it? Well, first add the following to the top of your Boot.scala file.

import _root_.net.liftweb.http.LiftRules

Next, add the following code below the snippet package definition:

LiftRules.rewrite.prepend(NamedPF("ProductExampleRewrite") {
  case RewriteRequest(
      ParsePath("product" :: product :: Nil, _, _,_), _, _) => 
    RewriteResponse(
      "product-display" :: Nil, Map("product" -> product)
  )
})

Rewrite matching in detail

So lets step through this part by part… we already know about the LiftRules object and what its for, and one of its var properties is “rewrite” - a RuleSeq - and has the notion of both prepending values, and appending them. In this case, we prepend this rewrite rule, meaning that it will execute before any other rewrite rules previously added to the rewrite var.

List[String] is used to match the incoming path - lets take a closer look at our request object and the parameters we pass. From the scaladocs, we can see that RewriteRequest object has the following signature:

The first argument in the RewriteRequest is a ParsePath, this is one of the primary URI matching mechanisms and enables you to define detailed parameters on which request to match and which to ignore. Lets take a look at some various RewriteRequest examples:

// 
// example 1.
// matches: GET /some/demo/path
// 
RewriteRequest(
  ParsePath("some" :: "demo" :: "path" :: Nil, "", true, false), 
  GetRequest, _
)
//
//example 2.
//matches: PUT /some/image.png
//
RewriteRequest(
  ParsePath("some" :: "image" :: Nil, "png", true, false), 
  PutRequest, _
)
//
//example 3.
//matches: * /some/demo/
//
RewriteRequest(
  ParsePath("some" :: "demo" :: "index", "", true, true), _, _
)
//
//example 4.
//matches: GET /product/[item]/details
//
RewriteRequest(
  ParsePath("product" :: item :: "details" :: Nil, "", true, true), 
    GetRequest, _
)

So far we’ve seen how to configure the incoming request - to complete the picture, lets now take our request handling and couple that up with matching the response and mapping parameters so they are then available in the rest of our code via S.param.

The RewriteRequest object has several overload apply methods to keep the verbosity of your code to a minimum - namely, these overloads are:

def apply(path : ParsePath, params : Map[String, String])
def apply(path : List[String])
def apply(path : List[String], suffix : String)
def apply(path : List[String], params : Map[String, String])

We can see that this gives us a bunch of flexibility depending on our needs - straight rewrite, rewrite with params etc etc. Lets take a look at some examples:

Rewrite to a html file in webapp/show.html with no params

RewriteResponse("show" :: Nil)

Rewrite to a html file in webapp/show.html with a paramater called “product” from a parameter placeholder called product

RewriteResponse("show" :: Nil, Map("product" -> product))

Rewrite to a html file in webapp/example.pdf with no params, but a pdf suffix:

RewriteResponse("example" :: Nil, "pdf")

We have now looked at both requests and response - so moving back to our original example I hope you can see how it now works. One thing we have no explored is how to access the various parameters you might configure in your snippets / other application code. The answer my friends, is simple:

S.param("product").openOr("fail over product")

By now I presume you are a rewriting guru!… that might be a bit overboard, but I hope you found this guide informative and useful.

comments powered by Disqus