Last active
December 30, 2015 07:19
-
-
Save alexflav23/7794808 to your computer and use it in GitHub Desktop.
Example of asynchronous REST route.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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