% -*- mode: latex; -*- mustache tags: \ifx\wholebook\relax\else \documentclass[10pt,twoside,english]{root/support/latex/sbabook/sbabook} \usepackage{import} \subimport{root/support/latex/}{common.tex} \hypersetup{pdfinfo = { Title = {Construire une interface Web avec Teapot pour TinyBlog}, Author = {}, Keywords = {pharo, smalltalk}}} \begin{document} \fi \chapter{Une Interface Web avec Teapot pour TinyBlog}\section{Correction semaine pr\'{e}c\'{e}dente} Vous pouvez charger la correction de la semaine pr\'{e}c\'{e}dente en ex\'{e}cutant le code suivant: \begin{displaycode}{smalltalk} Gofer new smalltalkhubUser: 'PharoMooc' project: 'TinyBlog'; package: 'ConfigurationOfTinyBlog'; load. #ConfigurationOfTinyBlog asClass loadWeek2Correction \end{displaycode} Apr\`{e}s le chargement d'un package, il est recommand\'{e} d'ex\'{e}cuter les tests unitaires qu'il contient afin de v\'{e}rifier le bon fonctionnement du code charg\'{e}. Pour cela, vous pouvez lancer l'outil TestRunner (World menu \textgreater{} Test Runner), chercher le package TinyBlog-Tests et lancer tous les tests unitaires de la classe \textcode{TBBlogTest} en cliquant sur le bouton \symbol{34}Run Selected\symbol{34}. Tous les tests doivent \^{e}tre verts. Une alternative est de presser l'icone verte qui se situe \`{a} cot\'{e} de la class \textcode{TBBlogTest}. Ouvrez maintenant un browser de code pour regarder le code des classes \textcode{TBBlog} et \textcode{TBBlogTest} et compl\'{e}ter votre propre code si n\'{e}cessaire. Avant de poursuivre, n'oubliez pas de committer une nouvelle version dans votre d\'{e}p\^{o}t sur Smalltalkhub ou SS3 si vous avez modifi\'{e} votre application. \section{Une interface web avec Teapot} Dans la suite, nous allons cr\'{e}er une premi\`{e}re interface web pour TinyBlog en utilisant Teapot (\url{http://smalltalkhub.com/#!/~zeroflag/Teapot}). Nous verrons une solution bien plus compl\`{e}te avec Seaside par la suite. \subsection{La classe TBTeapotWebApp} Cr\'{e}er une classe nomm\'{e}e \textcode{TBTeapotWebApp} ainsi: \begin{displaycode}{smalltalk} Object subclass: #TBTeapotWebApp instanceVariableNames: 'teapot' classVariableNames: 'Server' package: 'TinyBlog-Teapot' \end{displaycode} La variable \textcode{teapot} contiendra un petit server HTTP Teapot. Ici on utilise une impl\'{e}mentation diff\'{e}rente du Design Pattern Singleton en utilisant une variable de classe nomm\'{e}e \textcode{Server}. Nous faison cela afin de ne pas avoir deux serveurs g\'{e}rant les connexions sur le m\^{e}me port. Ajouter la m\'{e}thode d'instance \textcode{initialize} pour initialiser la variable d'instance \textcode{teapot} : \begin{displaycode}{smalltalk} TBTeapotWebApp >> initialize super initialize. teapot := Teapot configure: { #port -> 8081. #debugMode -> true }. \end{displaycode} \subsection{La Page d'accueil} D\'{e}finissons une m\'{e}thode \textcode{homePage} dans le protocol 'html' qui retourne le code HTML de la page d'accueil de notre application web. Commen\c{c}ons par une version simple : \begin{displaycode}{smalltalk} TBTeapotWebApp >> homePage ^ '

TinyBlog Web App

