[Lift] A new way to do REST with Lift

49 views
Skip to first unread message

David Pollak

unread,
Apr 27, 2010, 6:40:42 PM4/27/10
to liftweb
Folks,

The REST helpers is in Lift.  I expect that it will evolve as people use it and give feedback.

You can read about it https://liftweb.assembla.com/spaces/liftweb/wiki?id=liftweb&wiki_id=REST_Web_Services

Thanks,

David

--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Surf the harmonics

--
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.

aw

unread,
Apr 28, 2010, 3:40:05 PM4/28/10
to Lift
Looks nice!

Can you provide any guidance for enforcing security?

And are you still using Box[LiftResponse]? If so, I was unclear what
would happen if an Empty box or Failure box was returned.
(Considering all the available subclasses to LiftResponse, I was
puzzled as to why the Box wrapper exists for this -- but perhaps I am
missing something.)

David Pollak

unread,
Apr 28, 2010, 5:44:28 PM4/28/10
to lif...@googlegroups.com
On Wed, Apr 28, 2010 at 12:40 PM, aw <ant...@whitford.com> wrote:
Looks nice!

Can you provide any guidance for enforcing security?

What do you mean by security?  Do you mean authentication? Do you mean URL level access?  Do you mean access to a given resource (e.g., is a task accessible to a user?)
 

And are you still using Box[LiftResponse]?

The serve method still takes a PartialFunction[Req, () => Box[LiftResponse]] but there are a bunch of implicit conversions that will convert a Box[NodeSeq], a Box[JValue], a NodeSeq, a JValue, etc. into a () => Box[LiftResponse]

 
 If so, I was unclear what
would happen if an Empty box or Failure box was returned.

If you return Empty, then the request 404s.  If you return a Failure(msg, _, _), then the request 404s, but msg is in the response body.  If you return a ParamFailure(msg, _, _, responseCode: Int) then the responseCode is returned as the HTTP response on msg is in the response body.
 
(Considering all the available subclasses to LiftResponse, I was
puzzled as to why the Box wrapper exists for this -- but perhaps I am
missing something.)

Take a look at the last example on the page.  Being able to do:

for {
  p1 <- r.param("p1") ?~ "P1 is missing"
  p2 <- r.param("p2") ?~ "P2 is missing"
  } yield <xml><p1>{p1}</p1><p2>{p2}</p2></xml>

Without worrying about explicitly creating a LiftResponse is part of removing the cruft and allowing the developer to focus on the important stuff... the business logic.
 

--
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
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Surf the harmonics

aw

unread,
May 5, 2010, 3:01:10 AM5/5/10
to Lift


On Apr 28, 2:44 pm, David Pollak <feeder.of.the.be...@gmail.com>
wrote:
> On Wed, Apr 28, 2010 at 12:40 PM, aw <anth...@whitford.com> wrote:
> > Looks nice!
>
> > Can you provide any guidance for enforcing security?
>
> What do you mean by security?  Do you mean authentication? Do you mean URL
> level access?  Do you mean access to a given resource (e.g., is a task
> accessible to a user?)

Specifically, I am referring to enforcing URL level access
restrictions. So, for example, if I have a REST URL like "/api/foo"
and let's say that I need to have the "FooAdmin" role to execute/
access the URL, what is the "best practice"? I am wondering if it
would make sense to build this kind of declarative security into your
DSL. I have some existing code that simply does something like the
following to enforce security:

case r @ Req(...) if hasRole("FooAdmin") => foo(r)

But me thinks that this isn't the smartest strategy...


Another nuisance issue that I run into is type validation. So let's
say that my REST pattern is something like /api/foo/id where id needs
to be a positive numeric. I ended up writing some routines like
"isPositiveNumeric(x)" and use the case filtering again... Would be
nice to see some basic validation routines built into Lift (or
Scala). Since toInt/toLong exists, but isInt/isLong does not, this
leads me to believe that I should just call toInt/toLong and then use
exception handling in the event of an error... The semantics for
exception handling need to be clearly documented -- what happens if my
dispatch throws an exception?

