Help understanding RestHelper serve params

1,347 views
Skip to first unread message

Nolan Darilek

unread,
Oct 7, 2011, 8:17:43 AM10/7/11
to Lift
There are some things I keep cutting and pasting without understanding
them, and I'd like to change that. One such is the various case
statements in the PF passed to serve{} in RestHelper. Take this example
from Simply Lift:

case "api" :: "static" :: _ XmlGet _ => <b>Static</b>

My understanding of _ is that it is a wildcard symbol meant to either
ignore an unwanted parameter, or when passing a single-use parameter
into a function. So:

1. Why are there no commas in that argument list? The first part is
obviously a path, but I'd expect there to be a "," after the first "_".
There isn't. I'm missing some language construct here, because my
impulse is to add commas there somewhere...

2. What are those _s doing? My understanding must be incorrect, as I've
never seen a case in which someone names the parameters and uses them in
a REST API. So part of me thinks that they're unnecessary ceremony and
wonders why they aren't removed, but then another thinks that surely I'm
missing some edge case that, if they weren't present, would only mean
that 90-95% of what is possible in a REST API is achievable by RestHelper.

Thanks for helping me understand.

Peter Robinett

unread,
Oct 7, 2011, 4:40:49 PM10/7/11
to lif...@googlegroups.com
Hi Nolan,

You're definitely right that it looks like magic. I myself don't really understand it, but let's see if we can't get to a better understanding by going through the ScalaDocs together.

First, let's look at RestHelper. Hmm, no serve definition there... Let's look at the actual RestHelper.scala file. serve is there but it's protected. Oh, ok! Back to the ScalaDocs and chose 'Visibility All' instead of 'Visibility Public' near the top of the page. Moving on...

So we have the following method signature:

defserve (handler: PartialFunction[Req, () ⇒ Box[LiftResponse]])Unit

Add request handlers

serve takes a PartialFunction[Req, () => Box[LiftRepsonse]]. Ok, so we've obviously got A LOT of implicit conversions going on here. If we look for implicit defs we find a lot. First, and simplest, there is boxFuncToResp and boxToResp, explaining how we'll get that second type in the PartialFunction signature.

But you're trying to understand "api" :: "static" :: _ XmlGet _, which must eventually become a Req. Let's start with the one bit of code that we know Lift gives us, XmlGet.

lazy valXmlGet TestGet with XmlTest

The stable identifier for XmlGet. You can use it as an extractor.


Ok, this is interesting. Somehow it can be used as an extractor. Let's look at TestGet, specifically its unapply method since it's apparently used as an extractor.

defunapply (r: Req)Option[(List[String], Req)]

Test to see if the request is a GET and expecting JSON in the response. The path and the Req instance are extracted.


Great, now this is it! The unapply method takes our mythical Req that the handler PartialFunction is referring to and returns an Option[(List[String], Req)]. Now, I've never written an unapply method that has a Tuple2 in its Option, but apparently if you do then the first element can be referenced before the object and the second after.

So, that means that "api" :: "static" :: _ is our List[String], which represents our path, and _ is again our Req. Why is there an underscore in our List[String]? Because we don't care about the end of our path as long as the beginning is correct. The remainder of the list isn't stored in a value since we don't need to refer to it.

Likewise we don't need to refer to the Req, so there too we don't even bother to store it as as a value. That being said, sometimes you might want to have the Req available in your body when preparing the response: I've used it when supporting conditional requests. This means we might have a case statement like case "api" :: "static" :: extraPathElements XmlGet request { request.ifModifiedSince match { case Full(date) => ... } }.

And that's how "api" :: "static" :: _ XmlGet _ is valid code!

Wow, that was a lot but I think I've explained it correctly and I hope you were able to follow it!

Peter

Diego Medina

unread,
Oct 7, 2011, 5:51:45 PM10/7/11
to lif...@googlegroups.com

Wow Peter, this was a great explanation!

/me was silently waiting for someone to reply to this question.

Diego
Sent from my android cell

On Oct 7, 2011 4:41 PM, "Peter Robinett" <pe...@bubblefoundry.com> wrote:
> Hi Nolan,
>
> You're definitely right that it looks like magic. I myself don't really
> understand it, but let's see if we can't get to a better understanding by
> going through the ScalaDocs together.
>
> Hmm, no serve definition there... Let's look at the actual RestHelper.scalafile.
> serve is there but it's protected. Oh, ok! Back to the ScalaDocs and chose
> 'Visibility All' instead of 'Visibility Public' near the top of the
> page. Moving on...
>
> So we have the following method signature:
>

> ]]): Unit
>
> Add request handlers
> serve takes a PartialFunction[Req, () => Box[LiftRepsonse]]. Ok, so we've
> obviously got A LOT of implicit conversions going on here. If we look for
> implicit defs we find a lot. First, and simplest, there is boxFuncToResp and
> boxToResp, explaining how we'll get that second type in the PartialFunctionsignature.
>
> But you're trying to understand "api" :: "static" :: _ XmlGet _, which must
> eventually become a Req. Let's start with the one bit of code that we know
> Lift gives us, XmlGet.
>

