Enhanced stateless support in 2.4-master

351 views
Skip to first unread message

David Pollak

unread,
Aug 23, 2011, 5:05:23 PM8/23/11
to liftweb
Folks,

Based on some feedback from folks like Alex Black, I've enhanced Lift's support for stateless requests.

First, determining if a request should be stateless.  There's a new LiftRule:
  /**
   * Certain paths and requests within your application can be marked as stateless
   * and if there is access to Lift's stateful facilities (setting
   * SessionVars, updating function tables, etc.) the developer will
   * receive a notice and the operation will not complete.
   */
  val statelessReqTest = RulesSeq[StatelessReqTestPF]

  /**
   * The test between the path of a request, the HTTP request, and whether that path
   * should result in stateless servicing of that path
   */
  type StatelessReqTestPF = PartialFunction[StatelessReqTest, Boolean]

/**
 * The data structure that contains information to determine if the
 * request should be treated as a stateful or stateless request
 */
final case class StatelessReqTest(path: List[String], httpReq: HTTPRequest)

You can now test the HTTPRequest for stuff like user agent, cookies, etc. to determine if the request should be stateless.

Further, a Loc can be marked as stateless based on a function executed at request time via LocParams:

  /**
   * A function that calculates the statelessness of the Loc for the given request
   */
  case class CalcStateless(f: () => Boolean) extends AnyLocParam

  /**
   * A function that calculates the statelessness of the Loc for the given request
   * with the parameterized type passed into the function
   */
  case class CalcParamStateless[-T](f: Box[T] => Boolean) extends LocParam[T]

Finally, it's bad practice to ignore the state in stateless error boxes.  They mean that there's something wrong with your code.  However, it seems that some folks have snippets that access session state and just kinda ignore that sometimes these snippets execute in stateless mode because in production mode the only bad thing that happens is that a log message is generated.

For those folks, I've added the following traits:

/**
 * Mix this snippet into any snippet.  If the snippet is invoked in response to a
 * stateless request, then the behavior method is called with the method name of
 * the snippet (usually render, but there may be others if you specify a method
 * after the snippet name: MySnippet.dothing).
 */
trait StatelessBehavior {
  /**
   * Given the method name, return the transformation for the method
   */
  def behavior(methodName: String): NodeSeq => NodeSeq
}

/**
 * A simpler way to define behavior if the snippet is invoked.  Just implement the behavior() method
 * and all methods for the snippet will use that behavior.
 */
trait DefaultStatelessBehavior extends StatelessBehavior {
  def behavior(): NodeSeq => NodeSeq
  def behavior(methodName: String): NodeSeq => NodeSeq = behavior()
}

/**
 * A "default" implementation of StatelessBehavior.  Just ignore everything and return a zero-length Text.
 */
trait BlankStatelessBehavior extends StatelessBehavior {
  def behavior(methodName: String): NodeSeq => NodeSeq = ignore => Text("")
}
Mix these traits into your the snippets that have stateful access.  The snippet execution will be short-circuited and the behavior defined in the StatelessBehavior will be used instead of the normal snippet behavior.  Put simply, mix BlankStatelessBehavior into your snippets that display buttons and forms and such and instead of executing those snippets, you'll just get empty Node in your output.

Questions?

Thanks,

David

PS -- Alex -- stateless is binary... a request is either handled in a stateless manner or not.  There's no tri-state or ability to toggle the statelessness based on user actions (although you could do so by putting a marker cookie in responses of a certain type and then look for that cookie in the stateless test.)


--
Lift, the simply functional web framework http://liftweb.net

Naftoli Gugenheim

unread,
Aug 23, 2011, 6:47:48 PM8/23/11
to lif...@googlegroups.com
Why bother with StatelessBehavior? Why not just an API to test if the current request is stateless?

Also perhaps the parameterless def behavior should not have the empty parameter list?




--
You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.

Naftoli Gugenheim

unread,
Aug 23, 2011, 6:50:09 PM8/23/11
to lif...@googlegroups.com
Also perhaps the case class should not have "Test" in its name, since it's not a test; it just represents a request. Why not use Req -- is it only for stateful requests? (If yes then it certainly makes sense to just call the case class StatelessReq.)

Alex Black

unread,
Aug 23, 2011, 8:18:02 PM8/23/11
to Lift
Great, I'm looking forward to trying it out.

Feedback:
- StatelessReqTest looks great, I can easily see how we can use that
to mark say requests from GoogleBot as stateless
- I haven't yet been able to understand the behavior trait, I'm not
following what use case its for, or how to use it, I'll read it over
again.

I'm still interested in not creating sessions until they're needed,
but, thats probably just icing on the cake, being able to not give
sessions to crawlers should be a big help.

- Alex

On Aug 23, 5:05 pm, David Pollak <feeder.of.the.be...@gmail.com>
wrote:
> Simply Lifthttp://simply.liftweb.net

Alex Black

unread,
Aug 23, 2011, 8:26:43 PM8/23/11
to Lift
I glossed over your point about cookies.

