# Building Web Services in lift

##

### Steve Jenson

#### stevej@pobox.com

#### http://saladwithsteve.com

#### http://twitter.com/stevej

# What makes it so easy?

- Pattern Matching
- Higher-Order Functions
- Scala's XML support
- If the first two have not already been explained then this won't go well.

[any material that should appear in print but not on the slide]

# Who am I?

- Spent too many years working on Web Services.
- Blogger.com, a pioneer of web services in the blogging world.
- Used XML-RPC extensively.
- Wrote a simple Document Literal SOAP stack at Google.
- Was in the Atom IETF Working Group.
- Built Google's first REST API.
- lift is the easiest framework I've used for building Web Services.

# Let's start

- Start with a Simple Class
`

    package com.hellolift.api

    import net.liftweb.http._
    import com.hellolift.model._
    import scala.xml._

    class BlogAPI(val request: RequestState)
     extends SimpleController {

      def index: ResponseIt = {
        // To be filled in.
      }

      def get(id: String): ResponseIt = {
        // To be filled in.
      }

      def create: ResponseIt = {
        // To be filled in.
      }

      def delete(id: String): ResponseIt = {
        // To be filled in.
      }
    }

`

# GET an existing Item

- `GET /api/$itemid`
- Route a request to the proper method via pattern matching

`

    def index: ResponseIt {
      request match {
        case RequestState(ParsePath("api" :: itemid :: Nil,
          _, _), GetRequest, _, _) => get(itemid)
      }
    }

    def get(id: String): ResponseIt = {
      val it = Item.find(By(Item.id, Helpers.toLong(id)),
                         By(Item.author, user)
      it match {
        case Full(item) => AtomResponse(item.toAtom())
        case Empty => NotFoundResponse()
      }
    }

`

# Outputting XML

- `scala.xml` makes it really easy to output valid XML.
- The following is in our Item model object.

`

    def toAtom = {
      val id = "http://example.com/api/" + this.id
      val formatter = new
        SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
      val updated = formatter.format(this.lastedited.is)

      <entry xmlns="http://www.w3.org/2005/Atom">
        <id>{id}</id>
        <updated>{updated}</updated>
        <author>
          <name>{name}</name>
        </author>
        <content type="xhtml">
          <div xmlns="http://www.w3.org/1999/xhtml">
            {body}
          </div>
        </content>
      </entry>
    }

`

- You can just use xml.
- Much easier on the eyes
- Just as correct as DOM.

# Create a New Item (Part 1)

- Longer code snippet so this is only the pattern matching
- `POST /api/create`
- Match against `PostRequest`

`

    // the previous pattern matching clause is assumed
    // (for space reasons)
    def index: ResponseIt = {
      request match {
        case RequestState(ParsePath("api" :: "create"
          :: Nil, _, _), PostRequest, _, _) => create
      }
    }

`

# Create a new Item (Part 2)

- `POST /api/create`

`

    def create: ResponseIt = {
      try {
        val xml = XML.load(httpRequest.getInputStream())
        val body = (xml \\ "content").text
        val item = Item.create.author(user).body(body)
        item.save
        AtomCreatedResponse(item.toAtom)
      } catch {
        case e: Exception => BadResponse()
      }
    }

`

- `\\` searches the children for the element.
- You've probably noticed that we are lacking authentication.
- Are you wondering where `user` coming from?
- All will be revealed!

# Delete an Item

- `DELETE /api/$itemid`

`

    def index: ResponseIt = {
      request match {
        case RequestState(ParsePath("api" :: itemid ::
          Nil, _, _), DeleteRequest, _, _)
            => delete(itemid)
      }
    }
    def delete(itemid: String): ResponseIt = {
      Item.find(By(Item.id, Helpers.toLong(itemid)),
                By(Item.author, user)) match {
        case Full(item) => {
          item.delete_!
          OkResponse()
        }
        case _ => NotFoundResponse()
      }
    }

`

- I bet you're getting the hang of this now.

# Routing Requests to BlogAPI

- How do requests get dispatched to `BlogAPI`?
- Dispatching happens in `Boot.scala`

`

    class Boot {
      def boot {
        val apiDispatcher: LiftRules.DispatchPf = {
          case RequestMatcher(r, ParsePath("api" :: _ ::
            Nil, _, _), _, _) => api(r, "index")
        }
        LiftRules.statelessDispatchTable = apiDispatcher
          orElse LiftRules.statelessDispatchTable
       }
      // this doesn't format well for presentations.
      private def api
       (request: RequestState, methodName: String)
       (req: RequestState): Can[ResponseIt] =
        createInvoker(methodName,
         new BlogAPI(request)).flatMap(_() match {
          case Full(ret: ResponseIt) => Full(ret)
          case _ => Empty
        })
    }

`

- The `api` method is really just boilerplate and we should make it go away in lift so you don't have to think about it.
- `statlessDispatchTable` keeps your api requests from incurring session costs.

# Authenticating (Part 1)

- The Question of the Mystery `user` is Answered
- We want each Request to be Authenticated
- Higher-Order Functions to the Rescue!

`

    // getUser is on the next slide
    def authenticated(f: User => ResponseIt) = {
      getUser match {
        case Full(user) => f(user)
        case Empty => UnauthorizedResponse("Our Realm")
      }
    }
    // new and improved
    def get(id: String): ResponseIt {
      authenticated { user => {
        val it = Item.find(By(Item.id, Helpers.toLong(id)),
                           By(Item.author, user)
        it match {
          case Full(item) => AtomResponse(item.toAtom())
          case Empty => NotFoundResponse()
        }
      }
    }

`

- authenticated takes a function that takes a User and returns a `ResponseIt`.

# Authenticating (Part 2): getUser

- We read in the HTTP `Authorization` header and `find` the proper User based on it's contents

`

    private def getUser: Can[User] = {
      Can.legacyNullTest(request.request
        .getHeader("Authorization")).flatMap(auth => {
        val up: List[String] = if (auth.length > 5) {
        new String(Base64.decodeBase64(auth.substring(5,
          auth.length).getBytes())).split(":").toList
        } else {
          Nil
        }

        if (up.length < 2) {
          Empty
        } else {
          val email = up(0)
          val password = up(1)
          User.find(By(User.email, email)) match {
            case Full(user) if user.validated &&
              user.password.match_?(password) => Full(user)
            case _ => Empty
          }
        }
    })}

`

- Again, this is mostly boilerplate, will soon be in lift.
- `Base64` is from Apache Commons Codec.

# Thank you

- Any questions?