>
> The stable identifier for XmlGet. You can use it as an extractor.
>
> Ok, this is interesting. Somehow it can be used as an extractor. Let's look
> at TestGet, specifically its unapply method since it's apparently used as an
> extractor.
>

> )]
>
> Test to see if the request is a GET and expecting JSON in the response. The
> path and the Req instance are extracted.
>
> Great, now this is it! The unapply method takes our mythical Req that the handler
> PartialFunction is referring to and returns an Option[(List[String], Req)].
> Now, I've never written an unapply method that has a Tuple2 in its Option,
> but apparently if you do then the first element can be referenced before the
> object and the second after.
>
> So, that means that "api" :: "static" :: _ is our List[String], which
> represents our path, and _ is again our Req. Why is there an underscore in
> our List[String]? Because we don't care about the end of our path as long as
> the beginning is correct. The remainder of the list isn't stored in a value
> since we don't need to refer to it.
>
> Likewise we don't need to refer to the Req, so there too we don't even
> bother to store it as as a value. That being said, sometimes you might want
> to have the Req available in your body when preparing the response: I've
> used it when supporting conditional requests. This means we might have a
> case statement like case "api" :: "static" :: extraPathElements XmlGet
> request { request.ifModifiedSince match { case Full(date) => ... } }.
>
> And that's how "api" :: "static" :: _ XmlGet _ is valid code!
>
> Wow, that was a lot but I think I've explained it correctly and I hope you
> were able to follow it!
>
> Peter
>
> --
> Lift, the simply functional web framework: http://liftweb.net
> Code: http://github.com/lift
> Discussion: http://groups.google.com/group/liftweb
> Stuck? Help us help you: https://www.assembla.com/wiki/show/liftweb/Posting_example_code

Peter Robinett

unread,
Oct 8, 2011, 12:16:54 PM10/8/11
to lif...@googlegroups.com
Thanks!

Peter

David Pollak

unread,
Oct 8, 2011, 9:03:39 PM10/8/11
to lif...@googlegroups.com
On Fri, Oct 7, 2011 at 2:51 PM, Diego Medina <di...@fmpwizard.com> wrote:

Wow Peter, this was a great explanation!


Absolutely stellar explanation!  I'm going to ping this to the top of the Lift list!

Nolan Darilek

unread,
Oct 10, 2011, 7:52:23 PM10/10/11
to lif...@googlegroups.com
Apologies for the delay in responding. I was sufficiently off-grid for
most of the weekend and couldn't really give reading this the cycles it
deserved.

But thanks so much for this. Just one final question:

serve {
case "simple3" :: "item" :: itemId :: Nil JsonGet _ =>

What Scala magic is it that doesn't require a "," between Nil and
JsonGet? I always want to enter it because I'd assume those are separate
arguments in some unapply() extractor or other.

It's possible that you answered that and I'm just not connecting the
dots. If so then I apologize.

Thanks again.

Viktor Hedefalk

unread,
Oct 11, 2011, 4:44:24 AM10/11/11
to lif...@googlegroups.com

> What Scala magic is it that doesn't require a "," between Nil and
> JsonGet? I always want to enter it because I'd assume those are
> separate arguments in some unapply() extractor or other.
>
Actually Peter mentioned that too:

"Great, now this is it! The unapply method takes our mythical Req that
the handler PartialFunction is referring to and returns an
Option[(List[String], Req)]. Now, I've never written an unapply method
that has a Tuple2 in its Option, but apparently if you do then the first
element can be referenced before the object and the second after."

I looked it up because I had for some reason forgotten about it even
though I have used the exact same thing with combinator parsers. There
it's used with a case class ~ for sequential composition which allows
you to match something like a ~ b ~ c when constructing your AST.

In the lang spec it's described under:

8.1.10 InfixOperationPatterns

http://www.scala-lang.org/sites/default/files/linuxsoft_archives/docu/files/ScalaReference.pdf

It's basically a special case for extractors to allow for infix
notation. It applies to constructor and extractor patterns. And it's not
even only for binary patterns, x e (y, z) => e(x, y, z) too. That I
think would be confusing though� :)

Thanks,
Viktor

Peter Robinett

unread,
Oct 11, 2011, 5:00:34 AM10/11/11
to lif...@googlegroups.com
Thanks, Viktor!

Peter

Andreas Joseph Krogh

unread,
Oct 11, 2011, 6:36:32 AM10/11/11
to lif...@googlegroups.com

Hi.

I have several long-running reports which need more than 5s. to complete which I run using AJAX. I have set this in Boot.scala

        LiftRules.ajaxPostTimeout = 15000