So, if one wanted users to be stateless until some action took place
you could:
a. mark all requests as stateless by default
b. if the user takes some action, then give them a cookie
c. mark future requests as stateful if the cookie is present

so I'm just thinking this through.. imagine:
- request comes in, no cookie present, mark it as stateless
- process the request, determine its a request to store some state and
hence initiate a session
- now, we cannot store that state in the session, because we're
already stateless

any thoughts? Perhaps I'm over complicating things, or perhaps there
is a way around this.

David Pollak

unread,
Aug 23, 2011, 10:56:04 PM8/23/11
to lif...@googlegroups.com
On Tue, Aug 23, 2011 at 6:47 PM, Naftoli Gugenheim <nafto...@gmail.com> wrote:
Why bother with StatelessBehavior? Why not just an API to test if the current request is stateless?

There's already an API to test.  The problem is that wrapping an "if (stateless) xxx else normal behavior" around lots of code takes a lot of time and is error prone and messes up the code.
 

Also perhaps the parameterless def behavior should not have the empty parameter list?

Then you get into the "is this a partially applied function or a method call" quandary.
 

David Pollak

unread,
Aug 23, 2011, 11:07:26 PM8/23/11
to lif...@googlegroups.com
On Tue, Aug 23, 2011 at 8:26 PM, Alex Black <al...@alexblack.ca> wrote:
I glossed over your point about cookies.

So, if one wanted users to be stateless until some action took place
you could:
a. mark all requests as stateless by default
b. if the user takes some action, then give them a cookie
c. mark future requests as stateful if the cookie is present

so I'm just thinking this through.. imagine:
- request comes in, no cookie present, mark it as stateless
- process the request, determine its a request to store some state and
hence initiate a session

This is something you can do.  It is not something that Lift can do.
 
- now, we cannot store that state in the session, because we're
already stateless

Basically, you need to create a URL that is the "transition from stateless to stateful" URL and when a request comes into that URL, it creates a new, stateful session.  But the decision about stateful vs. stateless happens so early in the request response cycle (and it cannot happen any later than it does) that it forecloses on doing the kind of design that you want.
 
--
You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.




--
Lift, the simply functional web framework http://liftweb.net

Naftoli Gugenheim

unread,
Aug 23, 2011, 11:35:30 PM8/23/11
to lif...@googlegroups.com
On Tue, Aug 23, 2011 at 10:56 PM, David Pollak <feeder.of...@gmail.com> wrote:


On Tue, Aug 23, 2011 at 6:47 PM, Naftoli Gugenheim <nafto...@gmail.com> wrote:
Why bother with StatelessBehavior? Why not just an API to test if the current request is stateless?

There's already an API to test.  The problem is that wrapping an "if (stateless) xxx else normal behavior" around lots of code takes a lot of time and is error prone and messes up the code.

I hear that.

 
 

Also perhaps the parameterless def behavior should not have the empty parameter list?

Then you get into the "is this a partially applied function or a method call" quandary. 
 
Oh, I see.

What is the advantage of DefaultStatelessBehavior -- that it saves the extra typing of
def behavior(method: String) = ... ?

If so maybe one option to consider is just to make one trait with one method, and define it as
def behavior: String => NodeSeq=>NodeSeq

This has two advantages:
1. If you don't care about the method name you can do
 def behavior = _ => "*" #> ...   // very concise.
2. You can use pattern matching syntax:
def behavior = {
  case "method1" => statelessMethod1
  case _ => ClearNodes
}

Actually the above reminds me of DispatchSnipet#dispatch, so we can throw in a third advantage: making it easier to remember how to use it, since it's similar to other Lift contstruct(s). Or, come to that, another possibility is to give it the same signature as dispatch. But then you lose #1 above (although it increases #3)...

Just some food for thought... :)


David Pollak

unread,
Aug 26, 2011, 5:59:31 PM8/26/11
to lif...@googlegroups.com
Thanks for the suggestion, I've updated:


/**
 * Mix this snippet into any snippet.  If the snippet is invoked in response to a
 * stateless request, then the behavior method is called with the method name of
 * the snippet (usually render, but there may be others if you specify a method
 * after the snippet name: MySnippet.dothing).
 */
trait StatelessBehavior {
  /**
   * Given the method name, return the transformation for the method
   */
  def behaviorDispatch: PartialFunction[String, NodeSeq => NodeSeq]
}

So it's like a DispatchSnippet

--
You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.

Naftoli Gugenheim

unread,
Aug 28, 2011, 5:57:33 PM8/28/11
to lif...@googlegroups.com
Cool!
Sorry for being so perfectionist, but that makes me think it should be called def statelessDispatch...

David Pollak

unread,
Aug 28, 2011, 7:54:13 PM8/28/11
to lif...@googlegroups.com

How about you go and update the names and any other method signatures that you think need improvement?  I am less than optimal at naming and am happy to have improvements.

>>> def behavior*(method: String)* = ... ?

Naftoli Gugenheim

unread,
Aug 28, 2011, 11:26:25 PM8/28/11
to lif...@googlegroups.com
Thoughts?

