Affichage des articles dont le libellé est play. Afficher tous les articles
Affichage des articles dont le libellé est play. Afficher tous les articles

vendredi 19 avril 2013

Why Scala: La stack (2/2)







Passons maintenant au morceau épineux. Play est un framework web dont la particularité est d'être entièrement stateless, il est mu par Netty, le serveur HTTP asynchrone de RedHat. Il offre des API pour Java comme pour Scala depuis la version 2. C'est évidemment cette dernière facette qui retient mon attention.






Play ne repose pas sur la spécification Servlet (Netty non plus d'ailleurs), le seul standard auquel il semble adhérer est HTTP. Il se décompose principalement en trois couches:


Le routeur: il défini dans un fichier les URI pris en charge par l'application ainsi que le contrôleur associé et la fonction qui sera invoquée.





GET     /                           controllers.Application.index


Au début, je me suis dit que c'était plutôt nase par rapport aux contrôleurs annotés du monde Java mais en fait cela présente un avantage non négligeable: tous les accès HTTP de l'application sont centralisés dans une seule ressource au lieu d'être disséminés dans le code. Ce point a attiré mon attention car j'ai été récemment sensibilisé sur la sécurité applicative et la maîtrise des points d'entrée de l'application est un point clef.





Les contrôleurs: ils prennent la forme d'objects Scala et les fonctions sont chargées d'intégrer le modèle (à la charge du développeur) et doivent retourner des implémentations d'Action





def index = Action {    Ok(views.html.index("Your new application is ready."))  }


Les Action permettent de spécifier la nature du retour: délégation à une vue, JSon, le code HTTP de la réponse, les cookies, etc.







La vue: il est équipé d'un moteur de templating spécifique basé sur Scala: il supporte notamment le Currying et permet de manipuler les collections 






Nous sommes dans la dimension fonctionnelle, donc le typage est primordial, les routes comme les vues sont donc typées, ce ne sont pas simplement des fichiers plats évalués au runtime mais à la compilation.






Somme toute, à quelques différences identitaires près, c'est proche de ce dont nous avons l'habitude au niveau du paradigme.






En plus d'application exemples plutôt riches, Play propose de créer un squelette d'application utilisable instantanément:






$ play new the-babel-tower
_ _
_ __ | | __ _ _ _| |
| '_ \| |/ _' | || |_|
| __/|_|\____|\__ (_)my
|_| |__/

play! 2.1-RC1 (using Java 1.7.0_07 and Scala 2.10.0-RC1), http://www.playframework.org

The new application will be created in /private/tmp/the-babel-tower

What is the application name?
> the-babel-tower

Which template do you want to use for this new application?

1 - Create a simple Scala application
2 - Create a simple Java application

> 1
OK, application the-babel-tower is created.

Have fun!

$ cd the-babel-tower/

$ play run
[info] Loading project definition from /private/tmp/the-babel-tower/project
[info] Set current project to the-babel-tower (in build file:/private/tmp/the-babel-tower/)

[info] Updating {file:/private/tmp/the-babel-tower/}the-babel-tower...
[info] Done updating.
--- (Running the application from SBT, auto-reloading is enabled) ---

[info] play - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

(Server started, use Ctrl+D to stop and go back to the console...)


La page de garde comprend une directive qui génère une vue descriptive de l'application:







@(message: String)

@main("Welcome to Play 2.0") {

@play20.welcome(message)

}



Je ne sais pas pour vous, mais moi ça m'a fait penser au fameux phpinfo(). Et bien tout ça, ça roxxe car dans le développement d'applications web en java, maven permet de faire appel à une myriade de templates d'applications, mais à chaque fois que j'en ai essayé un, j'ai trouvé des dépendances et des configurations dont je n'avais pas nécessairement besoin me contraignant à un peu de ménage. Du coup, quand je démarre un projet j'ai pris l'habitude d'un simple archetype:create que je complète manuellement. Play pose l'essentiel, c'est fonctionnel et n'attend que votre code.








Back to basics. Play est très proche des couches basses. Il est aisé de positionner des headers, des cookies ou le code de retour. Il affiche se destiner au développement d'applications massivement sollicitées et hautement scalables, du coup la philosophie n'est pas d'empiler les couches d'abstractions au dessus de HTTP et de HTML à l'opposé d'autres frameworks MVC, comme si pour tirer le meilleur parti d'une infrastructure le développeur devait maîtriser toutes les couches. En tant que geek, même si c'est une vérité de La Palice, j'abonde en ce sens et j'aime avoir du contrôle sur les couches basses.


Le revers de la médaille est qu'il ne faut pas s'attendre à une galaxie de composants à la PrimeFaces, offrant des fonctionnalités riches pour un minimum d'efforts, comme s'il y avait une relation inversée entre rapidité de développement et poids sur l'environnement d'exécution… Le salut passe donc par Twitter Bootstrap (ou autre pour les HTML Heroes).


C'est d'ailleurs ce qui m'a encouragé à évincer le moteur de templating de Play: j'ai misé sur du HTML statique complété par du MVC client (Angular) et ai pris le parti d'exploiter Play qu'au travers de requêtes REST. Comme déjà argumenté dans un autre billet, si l'architecture est sans état, autant déléguer la charge de génération des vue au client, sa CPU est gratuite pour l'application!






C'est dit et redit, Play est stateless, ce qui implique qu'aucune donnée liée à la session applicative ne figure dans la mémoire du serveur, pas même l'authentification. D'ailleurs pour faire simple il n'y a aucune API intégrée de contrôle d'accès et de gestion des rôles! Les données de session sont stockées dans un cookie de type session signé: il n'est pas forgeable et permet à chacune des instances de l'application de valider le cookie présenté grâce à une clef de cryptage partagée. Cela implique que l'utilisation de la session applicative est totalement différente d'une application web stateful, exit les objets vomis dans la session sous peine de voir le poids des transferts s'envoler et les temps de réponse s'effondrer. Décidément, le profil du développeur éligible se distingue de plus en plus de celui rencontré communément pour le développement des applications d'entreprise…






Revenons sur un point: pas d'API de sécurité intégrée, enfin j'exagère il y a un object (play.api.mvc.Security) offrant deux fonctions. Security ne suffit pas et d'ailleurs le sample Zentasks guide tout de même les newbies tels que moi en proposant une trait à ajouter aux contrôleurs des ressources soumises à contrôle d'accès: elle vérifie l'authentification comme le périmètre de l'utilisateur sur l'application. Dans ses moments là, malgré les efforts investis sur le langage, il devient clair que pouvoir lire aisément du code Scala écrit par autrui va prendre encore un peu de temps, même si après coup, les concepts s'avèrent faciles à intégrer. Je vous invite à consulter le code de Zentasks (
https://github.com/playframework/Play20/blob/master/samples/scala/zentasks/app/controllers/Application.scala#L70). Le guide de référence de la version précédente de cite également un plugin tiers (Deadbolt), j'ai préféré ne pas miser sur cette extension, il m'a paru évident qu'une montée de version du framework pourrait ne pas forcément être suivie par ses extensions, d'autant que l'histoire a démontré que la compatibilité ascendante n'était pas une priorité de Play. Coïncidence: la mention vers ce plugin n'existe plus dans la documentation 2.1, d'ailleurs le chapitre sur la sécurité a disparu… JSR-250 and @RolesAllowed, I miss you!






J'ai décidé de bâtir sur une conception REST, il va donc me falloir du JSON. En Java c'est cool: JAXB, Jackson et POJO annotés, le sujet est traité. Avec Play, ce n'est pas beaucoup plus compliqué mais c'est moche: il intègre également Jackson mais l'utilisation est programmatique (basé sur des maps) au lieu d'être déclarative:






Json.obj(
"users" -> Json.arr(
Json.obj(
"name" -> "bob",
"age" -> 31,
"email" -> "bob@gmail.com"
),
Json.obj(
"name" -> "kiki",
"age" -> 25,
"email" -> JsNull
)
)
)



L'avantage est que la transformation n'est pas soudée à un modèle.







J'ai parlé de Slick précédemment et l'intégration se fait sans surprise, manuellement! Avec le cake pattern. Il y a néanmoins un projet d'intégration que je n'ai pas testé (
https://github.com/freekh/play-slick)






C'est la tendance, pour rationaliser les threads, les requêtes http sont servies de façon asynchrone. Toutefois Play brouille un peu les pistes car il permet que le contenu de l'action du contrôleur soit lui même asynchrone (liée à des I/O, charge transférée à un backend, ou délégué à un autre pool de threads):






def index = Action {
val futureInt = scala.concurrent.Future { intensiveComputation() }
Async {
futureInt.map(i => Ok("Got result: " + i))
}
}







Du coup de prime abord, on est en droit de se poser en toute bonne fois une question parfaitement fausse: pourquoi les actions ne sont elles pas asynchrones par défaut comme peut le proposer Grizzly (embarqué dans Glassfish)? Mais pas de doute, Play traite bel et bien les requêtes de façon asynchrone par défaut, vous en trouverez l'explication détaillée 
ici .






Play revendique être destiné au développement des applications web modernes, à ce titre il documente les types de restitutions un peu particuliers comme les réponses par morceaux (chunked responses), les comets sockets et les web sockets. Tout ça c'est très geek et permet de pousser des données du serveur vers le navigateur. C'est évidemment très apprécié. Ce qu'il l'est moins c'est l'API associée: les Enumerator, Enumeratee and co. Toutefois, ce n'est pas parce que je trouve la conception indigeste qu'elle est mauvaise et je pars du postulat que les gens qui s'aventurent à écrire des frameworks sont forcément brillants, mais quand je suis tombé sur un article titré
"Understanding Play2 Iteratees for Normal Humans", je me suis dit que je n'avais pas du être le seul… J'ai un peu transpiré mais j'ai fini par réussir à utiliser cette API.






Pour continuer sur l'aspect 'application moderne', Play 2 gère les sources CoffeeScript et LESS CSS. C'est cool d'autant que si le code contient une erreur de 'transpilation', elle est identifiée et mise en forme dans le navigateur (il fait mieux qu'IntelliJ sur ce point là!). 






Les modifications du code prises en compte à chaud en mode développement est un autre point positif de Play. Cela change par rapport à Java, car sans JRebel, il faut s'en remettre aux limites du hot code replace du compilateur, ou encore paramétrer le conteneur pour scanner les différences et redéployer l'application le cas échéant. Le coût de la compilation des classes Scala est largement compensé par le fonctionnement automatique, de plus le serveur ne conservant pas l'état des clients, c'est transparent côté navigateur. Un bon point.






Alors finalement, après ce florilège de points tantôt à l'avantage de Play et tantôt pas, que puis-je proposer comme conclusion? D'abord, a-t-il sa place dans la galaxie des frameworks web? Oui assurément, car j'ai décrété précédemment que Scala avait la sienne dans l'écosystème et il faut une technologie web qui repose sur ce langage. Mais du coup Play 2 Scala, c'est pour qui? Même réponse que pour Scala: pour des développeurs murs ou de jeunes stars.
Simple ou simpliste? Il y a un dogme sur lequel j'aimerais m'attarder, Play est souvent qualifié de 'stack légère' et du coup on a l'impression que tout est facile et rapide. Alors on l'a démontré, c'est rapidement prêt à coder, c'est léger à héberger mais la productivité ne me semble pas au rendez-vous, en tous cas au niveau des IHM, le budget développement le plus important d'un projet web: pas ou peu de composants ou autre taglib, il faut taper du HTML et du Javascript, c'est évidemment tendance mais sûrement pas productif. Donc légère implique également légère en fonctionnalités! Ces éléments nous apporte la réponse à la question 'dans quel contexte utiliser Play?': s'il faut des stars et que c'est long à développer, eh bien pas pour le développement web d'entreprise déjà! D'autant que j'ai vu des applications JSF 1.2 codées moyennement se comporter correctement jusqu'à 900 utilisateurs concurrents sur un noeud modeste avec une JVM 32bits, donc avant d'avoir un besoin des atouts de Play, il faut être sûr d'avoir une activité soutenue, ou avoir un budget d'hébergement faible et des capacités en développement (du temps et pas d'argent… PME ou artisanat, non?). Ce qui me pousse à penser qu'il est résolument taillé pour le web, le vrai, pas l'intranet, et même pour le Cloud (alerte #buzzword) vu son appétit d'oiseau en ressources matérielles, d'ailleurs l'éditeur ne s'y trompe pas et propose de déployer chez différents fournisseurs en quelques commandes l'application fraîchement générée.  














Voilà, c'est la fin de mon périple en Fonctionnalie. J'étais parti plein d'espoirs fondés sur ces technologies alternatives, qu'en reste-t-il aujourd'hui? J'ai pris du plaisir à coder d'une autre manière, Scala m'a montré de nouveaux horizons et obligé à revoir des algorithmes de base et ça rafraîchit lorsque le quotidien est meublé par de la "tuyauterie". La pile proposée m'a demandé de me rapprocher des couches basses et j'ai kiffé. Ca c'est pour le côté geek. Maintenant si on devait m'annoncer que mon karma était de ne jamais pouvoir programmer avec ces outils que me manquerait-il le plus? Les lambdas et la fluidité de l'API des collections, les case classes, le pattern matching et l'Option. Voilà pour le langage, mais du côté des frameworks, Slick est fun mais actuellement décevant et JPA est maîtrisé et complet (l'une des specs les plus importantes de JavaEE); Play est hype, agréable mais ne révolutionne pas et j'ai l'impression que le défunt Struts 1 était plus riche! De plus Play est stateless, mais il n'est que stateless; or si les technos web du JCP s'appuient sur des conceptions stateful, il reste néanmoins possible de développer des applications sans état avec les frameworks habituels.  


En anticipant à peine, Java8, ses lambdas et son API Stream conjugués à Lombok (pour donner aux beans la concision des case classes mais pas toutes leurs fonctionnalités)  et à L'Optional de Guava  apparaissent être des palliatifs acceptables... en tous cas en attendant de voir si la stack autour de Scala arrive à se démocratiser.



dimanche 6 janvier 2013

AngularJS + Play! Framework: Authentification





En tant que newbie sur Angular, je me suis posé la question de l'authentification dans ce type d'applications.

Pour ceux qui veulent aller directement à la présentation du code, c'est
là.



Depuis longtemps je suis habitué au développement de web apps dont les pages sont générées dynamiquement sur le serveur (JSP, JSF, PHP, etc.): cette problématique est donc comblée dans la plupart des cas par une redirection vers un formulaire d'authentification. Or avec un framework du type AngularJS, les pages sont générées dynamiquement sur le client à partir de templates nourries de données obtenues du serveur collectées grâce à du code client (Javasript).

D'un point de vue technique cette approche a évidemment énormément de sens, car:





  • Toutes les ressources clientes sont donc "cachables": templates, Javascript

  • A l'heure du Cloud (un buzzword, un), le coût d'hébergement est proportionnel à la sollicitation, il est tout naturel de transférer sur la machine du client (gratuite) toute charge éligilble

  • L'état du client est géré... sur le client (un truc de malade), permettant de réduire la pression sur la mémoire de la partie serveur (au point précédent on a économisé de la CPU, là on s'occupe de la RAM!). Sans compter que si la partie serveur ne maintient plus d'état, n'importe quelle instance pourra servir n'importe quelle requête... Nous parlons donc de conception Stateless (buzzword #2), permettant de déployer les noeuds en ayant aucun autre souci que la répartition de charge (pas de synchronisation entre les noeuds), m'amenant tout doucement vers le 3ème buzzword, la scalabilité horizontale.



En réfléchissant, si la traditionnelle redirection vers le formulaire de login ne semble pas être une option adaptée, que reste-t-il? Facile: le protocole HTTP prévoit au moins deux codes de retour liés aux problèmes de contrôles d'accès: 401, authentification requise, et 403, authentification refusée. Parfait, le premier m'explique que je dois présenter mes papiers et le second me signale que l'authentification fournie ne comprend pas l'accréditation nécessaire pour accéder à la ressource demandée.



Donc me voilà parti sur Google avec les mots clefs "Angular authentication 401" et je tombe sur un article fort intéressant:
http://www.espeo.pl/2012/02/26/authentication-in-angularjs-application . La démonstration est complète: toutes les requêtes tenues en échec suite à une erreur 401 sont bufferisées, en attente de la connexion de l'utilisateur et retentées le cas échéant. Et si on veut quelque chose de plus basique? Un peu plus newbie? Genre une redirection vers un formulaire de login à la première erreur 401 et une redirection a la racine suite à authentification? C'est sûrement pas aussi complet, mais l'aspect naïf permet de monter tranquillement en compétence sur AngularJS.



Avant toute chose, il nous faut un backend, mon choix c'est porté sur Play 2.1-RC1 car:



  • Il est stateless par essence

  • C'est hype, j'en avais envie et c'est moi le chef du blog (#noTroll)

Point de départ standard avec une application Play vide, ensuite on récupère une partie de l'authentification du sample
zentask en modifiant quelques aspects:



  • Tout d'abord, en cas de nécessité d'authentification, pas de redirection => 401:


  private def onUnauthorized(request: RequestHeader) = Results.Redirect(routes.Application.login)
devient:


  private def onUnauthorized(request: RequestHeader) = Results.Unauthorized





  • La démonstration nécessite une ressource protégée:




  def protectedResource = IsAuthenticated{
    username => _ =>
    Ok(Json.obj("test"->"1234"))
  }






  • Comme on veut faire une application cliente qui se connecte à un backend, pas d'utilisation des templates Play et modification de la route pour que la racine pointe sur une ressource statique:

GET     /                           controllers.Assets.at(path="/public", file="index.html")



Le code est disponible
ici.



Vérifications:





  • Pas autorisé:




$ curl http://localhost:9000/protectedResource -v
* About to connect() to localhost port 9000 (#0)
*   Trying ::1...
* connected
* Connected to localhost (::1) port 9000 (#0)
> GET /protectedResource HTTP/1.1
> User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8r zlib/1.2.5
> Host: localhost:9000
> Accept: */*
>
< HTTP/1.1 401 Unauthorized
< Content-Length: 0
<
* Connection #0 to host localhost left intact
* Closing connection #0


L'application réfute bien l'accès avec un code 401.





  • Authentification:




$ curl http://localhost:9000/login -d 'mail=tony@stark.com&password=ironman' -v
* About to connect() to localhost port 9000 (#0)
*   Trying ::1...
* connected
* Connected to localhost (::1) port 9000 (#0)
> POST /login HTTP/1.1
> User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8r zlib/1.2.5
> Host: localhost:9000
> Accept: */*
> Content-Length: 36
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 36 out of 36 bytes
< HTTP/1.1 200 OK
< Set-Cookie: PLAY_SESSION=1701c43b7f845bdde0e38c0f43705d54b6815977-mail%3Atony%40stark.com; Path=/; HTTPOnly
< Content-Length: 0
<
* Connection #0 to host localhost left intact
* Closing connection #0

Code 200, un cookie signé de session en retour… tout va bien, donc on retente l'accès à la ressource protégée en présentant le sésame:




$ curl http://localhost:9000/protectedResource -b "PLAY_SESSION=1701c43b7f845bdde0e38c0f43705d54b6815977-mail%3Atony%40stark.com" -v
* About to connect() to localhost port 9000 (#0)
*   Trying ::1...
* connected
* Connected to localhost (::1) port 9000 (#0)
> GET /protectedResource HTTP/1.1
> User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8r zlib/1.2.5
> Host: localhost:9000
> Accept: */*
> Cookie: PLAY_SESSION=1701c43b7f845bdde0e38c0f43705d54b6815977-mail%3Atony%40stark.com
>
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=utf-8
< Content-Length: 15
<
* Connection #0 to host localhost left intact
{"test":"1234"}* Closing connection #0


On obtient donnée attendue, authentification réussie!



Maintenant je m'attaque au client:




<html ng-app="angularAuth" authenticator>
<head>
  <title>Angular authent</title>
    <script type="text/javascript" src="/public/javascripts/angular/angular.min.js"></script>
    <script type="text/javascript" src="/public/javascripts/angular/angular-resource.min.js"></script>
    <script type="text/javascript" src="/public/javascripts/app.js"></script>
    <script type="text/javascript" src="/public/javascripts/controllers.js"></script>
</head>
<body>
<h1>Simple authentication example</h1>
<div ng-view></div>
</body>
</html>


Mon module AngularJS est sommaire:




angular.module("angularAuth",['authServiceProvider']).
    config(['$routeProvider',function($routeProvider){
        $routeProvider.
            when("/", {templateUrl:"public/partials/protectedContent.html", controller:ProtectedCtrl}).
            when("/login",{templateUrl:"public/partials/login.html", controller:LoginCtrl}).
            otherwise({redirectTo:"/"})
    }]).
    directive('authenticator',function($location){
        return function(scope, elem, attrs){
            scope.$on('event:auth-loginRequired',function(){
                $location.path("/login")
            })
        }
    })  ;



En gros, quand "/" est demandé, affichage du template tirant la ressource protégée et lorsque "/login" est demandé, affichage du formulaire de login et redirection dans tous les autres cas.

Ensuite un listener va être positionné afin de réagir à l'évènement de demande d'authentification. L'utilisation de la directive permet de le placer au niveau du module, soit une fois pour toutes. Ne pas oublier de mentionner la directive dans le fichier html (moi, j'ai cherché un moment au début :-D).



Le concept présenté par Witold Szczerba est de tirer parti de la possibilité de poser des intercepteurs sur le service $http, c'est ce que je vais faire mais de façon plus naïve dans le module "authServiceProvider" injecté dans "angularAuth":




angular.module('authServiceProvider', []).
config(['$httpProvider', function($httpProvider) {

$httpProvider.responseInterceptors.push(function($q,$rootScope,$log){
function success(response) {
// $log.info(response)
return response
}

function error(response) {
if (response.status === 401) {
$log.error("401!!!!")
$rootScope.$broadcast('event:auth-loginRequired')
}
return $q.reject(response)
}

return function(promise) {
return promise.then(success, error)
}

})

}])



La fonction: si une requête retourne une erreur 401, alors l'évènement de demande d'authentification est propagé et stimule ainsi la directive et le formulaire de login apparaît. Je vous épargne un couplet sur l'API Promise d'AngularJS et cous encourage à aller consulter le Reference Guide.



Les contrôleurs du
formulaire d'authentification, de récupération de la ressource protégée et les
templates associés sont spartiates et consultables sur GitHub.



En introduction, j'ai présenté les arguments liés à la rationalisation de l'infrastructure d'hébergement, en revanche à l'heure actuelle il est clair que la charge de développement est supérieure comparée à une application dans laquelle les vues sont générées par le serveur: on y déclare simplement l'emplacement du formulaire de login.



La démo est testable sur
Heroku.