I plan to take a closer look at my REST api and try to integrate this
new stuff when 2.0-M5 is released and see what issues fall out.

David Pollak

unread,
May 5, 2010, 11:55:18 AM5/5/10
to lif...@googlegroups.com
On Wed, May 5, 2010 at 12:01 AM, aw <ant...@whitford.com> wrote:


On Apr 28, 2:44 pm, David Pollak <feeder.of.the.be...@gmail.com>
wrote:
> On Wed, Apr 28, 2010 at 12:40 PM, aw <anth...@whitford.com> wrote:
> > Looks nice!
>
> > Can you provide any guidance for enforcing security?
>
> What do you mean by security?  Do you mean authentication? Do you mean URL
> level access?  Do you mean access to a given resource (e.g., is a task
> accessible to a user?)

Specifically, I am referring to enforcing URL level access
restrictions.  So, for example, if I have a REST URL like "/api/foo"
and let's say that I need to have the "FooAdmin" role to execute/
access the URL, what is the "best practice"?  I am wondering if it
would make sense to build this kind of declarative security into your
DSL.  I have some existing code that simply does something like the
following to enforce security:

   case r @ Req(...) if hasRole("FooAdmin") => foo(r)

But me thinks that this isn't the smartest strategy...

First, if you're going to have a role defined by a String, then you've got a bad strategy.

But, yes, using a guard in the pattern is a good way to do URL-level access control.
 


Another nuisance issue that I run into is type validation.  So let's
say that my REST pattern is something like /api/foo/id where id needs
to be a positive numeric.  I ended up writing some routines like
"isPositiveNumeric(x)" and use the case filtering again...  Would be
nice to see some basic validation routines built into Lift (or
Scala).  Since toInt/toLong exists, but isInt/isLong does not

But Helpers.asInt(x: Any): Box[Int] and Helpers.asLong(x: Any): Box[Long] exist

More broadly Boolean testing is discourages where monad-based transformations (using a for comprehension) is encouraged.
 
, this
leads me to believe that I should just call toInt/toLong and then use
exception handling in the event of an error...  The semantics for
exception handling need to be clearly documented -- what happens if my
dispatch throws an exception?

Helpers.toInt/Helpers.toLong do not throw exceptions.  They return 0 if they cannot otherwise parse the input.

Please also take a look at Helpers.AsInt and the REST wiki page at https://liftweb.assembla.com/wiki/show/liftweb/REST_Web_Services

Specifically:

serveJx {
case Get("api" :: "info" :: Info(info) :: _, _) => Full(info)
}
See the Info(info) thing.  That's an extractor.  Basically, the third element of the list is passed to the unapply method in the Info object:

object Info {
  def unapply(str: String): Option[Info] = ...
}

So, the per-element access control can be applied based on the logic in Info.unapply.

If you just want an Int or Long, you can use the Helpers.AsInt/Helpers.AsLong objects to test the element as an Int or a Long.  Further, each MetaMapper comes with an unapply method, so you can do:

serveJx {
case Get("api" :: "info" :: User(user) :: _, _) => Full(info)
}
And if the user can be found based on the value of the third element in the list, the pattern will succeed and the user variable will contain the record pulled back from the database.


 

I plan to take a closer look at my REST api and try to integrate this
new stuff when 2.0-M5 is released and see what issues fall out.

--
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
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Surf the harmonics

DChenBecker

unread,
May 6, 2010, 9:24:28 AM5/6/10
to Lift
Just as an example of custom extractors:

scala> object PositiveInt {
| def unapply (input : String) = try { if (input.toInt > 0)
Some(input.toInt) else None } catch { case _ => None }
| }
defined module PositiveInt