' \end{displaycode} \subsection{D\'{e}clarer les URLs (routes)} Ajouter maintenant une m\'{e}thode \textcode{start} pour que l'objet teapot r\'{e}ponde \`{a} des URLs particuli\`{e}res. Commen\c{c}ons par r\'{e}pondre \`{a} l'URL \textcode{/} lorsqu'elle est acc\'{e}d\'{e}e en GET : \begin{displaycode}{smalltalk} TBTeapotWebApp >> start "a get / is now returning an html welcome page" teapot GET: '/' -> [ self homePage ]; start \end{displaycode} \subsection{Stopper l'application} Ajouter \'{e}galement une m\'{e}thode pour stopper l'application : \begin{displaycode}{smalltalk} TBTeapotWebApp >> stop teapot stop \end{displaycode} \subsection{D\'{e}marrage l'application } Ajouter deux m\'{e}thodes \textcode{start} et \textcode{stop} c\^{o}t\'{e} classe pour respectivement d\'{e}marrer et arr\^{e}ter l'application dans le protocol 'start/stop'. Ces m\'{e}thodes utilisent la variable de class \textcode{Server} pour implanter un Singleton. \begin{displaycode}{smalltalk} TBTeapotWebApp class >> start Server ifNil: [ Server := self new start ] \end{displaycode} \begin{displaycode}{smalltalk} TBTeapotWebApp class >> stop Server ifNotNil: [ Server stop. Server := nil ] \end{displaycode} \begin{figure} \begin{center} \includegraphics[width=0.6\textwidth]{figures/homepage.png}\caption{Une premi\`{e}re page servie par notre application.\label{fighome}}\end{center} \end{figure} \section{Tester votre application} Maintenant nous pouvons lancer notre application en ex\'{e}cutant le code suivant pour d\'{e}marrer votre application : \begin{displaycode}{smalltalk} TBTeapotWebApp start \end{displaycode} Avec un navigateur web, vous pouvez acc\'{e}der \`{a} l'application via l'URL \url{http://localhost:8081/}. Vous devriez voir s'afficher le texte \symbol{34}TinyBlog Web App\symbol{34} comme la figure \ref{fighome}. \begin{figure} \begin{center} \includegraphics[width=0.6\textwidth]{figures/withPosts.png}\caption{Afficher la liste des titres de posts.\label{figposts}}\end{center} \end{figure} \section{Afficher la liste des posts} On souhaite maintenant modifier le code de la m\'{e}thode \textcode{homePage} pour que la page d'accueil affiche la liste de tous les posts visibles. Pour rappel, tous les posts peuvent \^{e}tre obtenus via \textcode{TBBlog current allVisibleBlogPosts}. Ajoutons une m\'{e}thode d'acc\`{e}s aux posts dans le protocol 'accessing' et modifions la m\'{e}thode \textcode{homePage} ainsi que deux petites m\'{e}thodes auxilliaires. \begin{displaycode}{smalltalk} TBTeapotWebApp >> allPosts ^ TBBlog current allVisibleBlogPosts \end{displaycode} Comme il faut g\'{e}n\'{e}rer une longue chaine de caract\`{e}res contenant le code HTML de cette page, nous utilisons un fl\^{o}t (stream) dans la m\'{e}thode \textcode{homePage}. Nous avons \'{e}galement d\'{e}coup\'{e} le code en plusieurs m\'{e}thodes dont \textcode{renderPageHeaderOn:} et \textcode{renderPageFooterOn:} qui permettent de g\'{e}n\'{e}rer l'en-t\^{e}te et le pieds de la page html. \begin{displaycode}{smalltalk} TBTeapotWebApp >> homePage ^ String streamContents: [ :s | self renderPageHeaderOn: s. s << '

TinyBlog Web App

'. s << ''. self renderPageFooterOn: s. ] \end{displaycode} Notez que le message \textcode{\textless{}\textless{}} est un synonyme du message \textcode{nextPutAll:} qui ajoute une collection d'\'{e}l\'{e}ments dans un fl\^{o}t (stream). \begin{displaycode}{smalltalk} TBTeapotWebApp >> renderPageHeaderOn: aStream aStream << '' \end{displaycode} \begin{displaycode}{smalltalk} TBTeapotWebApp >> renderPageFooterOn: aStream aStream << '' \end{displaycode} Tester l'application dans un navigateur web, vous devez maintenant voir la liste des titres des posts comme dans la figure \ref{figposts}. Si ce n'est pas le cas, assurez vous que votre blog a bien des posts. Vous pouvez utiliser le message \textcode{createDemoPosts} pour ajouter quelques postes g\'{e}n\'{e}riques. \begin{displaycode}{smalltalk} TBBlog createDemoPosts \end{displaycode} \section{D\'{e}tails d'un Post}\subsection{Ajouter une Nouvelle Page} Am\'{e}liorons notre application. On souhaite que l'URL \url{http://localhost:8081/post/1} permette de voir le post num\'{e}ro 1. Commen\c{c}ons par penser au pire, et d\'{e}finissons une m\'{e}thode pour les erreurs. Nous d\'{e}finissons la m\'{e}thode \textcode{errorPage} comme suit : \begin{displaycode}{smalltalk} TBTeapotWebApp >> errorPage ^ String streamContents: [ :s | self renderPageHeaderOn: s. s << '

Oups, an error occurred

'. self renderPageFooterOn: s. ] \end{displaycode} Teapot permet de d\'{e}finir des routes avec des patterns comme '\textless{}id\textgreater{}' dont la valeur est ensuite accessible dans l'objet requ\^{e}te re\c{c}u en param\`{e}tre du bloc. Nous modifions donc la m\'{e}thode \textcode{start} pour ajouter une nouvelle route \`{a} notre application permettant d'afficher le contenu d'un post. \begin{displaycode}{smalltalk} TBTeapotWebApp >> start teapot GET: '/' -> [ self homePage ]; GET: '/post/' -> [ :request | self pageForPostNumber: (request at: #id) asNumber ]; start \end{displaycode} Il faut maintenant d\'{e}finir la m\'{e}thode \textcode{pageForPostNumber:} qui affiche toutes les informations d'un post: \begin{displaycode}{smalltalk} TBTeapotWebApp >> pageForPostNumber: aPostNumber |currentPost| currentPost := self allPosts at: aPostNumber ifAbsent: [ ^ self errorPage ]. ^ String streamContents: [ :s | self renderPageHeaderOn: s. s << ('

', currentPost title, '

'). s << ('

', currentPost date mmddyyyy, '

'). s << ('

Category: ', currentPost category, '

'). s << ('

', currentPost text, '

'). self renderPageFooterOn: s. ] \end{displaycode} Dans le code ci-dessus, on peut voir que le nombre pass\'{e} dans l'URL est utilis\'{e} comme la position du post \`{a} afficher dans la collection des posts. Cette solution est simple mais fragile puisque si l'ordre des posts dans la collection change, une m\^{e}me URL ne d\'{e}signera plus le m\^{e}me post. \subsection{Ajouter des liens vers les posts} Modifions la m\'{e}thode \textcode{homePage} pour que les titres des posts soient des liens vers leur page respective. \begin{displaycode}{smalltalk} TBTeapotWebApp >> homePage ^ String streamContents: [ :s | self renderPageHeaderOn: s. s << '

TinyBlog Web App

'. s << ''. self renderPageFooterOn: s ] \end{displaycode} Maintenant, la page d'accueil de l'application affiche bien une liste de lien vers les posts. \section{Extensions possibles} Cette application est un exemple p\'{e}dagogique \`{a} travers lequel vous avez manipul\'{e} des collections, des fl\^{o}ts (Streams), etc. Plusieurs \'{e}volutions peuvent \^{e}tre apport\'{e}es telles que: \begin{itemize} \item sur la page de d\'{e}tails d'un post, ajouter un lien pour revenir \`{a} la page d'accueil, \item ajouter une page affichant la liste cliquable des cat\'{e}gories de posts, \item ajouter une page affichant tous les posts d'une cat\'{e}gorie donn\'{e}e, \item ajouter des styles CSS pour avoir un rendu plus agr\'{e}able. \end{itemize} \ifx\wholebook\relax\else \end{document} \fi