目次

Version 5, last updated by Meddix at Nov 28 02:17 UTC

This wiki entry was originally written by Timothy Perrett and was lifted with permission from his blog which can be found at blog.getintheloop.eu

Before we start this discussion, its important that you know (and understand) the importance of partial functions in scala. If you’re 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:


/products/some-product

/** maps onto **/
./webapp/products/show.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.statelessRewrite.prepend(NamedPF("ProductExampleRewrite") {
  case RewriteRequest(
      ParsePath("products" :: product :: Nil, _, _,_), _, _) => 
    RewriteResponse(
      "products/show" :: Nil, Map("product" -> product)  // Use webapp/products/show.html
  )
})

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 “statelessRewrite” – a RuleSeq[PartialFunction] – 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 statelessRewrite 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 signiture:


/**
 * options for RewriteRequest
 */
case class RewriteRequest(
  val path : ParsePath, 
  val requestType : RequestType, 
  val httpRequest : HttpServletRequest
) 

/**
 * options for ParsePath
 */
case class ParsePath(
  val partPath : List[String], 
  val suffix : String, 
  val absolute : Boolean, 
  val endSlash : Boolean
) 

/**
 * options for requestType object
 */
GetRequest
PostRequest
PutRequest
DeleteRequest

The first argument in the RewriteRequest is a ParsePath, this is one of the primary URI matching mechanisms and enables you to define detailed paramaters 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" :: Nil, "", 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/products/show.html with 
 * a paramater called "product" from
 * a parameter placeholder called product
 */
RewriteResponse("products/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")

statefulRewrite vs. statelessRewrite

LiftRules.statelessRewrite has a brother called statefulRewrite – also of type RuleSeq[PartialFunction] -, which means that they are used exactly the same way described above.

The difference resides in the fact that when Lift processes a StatefulRewrite rule it takes place within the scope of the S state, so SessionVars and other session-related information is available.

One practical example of statefulRewrite is a conditional rewrite that uses information about the current logged in user:

Let’s suppose you want /user/[username] to redirect to a user’s profile page, but you want to redirect to a different page if the username passed in the URL is the username of the current logged in user.
The following code snippet does that:


LiftRules.statefulRewrite.append {
  case RewriteRequest(ParsePath("user" :: username :: Nil,_,_,_),_,_) 
  if(User.findByUserName(username).isDefined) =>
    if(username == User.currentUser.map(_.username.is).openOr(null))
      RewriteResponse("profile" :: "view_self" :: Nil)				
    else 
      RewriteResponse("profile" :: "view" :: Nil, Map("username" -> username))
}

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.