scala> List("foo","bar","14") match {
| case group :: user :: PositiveInt(id) :: Nil => println("%s:%s
(%d)".format(group, user, id))
| case _ => // NOP
| }
foo:bar (14)

Derek

On May 5, 9:55 am, David Pollak <feeder.of.the.be...@gmail.com> wrote:
> Please also take a look at Helpers.AsInt and the REST wiki page athttps://liftweb.assembla.com/wiki/show/liftweb/REST_Web_Services
>
> Specifically:
>
> serveJx {
> case Get("api" :: "info" :: Info(info) :: _, _) => Full(info)
> }
>
> See the Info(info) thing. That's an extractor. Basically, the third
> element of the list is passed to the unapply method in the Info object:
>
> object Info {
> def unapply(str: String): Option[Info] = ...
>
> }
>
> So, the per-element access control can be applied based on the logic in
> Info.unapply.
>
> If you just want an Int or Long, you can use the
> Helpers.AsInt/Helpers.AsLong objects to test the element as an Int or a
> Long. Further, each MetaMapper comes with an unapply method, so you can do:
>
> serveJx {
> case Get("api" :: "info" :: User(user) :: _, _) => Full(info)
> }
>
> And if the user can be found based on the value of the third element in the
> list, the pattern will succeed and the user variable will contain the record
> pulled back from the database.
>
>
>
> > I plan to take a closer look at my REST api and try to integrate this
> > new stuff when 2.0-M5 is released and see what issues fall out.
>
> > --
> > 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<liftweb%2Bunsu...@googlegroups.com>
> > .
> > For more options, visit this group at
> >http://groups.google.com/group/liftweb?hl=en.
>
> --
> Lift, the simply functional web frameworkhttp://liftweb.net
> Beginning Scalahttp://www.apress.com/book/view/1430219890
> Follow me:http://twitter.com/dpp
> Surf the harmonics
>
> --
> 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 athttp://groups.google.com/group/liftweb?hl=en.

Ralf Muri

unread,
May 19, 2010, 4:56:13 AM5/19/10
to Lift
I have an url structure like this:
/api/blog/<entryId>/comment/<commentId>

Is it possible to pass the result of de entryId extractor to the
commentId extractor so that I can check if entry and comment really
belong to each other?
for example:

the match:
case "api" :: "blog" :: AsBlogEntry(entry) :: "comment" ::
AsComment(comment, entry) :: Nil XmlGet _ => comment.toXml

the blogentry extractor:
object AsBlogEntry {
def unapply(in: String): Option[BlogEntry] = {
Model.find(classOf[BlogEntry], toLong(in))
}
}

and then the comment extractor would look like this:
object AsComment {
def unapply(in: String, entry: BlogEntry): Option[Comment] = {
Model.createNamedQuery[Comment]("findCommentByEntry", "id" ->
toLong(in), "entry" -> entry).findOne
}
}

Is that somehow possible?

David Pollak

unread,
May 19, 2010, 1:50:28 PM5/19/10
to lif...@googlegroups.com
On Wed, May 19, 2010 at 1:56 AM, Ralf Muri <ralf...@gmail.com> wrote:
I have an url structure like this:
/api/blog/<entryId>/comment/<commentId>

Is it possible to pass the result of de entryId extractor to the
commentId extractor so that I can check if entry and comment really
belong to each other?

Nope.  There's some preliminary discussion of adding this kind of feature to Scala's exctractors, but it doesn't exist today.

Your best bet is to use a guard:

case "api" :: "blog" :: AsBlogEntry(entry) :: "comment" :: AsComment(comment) :: Nil XmlGet _ if comment.belongsTo(entry) => comment.toXml



--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890

Ralf Muri

unread,
May 19, 2010, 7:00:35 PM5/19/10
to Lift
thanks, works just fine with the guard!

On May 19, 7:50 pm, David Pollak <feeder.of.the.be...@gmail.com>
wrote:
> > <liftweb%2Bunsu...@googlegroups.com<liftweb%252Bunsubscribe@googlegroup s.com>

David Pollak

unread,
May 19, 2010, 7:38:21 PM5/19/10
to lif...@googlegroups.com
On Wed, May 19, 2010 at 4:00 PM, Ralf Muri <ralf...@gmail.com> wrote:
thanks, works just fine with the guard!

Cool.  Please write a blog post or do a wiki page on what you've learned to make it easier for the next person.
 



--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Reply all
Reply to author
Forward
0 new messages