Skip to content

Instantly share code, notes, and snippets.

@alexflav23
Last active December 30, 2015 07:19
Show Gist options
  • Save alexflav23/7794808 to your computer and use it in GitHub Desktop.
Save alexflav23/7794808 to your computer and use it in GitHub Desktop.
Example of asynchronous REST route.
import net.liftweb.http.rest.{ RestContinuation, RestHelper }
import net.liftweb.http.{ NotAcceptableResponse, OkResponse, S, UnauthorizedResponse }
import net.liftweb.json.{ DefaultFormats, JsonAST, JsonParser }
import net.liftweb.json.JsonAst.JValue;
/**
* This is a trivial implementation of a Scala case class.
* In our code, they are companions to more complex database models.
*/
case class UserLoginRequest(val email: String, val password: String) {};
object MyRest extends RestHelper {
/**
* This creates the http://myapp.com/api/user/example route.
* Lets create a parser for incoming GET requests on this route.
* Inside the case, you are expected to return a LiftResponse, or one of its variations.
* Could mean OkResponse, NotAccetableResponse, UnauthorizedResponse, etc.
* See the net.liftweb.http package for possible options, they map all possible HTTP response statuses.
*/
serve {
case "api" :: "user" :: "example" :: Nil Get _ => {
// this is executed in the S thread.
// S values are not accessible inside a Continuation.
// S gives you access to all request parameters.
// you have to use closures to store their values, like shown below
val someParam = S.param("someQueryParameter")
RestContinuation.async {
reply => {
// Most actions in the API are asynchronous, this is the pattern to use.
// If you need to pass request parameters or headers etc, you need to get them in the parent thread.
// The continuation will spawn a child thread(worker) and wait for that action to complete before responding.
// This is the async pattern.
val x: Future[String] = someExpensiveComputation(someParam); // someParam can be used here.
// but calling S.param("someQueryParameter") would give you back Empty, because the request
// is no longer available in the scope of the child worker.
// If the asynchronous computation was successful, reply with HTTP 200.
x onSuccess {
case response: String => reply(OkResponse())
case _ => NotAcceptableResponse()
}
// If it wasn't, reply with HTTP 406 or whatever is suitable.
x onFailure {
case failure: Throwable => reply(NotAcceptableResponse(failure.getMessage))
}
}
}
}
}
/**
* Now lets do a synchronous POST request.
* On the same route, /api/user/example.
*/
serve {
case "api" :: "user" :: "example" :: Nil JsonPost json -> _ => {
// at this point you have two guarantees.
// First, the current request has a "Content-Type" -> "application/json"
// and the body of the post contains valid json.
// now we can optionally extract the JSON.
// We know it's JSON, but we can't know what it actually contains.
// Unlike PHP, Scala's static type system needs very rigorous parsing.
{
for {
user <- json.extract[UserLoginRequest] // this feeds back an option, means we can use for comprehensions.
} yield {
if (Users.getUserByEmailAndPassword(user.email, user.password).isDefined) {
OkResponse()
} else {
UnauthorizedResponse()
}
}
} getOrElse {
UnauthorizedResponse();
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment