¡La versión 2.0 de Play ya está lista! Ayúdanos a traducir la documentación de la útlima versión y sigue nuestro progreso.
Manuales, tutoriales & referencias
El paquete play.libs contiene varias bibliotecas súmamente útiles que te ayudarán a llevar adelante las tareas de programación más comunes.
La mayoría de estas bibliotecas son simples helpers que se usan de forma muy simple:
Las secciones que siguen aportan más información acerca de las bibliotecas más importantes.
XPath es probablemente el método más sencillo para procesar un documento XML sin tener que usar herramientas de generación de código. La biblioteca play.libs.XPath ofrece todas las primitivas necesarias para efectuar estas tareas.
Las operaciones XPath operan sobre todos los tipos de org.w3.dom.Node:
org.w3.dom.Document xmlDoc = … // recuperar un documento de algún sitio
for(Node event: XPath.selectNodes("events//event", xmlDoc)) {
String name = XPath.selectText("name", event);
String data = XPath.selectText("@date", event);
for(Node place: XPath.selectNodes("//place", event)) {
String place = XPath.selectText("@city", place);
…
}
…
}
play.libs.WS aporta un poderoso cliente HTTP. Internamente utiliza Async HTTP client.
Efectuar un pedido HTTP es muy sencillo:
HttpResponse res = WS.url("http://www.google.com").get();
Una vez que tienes el objeto HttpResponse puedes acceder a todas las propiedades del response:
int status = res.getStatus();
String type = res.getContentType();
También puedes recuperar el contenido del cuerpo de varios tipos de contenidos:
String content = res.getString();
Document xml = res.getXml();
JsonElement json = res.getJson();
InputStream is = res.getStream();
También puedes usar el API async para hacer solicitudes HTTP de forma no bloqueante. Entonces, recibirás un Promise<HttpResponse>. Cuando se ejecute, podrás usar el HttpResponse como de costumbre:
Promise<HttpResponse> futureResponse = WS.url(
"http://www.google.com"
).getAsync();
La biblioteca play.libs.F aporta varias estructuras súmamente útiles que vienen de la programación funcional. Estas estructuras se usan para manejar casos de abstracción compleja. Para los que están acostumbrados a la programación funcional, tenemos:
Option<T> (un valor T que puede inicializarse o no)Either<A,B> (contiene un valor A o bien un valor B)Tuple<A,B> (contiene tanto el valor A como el B)Option<T>, Some<T> y None<T>Cuando escribes una función que puede no devolver un resultado en algunos casos (por ejemplo, un método find), un patrón común (malo) en Java es devolver null cuando no hay resultado. Esta práctica es peligrosa porque el tipo de retorno de la función no muestra de manera evidente que puede no devolver un objeto, e incluso ha sido reconocido por el inventor de estas referencias anulables (nullable references) como un “error de mil millones de dólares“.
Option<T> es una solución elegante a este problema: en vez de devolver un objeto de tipo T, la función devuelve un Option<T>. Si la función tiene éxito, devuelve un objeto de tipo Some<T> (envolviendo al resultado real). Si no, se devuelve un objeto del tipo None<T>, siendo ambos subtipos de Option<T>.
Veamos un ejemplo:
/* Division segura (nunca arrojara un error ArithmeticException) */
public Option<Double> div(double a, double b) {
if (b == 0)
return None();
else
return Some(a / b);
}
Y esta es una forma de usar esta función:
Option<Double> q = div(42, 5);
if (q.isDefined()) {
Logger.info("q = %s", q.get()); // "q = 8.4"
}
Pero hay una sintaxis mejor para usarla, basada en el hecho de que @OptionIterable<T>:
for (double q : div(42, 5)) {
Logger.info("q = %s", q); // "q = 8.4"
}
El cuerpo del bucle se ejecuta una vez, sólo si la función div tiene éxito.
Tuple<A, B>La clase Tuple<A, B> envuelve a dos objetos del tipo A y B. Pueden recuperarse estos objetos usando los campos _1 y _2, respectivamente. Por ejemplo:
public Option<Tuple<String, String>> parseEmail(String email) {
final Matcher matcher = Pattern.compile("(\\w+)@(\\w+)").matcher(email);
if (matcher.matches()) {
return Some(Tuple(matcher.group(1), matcher.group(2)));
}
return None();
}
Luego:
for (Tuple<String, String> email : parseEmail("foo@bar.com")) {
Logger.info("name = %s", email._1); // "name = foo"
Logger.info("server = %s", email._2); // "server = bar.com"
}
La clase T2<A, B> es un alias para Tuple<A, B>. Para manejar tuplas de 3 elementos usa T3<A, B, C> y así hasta T5<A, B, C, D, E>.
A veces sentimos que necesitamos contar con coincidencia de patrones (pattern matching) en Java. Desafortunadamente, Java no trae incluida esta funcionalidad, y debido a la falta de construcciones funcionales, es difícil añadirlo como una biblioteca. De cualquier forma, nosotros hemos desarrollado una solución que no es tan mala.
Nuestra idea fue utilizar la más reciente sintaxis de bucles for para lograr implementar una coincidencia de patrones básica para Java. La coincidencia de patrones ha de comprobar si el objeto cumple las condiciones obligatorias y al mismo tiempo ha de extraer el valor que estamos buscando. La biblioteca de coincidencia de patrones para Play es parte de play.libs.F.
Veamos un ejemplo sencillo; tenemos una referencia de tipo Object y queremos comprobar si se trata de una cadena que empieza por ‘command:’.
El modo estándar sería:
Object o = anything();
if(o instanceof String && ((String)o).startsWith("command:")) {
String s = (String)o;
System.out.println(s.toUpperCase());
}
Usando la biblioteca de coincidencia de patrones de Play puede escribirse como:
for(String s: String.and(StartsWith("command:")).match(o)) {
System.out.println(s.toUpperCase());
}
El bucle for se ejecuta una vez, sólo si la condición se cumple, y automáticamente extrae el valor String sin necesidad de hacer casting. Como no hay casting explícito, todo es seguro en tipos (type-safe) y verificado por el compilador.
Las Promise son tipos particularizados por Play del tipo Future. De hecho un Promise<T> es también un Future<T> de modo que puede usarse como un Future estándar. Pero también tiene una propiedad interesante: la capacidad de registrar un método callback usando onRedeem(…) que será invocado tan pronto como el valor prometido esté disponible.
Las instancias de Promise se usan en todas partes en Play en vez de las instancias de Future (para Jobs, WS.async, etc…).
Pueden conbinarse de varias formas. Por ejemplo:
Promise p = Promise.waitAll(p1, p2, p3)
Promise p = Promise.waitAny(p1, p2, p3)
Promise p = Promise.waitEither(p1, p2, p3)
OAuth es un protocolo abierto para un API de autorización segura, usando un enfoque simple y estándar, tanto para las aplicaciones de escritorio como para las aplicaciones web.
Hay dos especificaciones diferentes: OAuth 1.0 y OAuth 2.0. Play incluye bibliotecas para conectarse como consumidor a servicios que soporten cualquiera de estas dos especificaciones.
El proceso general es el siguiente:
Afortunafamente, Play se ocupa de la mayor parte del proceso.
La funcionalidad para OAuth 1.0 está en la clase play.libs.OAuth y se basa en oauth-signpost. La usan servicios como Twitter o Google
Para conectarte a un servicio, tienes que crear una instancia de OAuth.ServiceInfo con la siguiente información, obtenida del proveedor del servicio:
El token de acceso puede ser obtenido de la siguiente manera:
public static void authenticate() {
// TWITTER es un objeto OAuth.ServiceInfo
// getUser() es un metodo que devuelve el usuario actual
if (OAuth.isVerifierResponse()) {
// Ya hemos obtenido el verificador;
// ahora conseguimos los tokens de acceso usando los tokens de request
OAuth.Response resp = OAuth.service(TWITTER).retrieveAccessToken(
getUser().token, getUser().secret
);
// los guardamos y volvemos al indice
getUser().token = resp.token; getUser().secret = resp.secret;
getUser().save()
index();
}
OAuth twitt = OAuth.service(TWITTER);
Response resp = twitt.retrieveRequestToken();
// Hemos recibido los tokens de no-autorizado
// tenemos que guardarlos antes de continuar
getUser().token = resp.token; getUser().secret = resp.secret;
getUser().save()
// Redirigimos al usuario a la pagina de autorizacion
redirect(twitt.redirectUrl(resp.token));
}
Ahora podemos hacer llamadas firmando las requests con la pareja de token:
mentions = WS.url(url).oauth(TWITTER, getUser().token, getUser().secret).get().getString();
Este ejemplo no controla errores, pero en producción deberías hacerlo. El objeto OAuth.Response tiene un campo de errores que es no nulo cuando ha habido algún error. Si lo hay suele ser que el usuario no ha conseguido acceder, o que el proveedor está muerto o tiene problemas.
Hay un ejemplo completo de uso de esta librería en samples-and-tests/twitter-oauth.
OAuth 2.0 es mucho más sencilla que OAuth 1.0 porque no necesita firmar las requests. Lo usan Facebook y 37signals.
La funcionalidad para esto viene en play.libs.OAuth2.
Para conectarse a un servicio, hay que crear una instancia de OAuth2 con la siguiente información, que se obtiene del proveedor del servicio:
public static void auth() {
// FACEBOOK es un objeto OAuth2
if (OAuth2.isCodeResponse()) {
// authUrl ha de ser la misma que en la llamada a retrieveVerificationCode
OAuth2.Response response = FACEBOOK.retrieveAccessToken(authUrl);
// nulo si ha habido error
String accessToken = response.accessToken;
// nulo si la llamada ha tenido exito
OAuth2.Error = response.error;
// salvar accessToker, lo necesitaremos para solicitar el servicio
index();
}
// authUrl es una String que contiene una URL absoluta donde el servicio
// tendra que redirigir al usuario
// Esto levantara una redireccion
FACEBOOK.requestVerificationCode(authUrl);
}
Una vez que tienes el token de acceso asociado al usuario actual, puede usarse para solicitar al servicio en nombre del usuario:
WS.url(
"https://graph.facebook.com/me?access_token=%s", access_token
).get().getJson();
Hay un ejemplo de uso completo en samples-and-tests/facebook-oauth2.
OpenID es un sistema de identidades abierto y descentralizado. Puedes aceptar fácilmente nuevos usuarios en la aplicación sin tener que guardar información específica de cada uno de ellos. Basta con que conserves el registro de los usuarios autorizados mediante su OpenID.
Este ejemplo aporta una vista de alto nivel de cómo puede usarse la autentificación OpenID con una aplicación Play:
La funcionalidad para OpenID está en la clase play.libs.OpenID.
@Before(unless={"login", "authenticate"})
static void checkAuthenticated() {
if(!session.contains("user")) {
login();
}
}
public static void index() {
render("Hello %s!", session.get("user"));
}
public static void login() {
render();
}
public static void authenticate(String user) {
if(OpenID.isAuthenticationResponse()) {
UserInfo verifiedUser = OpenID.getVerifiedID();
if(verifiedUser == null) {
flash.error("Oops. La autentificación ha fallado");
login();
}
session.put("user", verifiedUser.id);
index();
} else {
if(!OpenID.id(user).verify()) { // redirigimos al usuario
flash.error("Cannot verify your OpenID");
login();
}
}
}
Y la plantilla login.html:
#{if flash.error}
<h1>${flash.error}</h1>
#{/if}
<form action="@{Application.authenticate()}" method="POST">
<label for="user">¿Cuál es su OpenID?</label>
<input type="text" name="user" id="user" />
<input type="submit" value="login…" />
</form>
</code>
Y finalmente las definiciones de routes:
GET / Application.index
GET /login Application.login
* /authenticate Application.authenticate
Próximos pasos
Ahora veremos cómo ejecutar operaciones fuera de cualquier pedido HTTP utilizando Tareas aincrónicas (Jobs).
Comentarios