and have verified that it's used:

    lift_actualAjaxCall: function(data, onSuccess, onFailure) {
      jQuery.ajax({ url : liftAjax.addPageName("/front/ajax_request/"), 
data : data, type : "POST", dataType : "script", timeout : 15000,
 cache : false, success : onSuccess, error : onFailure });
        },

        lift_actualJSONCall: function(data, onSuccess, onFailure) {
          jQuery.ajax({ url : liftAjax.addPageName("/front/ajax_request/"),
 data : data, type : "POST", dataType : "json", timeout : 15000,
 cache : false, success : onSuccess, error : onFailure });
              }
            };

 

Despite having raised it to 15s I'm still seeing my ajax-calls being aborted after 5s. and my LiftRules.ajaxDefaultFailure is not being called.

Any suggestions what this might be?

I'm using 2.4-SNAPSHOT with jQuery-1.6.4

--
Andreas Joseph Krogh <and...@officenet.no> - mob: +47 909 56 963
Senior Software Developer / CTO - OfficeNet AS - http://www.officenet.no
Public key: http://home.officenet.no/~andreak/public_key.asc

Andreas Joseph Krogh

unread,
Oct 11, 2011, 8:50:01 AM10/11/11
to lif...@googlegroups.com

På tirsdag 11. oktober 2011 kl 12:36:32 skrev Andreas Joseph Krogh <and...@officenet.no>:

Hi.

I have several long-running reports which need more than 5s. to complete which I run using AJAX. I have set this in Boot.scala

        LiftRules.ajaxPostTimeout = 15000

and have verified that it's used:

    lift_actualAjaxCall: function(data, onSuccess, onFailure) {
      jQuery.ajax({ url : liftAjax.addPageName("/front/ajax_request/"), 
data : data, type : "POST", dataType : "script", timeout : 15000,
 cache : false, success : onSuccess, error : onFailure });
        },

        lift_actualJSONCall: function(data, onSuccess, onFailure) {
          jQuery.ajax({ url : liftAjax.addPageName("/front/ajax_request/"),
 data : data, type : "POST", dataType : "json", timeout : 15000,
 cache : false, success : onSuccess, error : onFailure });
              }
            };

 

Despite having raised it to 15s I'm still seeing my ajax-calls being aborted after 5s. and my LiftRules.ajaxDefaultFailure is not being called.

Any suggestions what this might be?

I'm using 2.4-SNAPSHOT with jQuery-1.6.4

 

I failed to mention (realized it my self after debugging) that this AJAX-request actually maps to a function in a comet-actor (render()) and it's that function which takes more than 5s to execute.

This code in LiftSession:655-669 is responsible for the 5s. timeout

    val ret = toRun.map(_.owner).distinct.flatMap {
      w =>
        val f = toRun.filter(_.owner == w)
        w match {
          // if it's going to a CometActor, batch up the commands
          case Full(id) if asyncById.contains(id) =>
            asyncById.get(id).toList.
              flatMap(a => a.!?(5000L, ActionMessageSet(f.map(i => buildFunc(i)), state)) match {
              case Full(li: List[_]) => li
              case li: List[_] => li
              case other => Nil
            })
          case _ => f.map(i => buildFunc(i).apply())
        }
    }

I realize now that I should send a message in my render()-method asking some other actor to do the actual computation, instead of making the computation inline. Then the other actor should send my comet-actor a message back with the response.

Anyway: Any chanse this timeout could also be configurable? It would certainly make my life easier if it was configurable with a big fat text describing why you really shouln't mess with that timeout.

Shall I open a ticket and assign it to my self?

Peter Robinett

unread,
Oct 11, 2011, 2:26:12 PM10/11/11
to lif...@googlegroups.com
Hi Andreas,

Somehow your messages about timeouts got attached to the pinned thread about RestHelper. Forum administrators, is there any way we could move them into another thread for the sake of clarity?

Peter

Andreas Joseph Krogh

unread,
Oct 11, 2011, 5:19:12 PM10/11/11
to lif...@googlegroups.com

Oh - I certainly didn't do that on purpose:-)

For some reason my first message has this header:

In-Reply-To: <12852792.1303.1318323634970.JavaMail.geo-discussion-forums@yqjh13>

Which explains why, but not how...

I might have accidentally hijacked another reply-message I was going
write in an already open mail-window.

Nolan Darilek

unread,
Oct 11, 2011, 10:19:55 PM10/11/11
to lif...@googlegroups.com
On 10/11/2011 03:44 AM, Viktor Hedefalk wrote:
>>
> Actually Peter mentioned that too:
>

Ah, so he did, figured it was in there somewhere. Thanks to everyone for
such a detailed explanation!

Viktor Hedefalk

unread,
Oct 12, 2011, 12:14:00 PM10/12/11
to lif...@googlegroups.com
Pjuh! I blamed Thunderbird since the threading looked fine in gmail
web client. Poor Thunderbird, I will immediately fire you up once
again :)

Cheers,
Viktor

Reply all
Reply to author
Forward
0 new messages