diff --git a/web/webkit/src/main/scala/net/liftweb/http/StatefulSnippet.scala b/web/webkit/src/main/scala/net/liftweb/http/StatefulSnippet.scala
index 3d533e4..468cba2 100644
--- a/web/webkit/src/main/scala/net/liftweb/http/StatefulSnippet.scala
+++ b/web/webkit/src/main/scala/net/liftweb/http/StatefulSnippet.scala
@@ -173,29 +173,29 @@ trait DispatchSnippet {
 * the snippet (usually render, but there may be others if you specify a method
 * after the snippet name: MySnippet.dothing).
 */
trait [-StatelessBehavior-]{+StatelessDispatch+} {
  /**
   * Given the method name, return the transformation for the method
   */
  def [-behaviorDispatch:-]{+statelessDispatch:+} PartialFunction[String, NodeSeq => NodeSeq]
}

/**
 * A simpler way to define behavior if the snippet is invoked.  Just implement the behavior() method
 * and all methods for the snippet will use that behavior.
 */
trait [-DefaultStatelessBehavior-]{+SimpleStatelessDispatch+} extends [-StatelessBehavior-]{+StatelessDispatch+} {
  def [-behavior():-]{+stateless:+} NodeSeq => NodeSeq
  def [-behaviorDispatch:-]{+statelessDispatch:+} PartialFunction[String, NodeSeq => NodeSeq] = {
    case _ => [-behavior()-]{+stateless+}
  }
}

/**
 * A "default" implementation of [-StatelessBehavior.-]{+StatelessDispatch.+}  Just ignore everything and return a zero-length Text.
 */
trait [-BlankStatelessBehavior-]{+BlankStatelessDispatch+} extends [-StatelessBehavior-]{+StatelessDispatch+} {
  def [-behaviorDispatch:-]{+statelessDispatch:+} PartialFunction[String, NodeSeq => NodeSeq] = {
    case _ => [-ignore-]{+_+} => NodeSeq.Empty
  }

Peter Brant

unread,
Aug 29, 2011, 9:50:53 AM8/29/11
to lif...@googlegroups.com
Hi Naftoli,

This is OT, but how are you generating that diff output? That's
pretty nifty (sort of like a b/w version of --color-words).

Thanks,

Pete

David Pollak

unread,
Aug 29, 2011, 11:48:58 AM8/29/11
to lif...@googlegroups.com


On Sun, Aug 28, 2011 at 8:26 PM, Naftoli Gugenheim <nafto...@gmail.com> wrote:
Thoughts?


I'm not keen on StatelessDispatch... because it's a behavior only in stateless mode.  I think it would be confusing to people who would see it as a stateless counterpart to StatefulSnippets.  Sorry... I guess I'm a better critic than I am a doer.  What do others think?  If I'm the only one seeing a likelihood of confusions, let's go with it.  If there are a couple of other folks who see a likelihood of confusions, let's try a different name (not necessarily StatelessBehavior.)
 

--
You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.

Naftoli Gugenheim

unread,
Aug 29, 2011, 11:16:54 PM8/29/11
to lif...@googlegroups.com
Good to know about --color-words (although of course it wouldn't work to paste into an email).
I used --word-diff. (I don't know why tab completion doesn't know about it.)

Naftoli Gugenheim

unread,
Aug 29, 2011, 11:23:41 PM8/29/11
to lif...@googlegroups.com
No problem --- what do you say to this one? I.e., your trait names and my method names.
Another possibility is "def statelessBehavior."


@@ -177,17 +177,17 @@ trait StatelessBehavior {
  /**
   * Given the method name, return the transformation for the method
   */
  def [-behaviorDispatch:-]{+statelessDispatch:+} PartialFunction[String, NodeSeq => NodeSeq]
}

/**
 * A simpler way to define behavior if the snippet is invoked.  Just implement the behavior() method
 * and all methods for the snippet will use that behavior.
 */
trait [-DefaultStatelessBehavior-]{+SimpleStatelessBehavior+} extends StatelessBehavior {
  def [-behavior():-]{+stateless:+} NodeSeq => NodeSeq
  def [-behaviorDispatch:-]{+statelessDispatch:+} PartialFunction[String, NodeSeq => NodeSeq] = {
    case _ => [-behavior()-]{+stateless+}
  }
}

@@ -195,7 +195,7 @@ trait DefaultStatelessBehavior extends StatelessBehavior {
 * A "default" implementation of StatelessBehavior.  Just ignore everything and return a zero-length Text.
 */
trait BlankStatelessBehavior extends StatelessBehavior {

David Pollak

unread,
Aug 30, 2011, 9:06:43 AM8/30/11
to lif...@googlegroups.com
Excellent.

Please commit this stuff right into master (this discussion is a good substitute for review board)

Peter Brant

unread,
Aug 30, 2011, 10:43:55 AM8/30/11
to lif...@googlegroups.com
Thanks.

Pete

Reply all
Reply to author
Forward
0 new messages