La nouvelle version de Play2 sortie en début d’année apporte un lot d’innovations et ouvre la porte à de nouvelles possibilités. Play2 est basé sur une approche simple et fonctionnelle : à partir d’une requête, le rôle du serveur est de transformer un ensemble de paramètres et de retourner une réponse. Bref chaque action d’une application Play est tout simplement une fonction. Soit un ensemble de paramètres E, vous appliquez la fonction f(x) et vous obtenez un résultat, qui se trouve être une page Web, un bout de JSON ou un flux Comet par exemple.
Sadek Drobi explique cela très bien dans un article que j’ai traduis et légèrement adapté en Français
Quelques bouts d’architecture de Play2
Play2 Simple HTTP API
Le coeur de l’architecture de Play2 est plutôt simple et reste facile à expliquer dans un article court de blog. Vous pouvez découvrir petit à petit le framework à différents niveaux, et à chaque fois découvrir un peu plus sur certains aspects de son design.
Le coeur de Play2 est vraiment très simple et petit, il est complété par un ensemble d’APIs pratiques, de services et permet de faciliter le développement Web.
De manière simple, Play2 est une API qui abstrait le type suivant :
RequestHeader -> Array[Byte] -> Result
Il s’agit d’une transformation simple qui part de l’entête de la requête HTTP RequestHeader
, puis prends ensuite des bytes de la requête Array[Byte]
et produit un objet Result
.
Lorsque l’on regarde cette approche, ceci présume donc que l’ensemble du body de la requête doit alors tenir en mémoire (ou sur le disque) même si finalement vous ne vouliez que calculer par exemple une valeur ou tout simplement stocker directement vers Amazon S3 cette requête. Pensez à l’upload d’un fichier du navigateur vers le serveur. Cette approche suppose donc que le serveur doit charger intégralement le contenu de la requête (le fichier uploadé) pour ensuite effectuer un traitement, comme celui de sauver l’image vers S3.
Il serait plus efficace de pouvoir recevoir des petits paquets de la requête (request chunk) comme un flux afin de pouvoir le processer progressivement, dans le but d’optimiser l’utilisation des ressources du serveur par exemple.
Pour cela, nous devons changer dans notre formule la deuxième flèche afin qu’elle recoive des données par petits paquetes (chunk) et qu’elle produise éventuellement un résultat ou non. Il existe un type dans Play2 qui fait exactement cela, appeléIteratee
. Il prend 2 paramètres et permet de faire cela.
Iteratee[E,R]
est un type de flèche finalement qui prend en entrée des morceaux de type E
et qui produira éventuellement un résultat de type R
. Pour notre exemple de départ, il nous faut un Iteratee qui prend des chunks de Array[Byte]
et qui retourne éventuellement un Result
ou non.
Notre fonction devient alors
RequestHeader -> Iteratee[Array[Byte],Result]
Ensuite pour notre première flèche, nous utiliserons simplement la fonction Function[From,To] que nous aliaserons avec =>
.
En fait et cela reste simple, si je définis un type infix alias type ==>[E,R] = Iteratee[E,R]
alors je peux écrire mon type de cette façon :
RequestHeader => Array[Byte] ==> Result
Ceci ce lit de la façon suivante : prend les entêtes de la requête HTTP, prend des chunks de Array[Byte]
qui correspondent au final au corps de la requête, puis retourne éventuellement un Result
.
Le type Result
quant à lui peut être abstrait comme étant finalement une composition de header de réponses HTTP et du corps de la réponse.
case class Result(headers: ResponseHeader, body:Array[Byte])
Mais ici encore, ce que nous voulons faire au final ce n’est pas retourner l’ensemble du body, ce qui nous obligerait à le charger complètement en mémoire. Repensez à l’envoi d’un fichier vers S3. Non ici nous voulons garder l’envoi progressif. Nous devons pour cela améliorer notre type. Nous devons donc remplacer dans la case class ci-dessus notre body:Array[Byte]
vers quelque chose qui produit des petits morceaux (des chunks) de Array[Byte]
. Nous pouvons aussi si nous le souhaitons retourner le body en une seule fois. L’un n’empêche pas l’autre.
Ce type existe, il s’agit d’un Enumerator[E]
. Il s’agit d’un type capable de produire ces chunks de type E
, dans notre cas unEnumerator[Array[Byte]]
.
case class Result(headers:ResponseHeaders, body:Enumerator[Array[Byte]])
Enfin il est un peu plus pratique d’être capable de streamer et d’écrire vers notre Socket tout ce qui serait convertible vers unArray[Byte]
, ce qui est assuré par un Writeable[E]
pour un type donné E
:
case class Result[E](headers:ResponseHeaders, body:Enumerator[E])(implicit writeable:Writeable[E])
Conclusion
Le coeur de l’API HTTP de Play2 est très simple :
RequestHeader -> Iteratee[Array[Byte],Result]
ou sinon la version plus sympa avec notre alias ==>
RequestHeader => Array[Byte] ==> Result
La ligne ci-dessus se lit de cette façon : Prend l’objet RequestHeader
puis ensuite des chunks de Array[Byte]
et retourne une réponse. Une réponse est constituée de ResponseHeaders
et d’un body qui est un ensemble de chunks de valeurs, convertibles en Array[Byte]
, prêt à être écrit sur une socket, représentée par le type Enumerator[E]
.
Pour la suite, suivez les articles publiés par Sadek sur son blog